NgRx Component Store meets Facade Pattern

Abstracting State Management with the Facade Pattern

ngrx-component-store-meets-facade-pattern-cover
portrait-image

On the one hand, the Component Store is a really fancy library for simple state management. But on the other hand, tightly coupling state management into an application is always a bad idea. The reason for that being, that the need for state management is changing during the lifetime of an application. In the beginning it is often unnecessary to invest too much time into a fully fledged state management library when a service-based state management would be enough. But suddenly, after a few months working with stateful services, the need for a fully fledged state management library arises. But what now? Just throw everything out of the window?

Facade Pattern

In order to remove the tight coupling between your application and state management, whether it is an NgRx store or stateful services, the Facade Pattern can be applied. It basically hides away dependencies to data services and exposes one service such that changes in the data services do not directly affect the components that depend on it because it uses the facade instead. Hence the only place the change is affecting a refactoring is the facade. Therefore, it is a general best practice and is also often found in other use cases than state management. But in the Angular world it is especially useful for state management. Just imagine that you would write a big application and did not start with an NgRx store, but recently the need for it did arise and now you have to refactor your current service-based state management to use the NgRx store. Usually, this would be overwhelming because you would have to change all the components that used the services before and replace it with some code for NgRx. Especially in that case, a facade is a tremendous improvement, because the change from service-based to NgRx would only affect the facade.

result
Facade Pattern

Therefore, facades are the key to state management in an agile project. In an agile project, many many requirements are not known right ahead in the beginning of a project, hence the need for an NgRx store could spontaneously arise at every time. In order to not over-engineer the application and use NgRx directly, even if it may not be needed, a facade can hide the state management and do the damage control in case the state management technology would be replaced.

State Model vs View Model

Working with immutables is a general best practice and is used heavily in NgRx because it is heavily inspired by functional programming. This means, that a mutation on an object is not allowed. So, instead of updating a property of an object directly, the entirely new object must be created and the old values will be copied into it. This is pretty straight forward for primitive typed properties, but copying deeply nested objects is difficult and a hit on performance. If done incorrectly, such that a deep clone is done insufficiently, the outcome could be terrifying because mutations on such nested objects might happen outside of the store and therefore violate the immutability in a non-obvious way which is really hard to troubleshoot. Hence, it is important to manage such deeply nested objects carefully. And the best way to do so is to not have deeply nested objects at all.

The solution is to normalize such objects, like it is done in relational databases. This means, that the nested objects would be stored separately and have a primary key (unique id) and the original object will point to this formerly-nested object by referencing its primary key. This normalized version used for storing the data is called State Model, whereas the nested object, which would be used inside the components is called View Model.

result
View Model [Left], State Model [Right]

In consequence, we implement both entity models, but use the state model for storing the data in an immutable store and then transform it into a view model when requested by a component. You can either use an open-source library for normalization like normalizr or aggregate the data on your own. Either way, putting in the work of having both models pays off whenever you are dealing with nested objects in an immutable store.

result
Normalization

Component Store

The NgRx Component Store is heavily inspired by its bigger brother, the NgRx store. Nevertheless, the difference could not be bigger. Whereas the original store is meant to be used globally across the entire application, the component store is meant to handle state management for components only locally, such that the state is non-persistent. Although, this is the intented use, it is also not uncommon to see the component store as a replacement for the original store if the service that extends the ComponentStore class, is injected in root such that the state is not lost when a component is destroyed.

If you make your facades extend the component store you get access to common state management functions such as effect, select, updater, setState, patchState, which will help you to create a state-ful facade that handles state in an industry-standardized way. If you really were to switch to the NgRx store later on, the contract of the facade for the outside use would not change at all, because you would already make use of these functions that are very similar to the fully-fledged Redux pattern.

Let's see what such facade super-charged by the component store looks like.

I. State

First of all, we have to create a service that extends the ComponentStore class, to which we must pass an interface defining the state object. This is just an interface that includes all the data that should be stored reactively. In this case, it is an array of users and a selected user. Note, that you will also have to pass an instance of the interface to the super constructor defining the initial value. It is always better to have some default value like an empty array instead of an undefined value that must be handled in the template somehow.

interface State {
  selectedUser: number;
  users: UserStateModel[];
}

@Injectable({
  providedIn: 'root',
})
export class FacadeService extends ComponentStore<State> {
  constructor(
    [...]
    ) {
    super({
      selectedUser: -1,
      users: []
    });
  }
}

II. Selector

A Selector is the NgRx variant of reading data from a store. We can use the select method and retrieve an observable of the state. Since it is an observable, we can do any pipe operations we want to, such as here in this example, where I wrote custom RxJS operators that map the state model to a view model.

export class FacadeService extends ComponentStore<State> {

  [...]

  readonly selectedUser$ = this.select(({ users, selectedUser }) =>
    users.find((u) => u.id === selectedUser)
  ).pipe(
    filter(user => !!user),
    switchMap((user) =>
      user ? of(user).pipe(this.mapOneToViewModel()) : of(user)
    ),
  );

  readonly users$ = this.select(({ users }) => users).pipe(
    this.mapArrayToViewModel()
  );

  [...]

}

III. Updater

An Updater is sort of comparable to a reducer, for those who are familiar with the Redux pattern, and therefore acts as method that updates the state by replacing the entire state object with a new one. Why? Remember because of the immutability.

Like the selector, the updater is also just a method, but this time with a callback function that is executed when the updater is called. It is important to notice that the updater always has the state as a parameter, because it always needs that for replacing the entire state. The other parameters are totally optional and up to you. Also notice, that it returns the new state object.

export class FacadeService extends ComponentStore<State> {

  [...]

  readonly selectUser = this.updater((state: State, user: User) => ({
    ...state,
    selectedUser: user.id,
  }));

  [...]

}

By the way, there are other methods for updating the state, like setState, or patchState. Hence you could alternatively create a regular method and call one those two methods in there instead of the updater.

IV. Effect

When it comes to asynchronous updating, we can not rely on the updater, because it is a synchronous function. Therefore, we need an effect, which boiled down is more or less an asynchronous updater. It usually returns an observable which is piped, such that a higher order mapping, like a switchMap can be executed. Inside the switchMap a data service is called and returns some data asynchronously. Afterwards it is important to update the state with the tapResponse operator, which performs an update on the state using either the setState, or patchState method. But why not just a regular tap operator? Well, that is because the tapResponse operator has built-in error handling and you can imagine that if an error would occur inside the switchMap, the entire effect would be completed and therefore broken.

export class FacadeService extends ComponentStore<State> {

  [...]

  readonly loadUsers = this.effect<void>(
    pipe(
      switchMap(() =>
        this.userService
          .getAll()
          .pipe(
            tapResponse((users) => this.patchState({ users }), console.error)
          )
      )
    )
  );

  [...]

}

By the way, this code also includes another nice goodie function that the component store provides. patchState can be used to update the state object without having to replace the entire object by hand. It handles the replacing underneath and accepts a partial state that will overwrite the current state.

Full Example

In the example below, you can see a facade that injects three data services to retrieve users and aggregates their favorite pizza and song. It makes use of the segregation between state model and view model and implements custom RxJS operators to map from state model to view model. Note, that you can find the entire application in the GitHub repository attached down below.

import { Injectable } from '@angular/core';
import { ComponentStore, tapResponse } from '@ngrx/component-store';
import {
  combineLatest,
  combineLatestAll,
  filter,
  map,
  mergeMap,
  Observable,
  of,
  pipe,
  switchMap,
  throwError,
} from 'rxjs';
import { User } from '../entities/view-models/user.model';
import { User as UserStateModel } from '../entities/state-models/user.model';
import { PizzaService } from '../infrastructure/pizza.service';
import { SongService } from '../infrastructure/song.service';
import { UserService } from '../infrastructure/user.service';

interface State {
  selectedUser: number;
  users: UserStateModel[];
}

@Injectable({
  providedIn: 'root',
})
export class FacadeService extends ComponentStore<State> {
  readonly selectedUser$ = this.select(({ users, selectedUser }) =>
    users.find((u) => u.id === selectedUser)
  ).pipe(
    filter(user => !!user),
    switchMap((user) =>
      user ? of(user).pipe(this.mapOneToViewModel()) : of(user)
    ),
  );

  readonly users$ = this.select(({ users }) => users).pipe(
    this.mapArrayToViewModel()
  );

  readonly selectUser = this.updater((state: State, user: User) => ({
    ...state,
    selectedUser: user.id,
  }));

  readonly loadUsers = this.effect<void>(
    pipe(
      switchMap(() =>
        this.userService
          .getAll()
          .pipe(
            tapResponse((users) => this.patchState({ users }), console.error)
          )
      )
    )
  );

  constructor(
    private readonly userService: UserService,
    private readonly pizzaService: PizzaService,
    private readonly songService: SongService
  ) {
    super({
      selectedUser: -1,
      users: [],
    });
  }

  private mapArrayToViewModel(): (
    source$: Observable<UserStateModel[]>
  ) => Observable<User[]> {
    return (source$) =>
      source$.pipe(
        switchMap((users) =>
          users && users.length > 0
            ? of(users).pipe(
                mergeMap((users) => users.map((u) => this.getUser(u))),
                combineLatestAll()
              )
            : of([])
        )
      );
  }

  private mapOneToViewModel(): (
    source$: Observable<UserStateModel>
  ) => Observable<User> {
    return (source$) => source$.pipe(switchMap((user) => this.getUser(user)));
  }

  private getUser(user: UserStateModel | null): Observable<User> {
    if (!user) return throwError(() => new Error('user not found'));

    return combineLatest({
      pizza: this.pizzaService.get(user.favourites.pizzaId),
      song: this.songService.get(user.favourites.songId),
    }).pipe(
      map(
        ({ pizza, song }) =>
          ({
            id: user.id,
            email: user.email,
            favourites: {
              pizza,
              song,
            },
          } as User)
      )
    );
  }
}

facade.service.ts

Conclusion

In conclusion, the main take-away form this article is that the facade pattern is essential to manage state in an agile project. If you are starting a new project and are not settled yet on a state management technology to begin with, I would highly recommend starting with the NgRx component store since it brings some very useful functionalities out-of-the-box and gives you clear instructions on how you should handle state.

GitHub Repository

Comments