Sander Philipse

July 10, 2021

The case against NgRx facades

Facades hide the features that make NgRx useful, while retaining what can easily be managed without NgRx. While facades are supposed to lead to more loosely coupled components, in practice they tend to lead to components that are more tightly coupled with APIs. The result is a more complicated and bloated application architecture that doesn't take full advantage of NgRx's best features.

NgRx is probably the most popular state management solution for Angular, but it's not the easiest to learn. That's why many developers hide the library's direct implementation behind facades. I've seen several great explanations for why facades are useful, but very few articles clearly explain the design pattern's downsides.

What is a facade?

First, a small bit of history. A facade is essentially any abstraction that hides implementation details from the developer. The term comes from the famous Gang of Four book Design Patterns: Elements of Reusable Object-Oriented Software. Almost every software developer will have used these patterns knowingly or unknowingly in their career, as most software libraries are facades.

When applied to NgRx, facades are meant to hide all of NgRx's implementation details. Instead of calling the store directly, components subscribe to observables exposed by an injectable facade and trigger functions on that same facade. The idea, as initially articulated by Thomas Burleson, is that your facades expose a clean data API, and you start thinking about your components purely as a view layer that uses those facades for data input.

This is great! I love creating a clear view that's separate from your data, clean API definitions, and the command-query separation that these facades maintain from NgRx. However, if you want just those things, why do you need all the other NgRx bells and whistles? You could simply do something like this:

import { HttpClient } from "@angular/common/http"

@Injectable()
export class DataFacade {
  data$ = new ReplaySubject<DataEntity>()

  constructor(private readonly http: HttpClient) {}

  getData(): void {
    this.http.get<DataEntity>(`url`).subscribe(result => data$.next(result))
  }
}

This has everything you want: a clearly defined observable that provides data to subscribers and a separate function that calls your API. Effects can be constructed by subscribing to data$ or other observables and reacting to changes. This may even be cleaner than using NgRx, especially if your application is relatively simple. Okay, you will miss out on selector memoization, action traceability, and the NgRx DevTools, but that's a fair trade-off for the increased simplicity and reduced external dependencies.

Wait, so you're saying we shouldn't use NgRx?

Well, no, I think NgRx is great. But if you're abstracting away all of its functionality behind facades, there's a good chance that you're better off without it. NgRx is a power tool for applications with complex, shared states, and it should be used for those purposes. If it's just an API abstraction layer, you're missing out on functionality and introducing unnecessary complexity into your codebase that will make it harder to maintain.

Facades also have some other issues. They promote action reuse, which is not ideal if you want to trace where each action came from. Brandon Roberts [has a good talk](Brandon Roberts on facades that explains that downside and a few others, while providing potential solutions that will still make your facades work. Sam Julien has a very similar talk as well. Having worked with facades in a few different codebases, though, I think they're understating the issues they present and the problems their solutions still retain.

The main reason to use a facade is to remove the NgRx store from your components. Without facades, your components will need to have access to the store and they'll need to call selectors and dispatchers on that store. But...why is that bad? To answer that question, let's go back to Thomas Burleson's original post introducing NgRx facades.

Reasoning through an example

Burleson used these examples to show why facades are superior to directly injecting the store. Take a look at both and see which seems better to you.

Without facades

import { Store } from "@ngrx/store"
import { carQuery } from "../+state/car.selectors"
import { loadCar, selectCar } from "../+state/car.actions"

@Component({
  template: `
    <ul>
      <li
        *ngFor="let car of allCars$ | async"
        (click)="selectCar(car.id)"
        [ngClass]="{ bold: (selectedCar$ | async).id === car.id }"
      >
        {{ car.name }}
      </li>
    </ul>
  `,
})
export class CarsComponent {
  allCars$ = this.store.select(carQuery.getAllCars)
  selectedCar$ = this.store.select(carQuery.getSelectedCar)

  constructor(private store: Store) {
    this.store.dispatch(loadCars())
  }

  selectCar(carId: string) {
    this.store.dispatch(selectCar(carId))
  }
}

With facades

import { CarsFacade } from "../+state"

@Component({
  template: `
    <ul>
      <li
        *ngFor="let car of allCars$ | async"
        (click)="carsFacade.selectCar(car.id)"
        [ngClass]="{ bold: (selectedCar$ | async).id === car.id }"
      >
        {{ car.name }}
      </li>
    </ul>
  `,
})
export class CarsComponent {
  allCars$ = this.carsFacade.allCars$
  selectedCar$ = this.carsFacade.selectedCar$

  constructor(public carsFacade: CarsFacade) {
    this.carsFacade.loadAllCars()
  }
}

Sure, using a facade eliminates a few imports and is slightly shorter, but we're not playing a game of shortest-component. The complexity is still present in your codebase, it's just been shifted out of view to a facade service. The extra imports in the first example won't generally have any impact on the efficiency or size of your compiled code. In fact, a facade actually bloats your production code because Angular and Typescript will fully compile those facades, rather than inlining their functions.

Personally, I think the second example is just as clear as the first, which is probably the most important criterion. Sure, technically speaking the component doesn't know about the store, selectors, or actions in the first example and it does in the second. But the first component needs to know about the facade, and the exact functions it provides. That's not a problem, but it's not clearly an improvement either.

Moreover, if we start adding more data to this component that doesn't belong to the cars domain, we need to start injecting additional facades. Suppose you want to start providing the user with some extra information based on their personal history (if they are logged in) and some IP-derived information. We might expand the first component to look a little something like this:

import { CarsFacade, AuthFacade, InfoFacade } from "../+state"

@Component({
  template: `
    <ul>
      <li
        *ngFor="let car of allCars$ | async"
        (click)="carsFacade.selectCar(car.id)"
        [ngClass]="{ bold: (selectedCar$ | async).id === car.id }"
      >
        {{ car.name }}
      </li>
    </ul>
    <p *ngIf="loggedIn$ | async">{{ loggedInExtraInfo$ | async }}</p>
    <p>{{ extraInfoIP$ | async }}</p>
  `,
})
export class CarsComponent {
  allCars$ = this.carsFacade.allCars$
  selectedCar$ = this.carsFacade.selectedCar$
  loggedIn$ = this.authFacade.loggedIn$
  extraInfoLoggedIn$ = this.infoFacade.extraInfoLoggedIn$
  extraInfoIP$ = this.infoFacade.extraInfoIP$

  constructor(
    public carsFacade: CarsFacade,
    public authFacade: Authfacade,
    public infoFacade: InfoFacade
  ) {
    this.carsFacade.loadAllCars()
    this.infoFacade.getExtraInfoIP()
    this.infoFacade.getExtraInfoLoggedIn()
  }
}

This is starting to look a little more complicated. More annoyingly, that component API is getting a lot less clean. We suddenly have five different observables and three different functions that need to be maintained. It's getting a little harder to see what is happening at a glance, and our component needs to maintain a lot of different subscriptions.

Let's make this component a little more user-friendly by adding some API status feedback with a spinner and an error message. We probably don't need to do that for the extra information blocks, as the user should be fine if those blocks just don't show up or fade in when they're loaded.

import { CarsFacade, AuthFacade, InfoFacade } from "../+state"

@Component({
  template: `
  <ng-container [ngSwitch]="allCarsStatus$ | async">
    <ul *ngSwitchCase="'SUCCESS'">
      <li *ngFor="let car of (allCars$ | async)"
          (click)="carsFacade.selectCar(car.id)"
          [ngClass]="{'bold': (selectedCar$ | async).id === car.id }"
          >
        {{car.name}}
      </li>
    </ul>
    <spinner *ngSwitchCase="'LOADING'">
    <p *ngIf="'ERROR'">Oops, we couldn't load your data.</p>
  <ng-container>
  <p *ngIf="loggedIn$ | async">{{ loggedInExtraInfo$ | async }}</p>
  <p *ngIf="extraInfoIP$ | async as extraInfo>{{ extraInfo }}</p>
`,
})
export class CarsComponent {
  allCars$ = this.carsFacade.allCars$
  allCarsStatus$ = this.carsFacade.allCarsStatus$
  selectedCar$ = this.carsFacade.selectedCar$
  loggedIn$ = this.authFacade.loggedIn$
  extraInfoLoggedIn$ = this.infoFacade.extraInfoLoggedIn$
  extraInfoIP$ = this.infoFacade.extraInfoIP$

  constructor(
    public carsFacade: CarsFacade,
    public authFacade: Authfacade,
    public infoFacade: InfoFacade
  ) {
    this.carsFacade.loadAllCars()
    this.infoFacade.getExtraInfoIP()
    this.infoFacade.getExtraInfoLoggedIn()
  }
}

Now this is really starting to look messy. Six observables and a switch in the template. All those separate async pipes aren't great for performance either. This can get out of hand pretty quickly as we keep adding functionality.

But, you could argue that you'd have the same issue in NgRx, right? Instead of injecting these facades you'd be importing more selectors while essentially doing the same thing. In some cases, that's certainly true. But there is another way!

Viewmodels and selectors

The above example, and most facade examples, effectively implement a model-view-controller pattern. The data exposed by the facade is the model, the facade's functions are (part of) the controller, and the component is the view. It's a good pattern, with one flaw in most NgRx facade implementations: the view's data input is driven largely by the data rather than by the view's own requirements. In this approach, the view needs to know about all of those different observables, facades, and functions that are largely irrelevant to its functioning -- it just wants some data and to know what to display when.

One alternative that solves this problem is the model-view-viewmodel pattern, where the component is provided with a model that is much closer to its UI implementation. Rather than exposing all of the above observables and letting the component transform that input into a view, we'd start with what we want the component to do, define a clear interface from the component's point of view, and then feed it that data. This is very hard to do with facades, which tend to be bound to APIs and domains and provide very data-focused models. But in NgRx, thanks to the magic of selectors, it's a breeze:

import { Store } from "@ngrx/store"
import { selectCarsViewModel, CarsViewModel } from "../+state/car.selectors"
import { selectCar } from "../+state/car.actions"

@Component({
  template: `
  <ng-container *ngIf="(viewModel$ | async) as viewModel">
    <ul *ngIf="viewModel.showData">
      <li *ngFor="let car of viewModel.cars"
          (click)="selectCar(car.id)"
          [ngClass]="{'bold': viewModel.selectedCarId === car.id }"
          >
        {{car.name}}
      </li>
    </ul>
    <spinner *ngIf="viewModel.showSpinner">
    <p *ngIf="viewModel.showError">Oops, we couldn't load our data.</p>
    <p *ngIf="viewModel.showUserInfo">{{ viewModel.userInfo }}</p>
    <p *ngIf="viewModel.showLocationInfo>{{ viewModel.locationInfo }}</p>
  <ng-container>
`,
})
export class CarsComponent {
  viewModel$: CarsViewModel = this.store.select(selectCarsViewModel)

  constructor(public store: Store) {
    this.store.dispatch(initCarsComponent)
  }

  selectCar(id: number) {
    this.store.dispatch(selectCar(id))
  }
}

We have one observable that tells our component what it needs to show, we have one outgoing action to initiate the data we need and one action to select a car. We're also only importing things from three other locations (and we could reduce that with a different application architecture). This is much cleaner and more clearly defined than the version with facades, where we were forced to manage many more separate data sources. Using a single init action for the component, which will need to trigger the right fetch actions through an effect, also abstracts a lot of logic away from the component. And the magic that makes all of this work is NgRx's selector mechanism:

import * as carSelectors from '../+state/car.selectors';
import * as authSelectors from '../+state/auth.selectors';
import * as infoSelectors from '../+state/info.selectors';

export const selectCarsViewModel = createSelector(
  carSelectors.selectCars,
  carSelectors.selectCarsStatus,
  carSelectors.selectSelectedCar,
  authSelectors.selectLoggedIn,
  infoSelectors.selectExtraInfoLoggedin,
  infoSelectors.selectExtraInfoIP,
  (cars, status, selectedCar, loggedIn, userInfo, locationInfo) => ({
    cars,
    userInfo,
    locationInfo,
    selectedCarId: selectedCar.id
    showData: status === 'SUCCESS',
    showSpinner: status === 'LOADING',
    showError: status === 'ERROR',
    showUserInfo: !!userInfo,
    showLocationInfo: !!locationInfo,
  })
);

This selector is fairly complicated, but it's still very efficient (because of NgRx's selector memoization), easily testable (it's a pure function!), and it decouples the component's view from the data shape in the store. If any of the underlying services or APIs change, all we have to do is update this one selector so it still provides the same data shape. And if we want to change when a spinner shows up, or when the location-based info is shown, we only need to update the logic in this specific spot.

This kind of viewmodel approach is very hard to implement with facades, because they tend to be domain-based: you'll have one facade for the cars domain, a second one for the authorization domain, a third one for the user-information domain and on and on. Those are all tightly coupled with the shape of the domain and data of your organization, and developers will start building their components and logic in those terms. The result is a tighter coupling and less separation between model (data) and view (component).

We could implement component-specific facades that import other facades to construct this kind of data-model. It would work, and I've seen this happen on occasion, but because facades are usually implemented by exposing data observables, you lose the ability to combine these different selectors while still retaining their memoization. Instead you'd have to use a combineLatest() operator to do this, which is significantly less efficient and harder to maintain. You could solve this by calling all of these selectors directly in a new component-based facade, but then what was the point of all those other facades in the first place? And if you implement component-based facades everywhere, is having a component facade that effectively mirrors store.select and store.dispatch really better than just exposing the store to your components?

Conclusion

Facades seem to work very well for many people. Ultimately, though, I personally haven't seen great examples of facades solving a crucial problem. I have seen them promote some bad habits, while hiding implementation details that developers should be aware of and can take advantage of. Facades may make it easier for new developers to get used to NgRx, but this approach will also hinder them in the long run.

The one thing that these facades are good for is making it easier to replace NgRx. But how likely are you to really strip out NgRx and replace it with a new state management system? And how easy would that be, even with all of these facades?

If you find yourself reaching for a facade whenever you implement NgRx, there's a chance you're missing out on some great NgRx features. And if you think you don't need those features, then maybe you don't need NgRx to begin with. Perhaps you can try implementing its basic concepts and command-query separation without using NgRx instead.

See more: