Picture of Brian Love wearing black against a dark wall in Portland, OR.

Brian Love

RxJS Antipatterns

Learn what not to do with RxJS in your Angular applications.

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:

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:

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:

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:

Some of the problems here are:

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:

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!

Using *ngIf

Updated: 2017-11-26

As Kimberly pointed out in the comments below, you can also use *ngIf result binding to avoid duplicate requests:


  [course]="c"
>

  [course]="c
>
```

This avoids the need to use the `.share()` operator for multicasting with RxJs.

Thank you Kimberly for sharing this solution!

## More?

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