0

I have a guard, where I want to ensure the data is pre-loaded before the page visually loads. I've been following Todd Motto's strategy, for pre-loading data that only requires one dispatch.

The issue is, my scenario requires multiple dispatches, where some are dependent on others.

I need to dispatch to load object A, then I need to use some data on object A to load object B.

For the sake of this minimal example, assume my state has loading (boolean), and things[] (loaded entities). When I do a Get action, loading is flipped to true. loading gets flipped to false on an unseen success action.

canActivate(route: ActivatedRouteSnapshot): Observable<boolean> {
    // fetches the first `thing` with the route's id
    this.store.dispatch(new actions.Get(route.params.id));
    return this.store
        .select(selectState)
        .filter(state => !!!state.loading)
        .do(state => {
            // the first `thing` is loaded
            // now use it to load another `thing`
            this.store.dispatch(new actions.Get(state.things[0].something));
        })
        .take(1)
        .switchMap(() => this.store.select(selectState))
        // in the filter, state.loading is false, even though
        // we just fired a dispatch to say it's loading. 
        // ...because this is checked before the
        // dispatch can flip the boolean
        .filter(state => !!!state.loading)
        .take(1)
        .switchMap(() => Observable.of(true))
        .catch(() => Observable.of(false));
}

While everything works fine for the first "wait for this to load", the subsequent dispatches don't flip my loading state variable to true before the next "wait for this to load" check.

Any help, or maybe a better pattern, is greatly appreciated.

CozyAzure
  • 8,280
  • 7
  • 34
  • 52
Mike
  • 4,071
  • 21
  • 36
  • One question I have is if loading is by default set to false why use the !!!? I'm not familiar with that check I know you can check an operator as truthy with !! but never seen !!! but even still it shouldn't be required. The other thing that looks odd to me is the the slice of state you are selecting is the same in both instances. Wouldn't you have a loading for object one, and a different loading for object 2? – Woot Feb 06 '18 at 19:43
  • I am using `!!!` to both convert it to a boolean, and check for "not" loading, since I want it to continue when the loading has finished. The slice of the state is the same, since I'm loading something that is stored in that state, then I need to load another of the same type of entity based on what I already loaded. In reality, I'm loading some entity, then I need to get the parent ID off of it, then load the parent. – Mike Feb 06 '18 at 20:29
  • There's no `!!!` operator and there's no `!!` operator. There's just the `!` operator [used two/three times in sequence](https://stackoverflow.com/questions/784929/what-is-the-not-not-operator-in-javascript). Three `!!!` has the exact same effect as one `!`. – noppa Feb 06 '18 at 20:51
  • so we select a slice of state, in that slice we check out loading, when loading is true we do dispatch and action, then we take 1, then we selected the same slice of state again, and check to see if it's loading again, take 1, and return. What's confusing me is does get action against the parent change the childs loading property? and if it does why? perhaps I am just coming at this from the wrong angle? – Woot Feb 06 '18 at 20:57
  • @Woot I'm selecting the same slice again, because the data from both the first check and the second check are stored there. When I call a `Get(1)`, it throws `{ id: 1, parentId: 2 }` into `state.entities`. Then I need to fetch `Get(2)` (where 2 = the parentId of the previous Get) and shove that result into `state.entities` as well. Am I approaching this from the wrong line of thinking? – Mike Feb 06 '18 at 22:05
  • @Mike I don't think your approach is wrong. I have similar code for sure. Where I am getting confused is in my state I would have a property for loaded and a property for parentLoaded. When the first object loads successfully I would set loaded to true, and the same with the second object. Then in my activator I would checked if the first object is loaded and if not dispatch. once loaded dispatch the second and once that one is loaded return true. Not sure if I'm making sense I will try and update my answer. – Woot Feb 06 '18 at 22:18
  • Oooh okay, I do understand you: I would have another attribute in my state that can tell me when the second stage of this data loading is completed. I will certainly try your method soon using your answer below. Thanks! – Mike Feb 06 '18 at 22:23

3 Answers3

2

Let's say my state looks like this

{
  user: User;
  userLoaded: boolean;
  userRoles: Role;
  userRolesLoaded: boolean
}

My goal is to not load a route until both the user and the userRoles are loaded.

Let's assume I have created selectors for getUserLoaded, and getUserRolesLoaded that only return the boolean values from my state for their respective properties.

Here is my approach to load multiple

canActivate(route: ActivatedRouteSnapshot): Observable<boolean> {
  return this.store.select(getUserLoaded).pipe(
      map(loaded => {
        // if my user is loaded this will be true
        if (!loaded) {
          this.store.dispatch(new fromStore.LoadUser());
        }
      }),
      filter(loaded) => loaded),
      take(1),
      withLatestFrom(this.store.select(fromStore.getUserRolesLoaded)),
      map(([nullData, loaded]) => {
        if(!loaded) {
          this.store.dispatch(new fromStore.LoadUserRoles()
        }
      }),
      withLatestFrom(this.store.select(fromStore.getUserRolesLoaded)),
      filter(([data, loaded]) => loaded),
      take(1)
    );
}

So here I first get the slice of state that is my userLoaded boolean. If my user is not loaded then I dispatch the action that will load the user. Then I filter it so that it won't return until true and take one to unsubscribe. Next I get the slice of state that would have getUserRolesLoaded and I check if they are loaded if not I dispatch, add a filter wait for true and take 1.

I am using the pipe method introduced in rxjs 5.5 but converting it for your needs shouldn't be too hard. For me I feel the key was understanding what slice of state I was selecting to ensure I was checking for the appropriately loaded data.

Woot
  • 1,759
  • 1
  • 16
  • 23
0

Maybe try this quick hack

.take(1)
    .switchMap(() => this.store.select(selectState).pluck('loading').distingUntilChanged()
)

which only fire if it's switch from false to true, or vice versa use. Alternatively use intermediate state, beforeload->loading->loaded

Fan Cheung
  • 10,745
  • 3
  • 17
  • 39
0

I was struggling to see why the code behaved as it did, so in case anyone else is wondering:

The @ngrx/store State class creates an Observable actionsOnQueue using the rxjs observeOn operator and the queueScheduler. It is these queued actions which are fed to the reducer to produce the new state. The new state then emits and triggers any selector.

The first dispatch executes in the context of the Resolver and runs synchronously. The next line creates an Observable which selects from the store and immediately sees the effect of the Get action. Thus the filter ignores the initial state where loading=true and the pipeline waits for the loading to complete.

When the Success action is later dispatched and reduced (setting loading=false), the store will emit and the filter will pass. Then the second dispatch will execute. The queueScheduler means this dispatch will not execute synchronously, as we are executing 'inside' the dispatch of the Success action.

The switchMap will then subscribe again to the store before the second Get action has been reduced into the state and see the stale state with loading=false.