Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use withLatestFrom with a selector in ngrx?

Tags:

angular

rxjs

ngrx

I have ngrx application, and I can't get the value from a selector in the effect, by the payload I sent in the component.

I write an example code of what I deal with:

This is my state, a simple docs array in it.

export const initialState = {
  docs: [{ id: 1, name: "doc1" }, { id: 2, name: "doc2" }]
};

export const reducer = createReducer(
  initialState,
);

I dont handle any action for this questions, this is not the issue.

In my component I get the array using a selector:

docs$ = this.store.pipe(select(fromStore.getAllDocs));

{{docs$ | async | json}}

the reducer:

export const selectDocsState = createFeatureSelector("docs");

export const selectDocsStatusState = createSelector(
  selectDocsState,
  state => state["docs"]
);

export const getAllDocs = createSelector(
  selectDocsStatusState,
  getDocs
)

I also have a selector to get by id:

{{docsById$ | async | json }}

docsById$ = this.store.pipe(select(fromStore.getById, { id: 1 }));

in the reducer:

export const getById = createSelector(
  selectDocsStatusState,
  getDocs,
  (state, docs, props) => {
    console.log({ props });
    return docs.docs.docs.filter(d => d.id === props.id);
  }
)

So far everything good. I get the docs array and display it.

Now, in my component I dispatch some action and catch by effect:

this.store.dispatch({ type: 'GET_DOC', payload: { id: 2 } });

And in the effect I want to know if this id is in the store or not.

So I use withLatestFrom from rxjs with the getById selector.

withLatestFrom((a: any) =>
  this.store.pipe(select(fromDocs.getById, { id: a.payload.id }))
),

The problem is i'm not getting the value from the selector instend I get the store instance.

tap(v => {
  console.log({ v });
}),

The full effect:

getDoc$ = createEffect(() =>
    this.actions$.pipe(
      ofType("GET_DOC"),
      map(a => a),
      tap(a => {
        console.log({ a });
      }),
      withLatestFrom((a: any) =>
        this.store.pipe(select(fromDocs.getById, { id: a.payload.id }))
      ),
      tap(v => {
        console.log({ v }); // <--------------- HERE SHOULD BE THE DOC AND THE ACTION.PAYLOAD, v[0].a.payload, v[1].id
      }),
      exhaustMap(payload => {
        return of({ type: "SOME_ACTION", payload: null });
      })
    )
  );

Here is my full example in stackbliz.

What do I missing here?

like image 501
Jon Sud Avatar asked Feb 18 '20 14:02

Jon Sud


People also ask

Can I use selector in effect NgRx?

Create Menu Selectors Similar to what you did with Actions, let's update the application to get data required by the components using NgRx Selectors. You can use Selectors by injecting NgRx's Store and calling it's select function passing in the selector's name, which returns the slice of the state that we need.

How do selectors work in NgRx?

Selectors are basically the NgRx change detection mechanism. When a state is updated, NgRx doesn't do any comparison between old state and the new state to figure out what observables to trigger. It simply sends a new state through all top level selectors.

How do you call an effect in NgRx?

Getting started. It will add and install the @ngrx/effects library to your package. json and scaffold your AppModule to import the NgRx EffectsModule into your application. With the setup complete, we can start modifying the app to introduce and handle some API calls using Effects.

What is withLatestFrom?

withLatestFrom combines each value from the source Observable (the instance) with the latest values from the other input Observables only when the source emits a value, optionally using a project function to determine the value to be emitted on the output Observable.


1 Answers

This is covered in the NgRx Docs - Incorporating State but I found Brandon Roberts github comment more useful:


There are two predominant things you do when listening to an action after using the ofType operator:

actions.pipe(
  ofType('SOME_ACTION')
  someMap(action => doSomething(action))
)

and

actions.pipe(
  ofType('SOME_ACTION'),
  withLatestFrom(store.select(someThing)),
  someMap(([action, latest]) => doSomething(action, latest))
)

These have two different behaviors as far as effects are concerned. The first one doesn't do anything until the action is dispatched, which is what it should do.

The second one immediately subscribes to the withLatestFrom whether the action is dispatched yet or not. If that state used by the selector is not initialized yet, you could get an error you're not expecting. In order to make it "lazy" you end up having to nest it within a flattening observable operator.

actions.pipe(
  ofType('SOME_ACTION'),
  someMap(action =>
    of(action).pipe(
      withLatestFrom(store.select(someThing)),
      someMap(([action, latest]) => doSomething(action, latest))
    )
)

Applying this gives the following:

  getDoc$ = createEffect(() => {
    return this.actions$.pipe(
      ofType(fromDocs.getDocument),
      switchMap(action =>
        of(action).pipe(
          withLatestFrom(
            this.store.select(fromDocs.getById, { id: action.payload.id })
          ),
          map(([action, latest]) => {
            return fromDocs.someAction();
          })
        )
      )
    );
  });

Stackblitz

like image 174
Andrew Allen Avatar answered Sep 18 '22 17:09

Andrew Allen