Brian Love
Angular + TypeScript Developer in Denver, CO

NgRx Antipatterns

Reading time ~5 minutes

Learn what not to do with RxJS, NgRx and Angular.

Dependent Actions

This has come up a lot for me. I need to wait until I have A before I can request B. You might say that the request for B is dependent on the response of A.

Here is the anti-pattern:

public courses: Observable<Array<Course>>;

ngOnInit() {
  this.store.select(getUser())
  .takeWhile(() => this.alive)
  .first()
  .subscribe(user => {
    this.store.dispatch(new LoadCoursesForUserAction({ user: user }));
    this.courses = this.store.select(getCourses);
  });
}

Let me explain what I am trying to accomplish:

  • First, I have a public property courses that is an observable of an array of Course objects.
  • I use takeWhile() to complete the subscription while the component is alive. I set the value of alive to false in the ngOnDestroy() lifecycle method.
  • I then use the first() method to complete the subscription after the first value is emitted. I am only interested in getting the single user that is authenticated.
  • Then, I subscribe to the observable using the subscribe() method. My observer is defined using a fat arrow function, with a single user argument.
  • Within the observer function I dispatch() a new action to load the courses for the user, aptly named LoadCoursesForUserAction, providing the user object.
  • Finally, I set the courses property to the the array of courses that is in the store using the getCourses() selector function.

It works.

I know we’ve all heard those words, and if we’re honest with ourselves it is likely that we have uttered those same words.

While, it works. There is a better, more concise way to working with dependent actions.

Let’s look at, what I think, is a better approach:

public courses: Observable<Array<Course>>;

ngOnInit() {
  this.courses = this.store.select(getUser())
  .do(user => this.store.dispatch(new LoadCoursesForUserAction({ user: user })))
  .switchMap(() => this.store.select(getCourses));
}

Let’s review the update code:

  • First, notice that I am setting the value of the courses property to the result of the switchMap() operator.
  • I use the do() operator to tranparently perform an action (or side effect), in this case, to dispatch() the LoadCoursesForUserAction action. The do() operator will receive the user that is emitted from the observable that is created using the select() method, specifying the getUser() selector function.
  • Finally, using the switchMap() operator, the inner observable (obtaining the user) is complete, returning the observable of the array of Course objects.

What are the benefits?

  1. We don’t need to deal with subscribing and unsubscribing.
  2. It’s more concise.
  3. We take advantage of provided RxJs operators.

Ok, what if we have multiple dependencies, such as:

  • C depends on B
  • B depends on A

Here is an example of the anti-pattern:

public administrator = false;

public group: Observable<Group>;

public users: Observable<Array<User>>;

ngOnInit() {
  const PARAMS_ID = "id";

  // get id route param
  this.activatedRoute.params
  .takeWhile(() => this.alive)
  .subscribe(params => {
    // get the id parameter
    const id = params[PARAMS_ID];

    // get the group
    this.store.dispatch(new LoadGroupAction({ id: id }
    this.group = this.store.select(getGroup);

    this.group
    .takeWhile(() => this.alive)
    .filter(group => !!group)
    .first()
    .subscribe(group => {
      // load users in group
      this.store.dispatch(new LoadUsersInGroupAction({ group: group }));
      this.users = this.store.select(getUsersInGroup);

      // set adminstrator
      this.store.select(getAuthenticatedUser)
      .takeWhile(() => this.alive)
      .first()
      .subscribe(user => {
        this.administrator = (group.security.administrators.indexOf(user._id.toString()) > -1);
      });
    });
  })
}

In this example, the dependencies are:

  • I need the user and the group to determine if the user is an administrator of a group.
  • I need the group to retrieve the users in the group.
  • I need the route param to get the group based on the id value.

Some of the problems here are:

  • There are a lot of nested subscriptions.
  • This is somewhat complicated and difficult to read.
  • I am using the deprecated params observable when I should be using the paramMap observable of ActivatedRoute.

Here is a better approach to this problem:

public administrator = false;

public group: Observable<Group>;

public users: Observable<Array&ltUser>>;

ngOnInit() {
  this.group = this.activatedRoute.paramMap
  .takeWhile(() => this.alive)
  .do(params => this.store.dispatch(new LoadGroupAction({ id: params.get(PARAMS_ID) })))
  .switchMap(() => this.store.select(getGroup));

  this.users = this.group
  .do(group => this.store.dispatch(new LoadUsersInGroupAction({ group: group })))
  .switchMap(() => this.store.select(getUsersInGroup));

  this.administrator = this.group
  .switchMap(group => this.store.select(getAuthenticatedUser)
    .switchMap(user => Observable.of(group.security.administrators.indexOf(user._id.toString()) > -1))
  );
}

There a few reasons why I like this better:

  1. It’s shorter and more concise.
  2. I can group together logical blocks of code making it more readable.
  3. There are no nested subscriptions with lots of indenting.
  4. I am using the recommended paramMap observable.

Avoid Duplicate Requests

The AsyncPipe is perhaps one of the coolest things in Angular. I can remember sitting at ng-conf in 2016 watching a presentation on Angular (then called Angular 2) and the thunderous applause after the presenter demoed the async pipe. For those of us that have been doing asynchronous coding in JavaScript for awhile, it was like the holy grail. It was pure magic.

But, as I grew in the ways of Angular I began to notice something odd. If I had defined an Observable in my component’s TypeScript file, and then had multiple child components with input bindings using the async pipe, I noticed there were multiple network requests for the same endpoint.

Let me explain in code. Here is my component:

public course: Observable&l;Course>;

ngOnInit() {
  const PARAMS_COURSE_ID = "courseId";

  // set course
  this.course = this.activatedRoute.paramMap
    .takeWhile(() => this.alive)
    .do(params => this.store.dispatch(new LoadCourseAction({ id: params.get(PARAMS_COURSE_ID) })))
    .switchMap(() => this.store.select(getCourse))
}

And here is my HTML template:

<ama-module-menu>
  [course]="course | async"
></ama-module-menu>
<ama-module-content>
  [course]="course | async
></ama-module-content>

A few things to note:

  • First, in my component’s ngOnInit() lifecycle hook I am setting the value of the public course property to an observable. It happens to be that the value is coming from an NgRx store using a selector, getCourse(), using the switchMap() operator.
  • In my template I have an <ama-module-menu> element that has an input binding for course, which uses the async pipe. (P.S. just for fun, “ama” is an acronym for ‘a million-dollar app’).
  • In my template I also have an <ama-module-content> element that also has an input binding for course, which also uses the async pipe.

The problem: a quick review of the network traffic shows that the async pipe will create multiple subscriptions, resulting in multiple HTTP OPTIONS requests for the same resource. Interestingly, it seems that the HttpClient module in Angular will cancel the multiple GET requests, and will on issue a single GET request.

Multiple OPTIONS request for the same endpoint

The solution, using the share() operator:

share(): Share source among multiple subscribers.

The share() operator is very easy to use, and allows us to avoid the duplicate HTTP requests when using multiple async pipe bindings in our templates.

We simply need to invoke the share() operator when setting up our observable:

  const PARAMS_COURSE_ID = "courseId";

  // set course
  this.course = this.activatedRoute.paramMap
    .takeWhile(() => this.alive)
    .do(params => this.store.dispatch(new LoadCourseAction({ id: params.get(PARAMS_COURSE_ID) })))
    .switchMap(() => this.store.select(getCourse))
    .share()
}

Note, there is only one difference between our component code before and this code above: we add on the invokation of the share() operator.

The results in a single HTTP GET request for our multiple async pipe subscriptions in our view template.

Single OPTIONS request for the same endpoint

Thank you RxJs!

More?

These are just two antipatterns that I have observed (haha). Please share any other antipatterns that you have learned from!

Brian Love

Hi, I'm Brian. I am interested in TypeScript, Angular and Node.js. I'm married to my best friend Bonnie, I live in Denver and I ski (a lot).