How to use withLatestFrom with a selector in ngrx?

吃可爱长大的小学妹 提交于 2020-05-30 11:37:42

问题


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?


回答1:


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




回答2:


withLatestFrom doesn't take a composite function, it takes a secondary observable source.

What you can do is use switchMap to switch to the new stream and see its value.

Try:

getDoc$ = createEffect(() =>
    this.actions$.pipe(
      ofType("GET_DOC"),
      map(a => a), // does nothing
      tap(a => {
        console.log({ a });
      }),
      switchMap((a: any) => this.store.pipe(select(fromDocs.getById, { id: a.payload.id }))),
      tap(doc => {
        // can switch this tap to a map and see if there is a value then return this, if not then make an API call
        console.log(doc);
      }),
      exhaustMap(payload => {
        return of({ type: "SOME_ACTION", payload: null });
      })
    )


来源:https://stackoverflow.com/questions/60282687/how-to-use-withlatestfrom-with-a-selector-in-ngrx

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!