angulartypescriptrxjsngrxngrx-entity

How can I execute an Action/Effect after properties in my state are initialized?


I have a store for a component that is new with each new component declaration. In ngOnInit I need to set the value of three properties on the state of the reducer. After that is executed, I need to call another dispatch which fires off an effect that loads data from my server.

Specifically, the data that is loaded by the effect has to reference the preceding three state properties that are initialized in state. However, when I set a breakpoint in the effect, the data is null which tells me that my dispatch calls to set the values haven't completed.

I know that you are suppose to subscribe to data that is saved into the store and then react to it. Maybe that's what I need to do, but I can not figure out how to daisy-chain the assignment of three state properties and THEN call the dispatch that launches the data loading NgRx effect. Here's some pseudo code of what I'm trying to do.

From inside ngOnInit

this.store.dispatch(actions.set_foo({ value: "A"}))
this.store.dispatch(actions.set_bar({ value: "B"}))
this.store.dispatch(actions.set_baz({ value: "C"}))

//This will call an effect.  The effect needs the data stored in state.foo, state.bar and state.baz
//How do I change this call so that it waits/subscribes to the assignment of Foo, Bar & Baz?
this.store.dispatch(actions.load_data_from_server());

From inside the called effect

loadData$ = createEffect(
  ()=>this.actions$.pipe(
    ofType(actions.load_data_from_server),

    //selectParameterData is a selector that returns a composite object of Foo/Bar/Baz. There might be a better way to do this, but this allowed me to get three state properties in one.
    withLatestFrom(this.store$.select(selectors.selectParameterData),
    mergeMap([action, data]=>
    
    ... Code that makes a data ball to the server, passing in values from Foo/Bar/Baz ...
        This the place where the data is uninitialized.   
  )
)

Please note, I can restructure all of this code however it should be properly done. Our team has decided that we need to migrate our Angular application to NgRx, and these are the problems I need to solve, setting up an example of migrating a portion of our application to NgRx. Thanks for any help.

And so that this is clearly a question, how do I set multiple properties on my state and only after they've been assigned, load data from the server, referencing those properties on my reducer state object?


Solution

  • You can chain the action handlers like this:

    From inside ngOnInit

    this.store.dispatch(actions.set_all({ a: "A", b: "B", c: "C"} ));
    

    From inside the called effect

    setAll$ = createEffect(
      () =>  this.actions$.pipe(
          ofType(actions.set_all),
          concatMap(t => {
             return [
                actions.set_foo({ value: t.a} ),
                actions.set_bar({ value: t.b} ),
                actions.set_baz({ value: t.c} ),
                actions.load_data_from_server
             ];
          })
    )
    loadData$ = createEffect(
      ()=>this.actions$.pipe(
        ofType(actions.load_data_from_server),
    
        //selectParameterData is a selector that returns a composite object of Foo/Bar/Baz. There might be a better way to do this, but this allowed me to get three state properties in one.
        withLatestFrom(this.store$.select(selectors.selectParameterData),
        mergeMap([action, data]=>
        
        ... Code that makes a data ball to the server, passing in values from Foo/Bar/Baz ...
            data is now initialized.   
      )
    )
    

    Alternate Solution

    Or schedule the dispatch using the async scheduler with setTimeout which will trigger the last dispatch in the next cycle of the event loop. Warning: this will trigger change detection a second time (compared to the previous solution).

    this.store.dispatch(actions.set_foo({ value: "A"} ))
    this.store.dispatch(actions.set_bar({ value: "B"}))
    this.store.dispatch(actions.set_baz( { value: "C" }))
    setTimeout(() => {
        this.store.dispatch(actions.load_data_from_server());
    });