RxJS in Angular: Part II

In this article, we are going to further explore the notion of reactive programming, but also apply it to more complex situations, and find beautiful solutions even for the ugliest problems.

RxJS in Angular: Part II

In my previous article I explored the ways in which RxJS makes frontend development in Angular so much more fun and easy than it is; we discussed the topics of reducing the component state, deriving view state from actual state using RxJS operators instead of imperative commands, and thinking in a reactive way in general. In this article, we are going to further explore the notion of reactive programming, but also apply it to more complex situations, and find beautiful solutions even for the ugliest problems.

From imperative to reactive

Most of us are already aware that RxJS uses a very declarative approach to programming, while Angular itself, being the object oriented framework it is, sometimes (maybe involuntarily) encourages imperative patterns. We can try to get rid of the imperative code by using the approach mentioned in part I, which is "always think from the perspective of how the state affects the UI and how to derive the state to be displayed from the state at hand". Let me repeat the points from part I; to solve any problem at hand we need to:

  1.  Understand what part of the state affects the UI and make it an Observable stream;
  2.  Use RxJS operators to perform calculations and derive the final state to be used in UI
  3.  Use the async pipe to put the result of our computations in the template

Now let's try to do it on an example of a problem which most of the less experienced Angular developers would probably solve by writing imperative code. Imagine we have a component that displays the current time in a specific format (let's say am/pm vs 24h), and also a dropdown which allows the user to select the preferred format. To make things easier, let's imagine we also have access to a formatTime  function, which receives the time and the format and returns the pretty, ready for UI version of it. Now let's start with the ugly, imperative solution:

@Component({
  selector: 'app-example',
  template: `
  <select [(ngModel)]="format">
    <option value="24h">24 Hour</option>
    <option value="ampm">AM PM</option>
  </select>

  {{ formattedTime }}
  `,
})
export class ExampleComponent implements OnInit {
  format: 'ampm' | '24h' = '24h';
  formattedTime: string;
  interval: number;

  ngOnInit() {
    this.interval = setInterval(() => {
      this.formattedTime = formatTime(new Date(), this.format);
    }, 900)
  }
  
  ngOnDestroy() {
    clearInterval(this.interval);
  }
}

Okay, so now we have implemented ngOnInit to start an interval that will recalculate time every 900ms, then display it in the view, and when the component gets destroyed, it will be cleared using clearInterval.

So far so good, but can we please make it better? Sure thing, let's use ReactiveForms and Observables to make this component fully declarative.

@Component({
  selector: 'app-example',
  template: `
  <select [formControl]="format">
    <option value="24h">24 Hour</option>
    <option value="ampm">AM PM</option>
  </select>

  {{ formattedTime$ | async }}
  `,
})
export class ExampleComponent {
  format = new FormControl('24h');
  formattedTime$ = combineLatest(
    interval(900).pipe(map(() => new Date())),
    this.format.valueChanges.pipe(startWith('24h')),
  ).pipe(
    map(([dateTime, format]) => formatTime(dateTime, format)),
  );
}

What we did here was think in terms of state: we need to display the time, but what does the time depend on? It depends on the current time (duh) and the format that the user  selected. So we just combine the current datetime (using interval) and the format valueChanges together, and then map them to what we will actually display to the end user. What benefits did we get?

  1.  Well, our component is 6 lines shorter. Less code, less bugs.
  2.  We also don't have any methods. Methods, if longer than one line, can get very imperative, or even outright complicated. Removing as many methods as possible is a sure way to reduce complexity and the amount of bugs.
  3.  Our component is very declarative; the class only has properties which describe themselves entirely.
  4.  As a bonus, we don't have to implement ngOnDestroy: the async pipe unsubscribes itself.

Okay, so what about cases with seemingly too custom or sophisticated logic?

Utilizing the power of RxJS

Sometimes people doing RxJS say "everything is a stream". You may feel like this is an exaggeration, or just a fancy saying, but... Everything is a stream. Even singular values. Even no value at all. When we start thinking like that, frontend development becomes easy. Why? Because frontend is all about immediate response.  Something happened, well, we need to immediately react to it, update the view, update the state, and do that perpetually. This is why it is better to think of, say, a list of users not as just a list of users, but a stream of past, current and future lists of users, and act accordingly.

Imagine we have a simple form that is divided into two columns; some fields are in the right column, some are in the left column. This is pretty straightforward, but there is a catch: besides from these fields, there are also custom fields, which users themselves created and need to fill in; the list of custom fields is retrieved from the backend (A Promise of an Array of objects). This is a bit complex, but not really complicated, just load the list and display the inputs. But the thing is, we want to preserve the symmetry of our UI, and display half of the custom fields on the right side, and the other half on the left, say, odd fields on the left and even fields on the right. How do we do it? Let's again start from the imperative approach and then explore how the reactive approach makes this better.

@Component({
  selector: 'app-example',
  template: `
  <div class="left-column">
    <div>
      <label>First Name</label>
      <input />
    </div>
    <div>
      <label>Age</label>
      <input />
    </div>
    <div>
      <label>Occupation</label>
      <input />
    </div>

    <div *ngFor="let field of oddFields">
      <label>{{ field.label }}</label>
      <input />
    </div>
  </div>
  <div class="right-column">
    <div>
      <label>Last Name</label>
      <input />
    </div>
    <div>
      <label>Interests</label>
      <input />
    </div>
    <div>
      <label>About yourself</label>
      <input />
    </div>

    <div *ngFor="let field of evenFields">
      <label>{{ field.label }}</label>
      <input />
    </div>
  </div>
  `,
  styleUrls: [ './app.component.css' ]
})
export class ExampleComponent implements OnInit {
  oddFields: Field[] = [];
  evenFields: Field[] = [];
  constructor(
    private readonly customFieldsService: CustomFieldsService,
  ) {}

  ngOnInit() {
    this.customFieldsService.getCustomFields().then(fields => {
      this.oddFields = fields.filter((_, index) => index % 2 === 0);
      this.evenFields = fields.filter((_, index) => index % 2 === 0);
    })
  }

}

Well now of course this works, but at what cost?

  1.  We are using a Promise, which in the world of RxJS is already not the best approach;
  2.  We write more logic into ngOnInit. In my article about Angular bad practices I explore how putting too much custom logic inside ngOnInit is not exactly the best idea; if we can get rid of  ngOnInit altogether, that would be even better;
  3. Finally, our logic inside ngOnInit is very imperative, in commanding statements like "GET THIS DATA!! DIVIDE THIS DATA INTO TWO PARTS!!", rather than describing that we have data split into two parts.

And here is a purely reactive solution:

@Component({
  selector: 'app-example',
  template: `
  <div class="left-column">
    <div>
      <label>First Name</label>
      <input />
    </div>
    <div>
      <label>Age</label>
      <input />
    </div>
    <div>
      <label>Occupation</label>
      <input />
    </div>

    <div *ngFor="let field of (oddFields$ | async)">
      <label>{{ field.label }}</label>
      <input />
    </div>
  </div>
  <div class="right-column">
    <div>
      <label>Last Name</label>
      <input />
    </div>
    <div>
      <label>Interests</label>
      <input />
    </div>
    <div>
      <label>About yourself</label>
      <input />
    </div>

    <div *ngFor="let field of (evenFields$ | async)">
      <label>{{ field.label }}</label>
      <input />
    </div>
  </div>
  `,
})
export class ExampleComponent {
  customFields: [Observable<Field[]>, Observable<Field[]>] = partition(
    from(this.customFieldsService.getCustomFields()).pipe(switchAll()),
    (_, index) => index % 2 === 0,
  );
  oddFields$ = this.customFields[0].pipe(toArray());
  evenFields$ = this.customFields[1].pipe(toArray());

  constructor(
    private readonly customFieldsService: CustomFieldsService,
  ) {}
}

What we did here was the following:

  1. Describe that we have all the custom fields streamed separately using switchAll;
  2. Use partition to divide the streams into two parts: odd indexed (starting from 1, not 0) fields to the left, even indexed fields to the right;
  3. Use toArray to actually make these separate streams of fields two distinct arrays;
  4. Display it in the view

As we can see, yet again we got rid of a method, some imperative logic, and also improved the readability overall. If someone wants to find out what are those odd and even fields, they can just see it themselves in the same place where those properties are defined; no need to find and read another method which tediously describes what is done to the custom fields step by step.

Some small recommendations

While RxJS itself makes our Angular apps better, we have to be careful not to abuse it and actually make our code worse. Here are some short pieces of advice on how to achieve it:

  1. subscribe may very well be the worst method ever. If we are subscribing to a stream inside our component, it means we are allowing imperative code to leak inside our functional/reactive code. Other than cases when it is impossible to avoid, never use subscribe;
  2. Avoid tap at all costs. tap is literally used to perform a side effect, which is in itself a bad thing from a purely functional point of view; tap also often is a harbour for imperative code, and generally also disrupts the harmonious flow of RxJS operators;
  3. Write custom RxJS operators. If we find yourself repeating the same chain of operators in your application (e.g. filter with this condition, then map to this and then finally collect all using toArray), then we may consider moving that particular logic to a custom operator of ours; while it may sound complicated, we can simplify and think of custom RxJS operators as functions that receive an Observable, and return another, which is usually a modified version of the previous one, possibly built by combining RxJS operators; so we can use the operators we want in the right order and not type it every time.
  4. If you do write custom operators, do your best to ensure your custom operators are pure function: don't use tap, don't perform your own side effects.

In conclusion

When you begin writing Angular in a more reactive way, you may get a feeling that you are maybe overengineering. But when you find and fix your first bug in just under a minute, or when you come back to your code after six months and immediately understand what's going on, then you will realise how easier it is to maintain a codebase devoid of imperative logic.

I will continue exploring RxJS use cases in Angular in my future articles.