Guarding for data with NgRx & RxJS

For the past couple of months, I have been toying around on a fun little project for myself, mostly just to learn and understand NgRx and keep up to speed on the latest Angular. I’ve come to embrace the fact that the reactive patterns that come from NGRX that were heavily influenced by Redux are super powerful and take out the complications of managing state.

After setting up all my actions, reducers and effects to manage the application state, I started to see an issue. During development, since I was using Angular Router for navigating from a collection of items to a single item and subsequently refreshing the single item page to see changes, my state would become out of alignment. My effects are set up in a way to rely on filtering out from a collection the single item the page was requesting. Since the component of that single page was only dispatching events to get that single item and not re-fetching the collection, I ended up with an error on a null object request.

Needless to say, I knew I needed a route guard of some sort to work with to make sure my application collection state was filled first.

A great reference book that goes deeper into this topic: Reactive Programming with RxJS 5: Untangle Your Asynchronous JavaScript Code

Setting up the route guard

I ran across an older post of Todd Motto’s Preloading ngrx/store with Route Guards which was doing exactly what I needed. The unfortunate part was that it was referencing syntax from older versions of NgRx and RxJS; yet the methodologies were the same. So taking this in and understanding what is actually happening, I decided to come up with my own version that used the latest versions I was already working with.

It is important to note that the below block of code has a lot of boilerplate and also references to classes that are not talked about, such as the action initialization of new GetItems(). For this post, I am going to assume that you are somewhat familiar with how an NgRx store and its parts work, including the streaming of RxJS observables.

1
//...imports
2
3
export class ItemsGuard implements CanActivate {
4
  constructor(
5
    private store: Store<IAppState>,
6
  ) { }
7
8
  canActivate(
9
    next: ActivatedRouteSnapshot,
10
    state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
11
    return this.getFromStoreOrApi().pipe(
12
      switchMap(() => of(true)),
13
      catchError(() => of(false))
14
    );
15
  }
16
17
  private getFromStoreOrApi(): Observable<IBankAccount[]> {
18
    return this.store.select(selectCollectionOfItems).pipe(
19
      tap((items: IItem[]) => {
20
        if (!items) {
21
          this.store.dispatch(new GetItems());
22
        }
23
      }),
24
      filter((items: IItem[]) => !!items),
25
      take(1),
26
    );
27
  }
28
}

The main factor that makes all of this work in the way it should is how RxJS streams work. In short the stream will not complete and unsubscribe until it ends up getting data from the filtering of the initial select method. When the stream completes from calling the take method, then canActivate will complete to allow through the guard accordingly.

Breaking the code down further:

  • The canActivate method of the class is what Angular calls when a guard is used to see if the route being requested can be shown based on the condition returned as an observable with a boolean type.
  • In the getFromStoreOrApi method:
    • line 18: I select the state of the collection from the store and pipe it through to start the observable. This was the major change from the Todd Motto post in that the NgRx store is now just plain object returned from select and not an observable, but can be chained into one with pipe or subscribe.
    • line 19-23: I tap (which do used to be the RxJS equivalent) into the streamed data to see if the data is available. By default, my initial state of all my stores are null so I can easily check against a falsy value. If the value is false, I call off to the store to dispatch and start the action of getting the collection.
    • line 24: This is what keeps the stream from completing. The filter method will not pass through until the items check returns true. This subsequently lets the application wait until it gets the data that is ultimately needed by the component page.
    • line 25: This is equally as important as it is what ends up completing the stream and unsubscribes itself for garbage cleanup.

Once the guard class is setup, adding it to your route file is just as typical.

1
//...imports
2
3
export const routes: Routes = [
4
  ...
5
  {
6
    path: 'items/:id',
7
    canActivate: [ItemsGuard],
8
    component: 'ItemComponent'
9
  }
10
  ...
11
]

With all of this in place, no longer do I need to worry about filling my application state from in a round-about way. At one point I toyed around with calling an effect action within another effect. Instead, I can easily rely on the use of RxJS streams and guarding against missing data. Best of all, this pattern can be extended to any other use cases with collection based navigation.

Filed under: Code