NgRx Best Practices Series: 4. Facade Pattern

This article covers the Facade pattern in NgRx. The reader must have a basic understanding of NgRx or a similar state management library.

The Facade pattern mainly decouples NgRx from the rest of our application. We can also see it as an API. The Facade masks the dispatching of actions as method calls and the selectors as properties of type Observable.

Next to the decoupling, the facade pattern offers even further advantages.


If you prefer a video over an article, there you go 😉


Theory

The Facade pattern, sometimes called Repository or API, is an architectural pattern.

We divide our application into different modules and organise them within two layers. The first divides our application into multiple scopes or domains (if you use DDD). Next to the domains, we have the application shell and shared modules.

Angular Architecture - Layer 1

Every domain holds multiple modules of different types. A typical structure for an NgRx-powered application is the division into feature, data, ui, and model type.

The feature module contains the container components, the ui contains the presentational components, and the data has the NgRx feature state.

Angular Architecture - Layer 2

Our modules need to support encapsulation and dependency rules. Encapsulation means that we can hide certain elements from the outside. With dependency rules, we can define which modules can access each other.

We must use additional tools since TypeScript does not support modularity out of the box. The author recommends Nx dependency rules or Sheriff. Both come as ESLint plugins.

We apply dependency rules on all two layers. In the first layer, we define that domains cannot talk to each other. If required otherwise, we will add exceptions on an individual basis.

Dependency rules on the second layer, i.e. the domain, follow the module type. Only elements of the feature module can access the data module. The presentational components, part of the ui module, get their data from the container components. The ui module does not have access to the data module.

The model type holds the TypeScript interfaces of the domain models. Since domain models are the ubiquitous language of the domain, every other module of the same domain has access to it.

For more information about architecture and NgRx, check out this article.

The Facade uses the encapsulation feature. We hide all NgRx elements within the data module. These are the reducer, the selectors and the actions. What stays exposed to the public is the actual Facade, and the provider function that creates the NgRx feature state.

In both Nx and Sheriff, modules define their exposed elements in the index.ts. In our case, it could have the following content:

import { provideState } from '@ngrx/store';
import { customersFeature } from '@app/customers/data/customers.reducer';
import { provideEffects } from '@ngrx/effects';
import { CustomersEffects } from '@app/customers/data/customers-effects.service';

export { CustomersFacade } from './customers-facade';

export const provideCustomers = [
  provideState(customersFeature),
  provideEffects(CustomersEffects),
];

Implementation

Let's say we have a simple CRUD feature state which manages customers. It has actions for adding, editing, listing, and removing customers. At the same time, it offers two selectors. One returns all customers, and the other finds a customer per id.

We have the following state:

export interface State {
  customers: Customer[];
}

The NgRx actions:

import { createActionGroup, emptyProps, props } from '@ngrx/store';
import { Customer } from '@app/customers/model';

export const customersActions = createActionGroup({
  source: 'Customers',
  events: {
    load: emptyProps(),
    loaded: props<{ customers: Customer[] }>(),
    add: props<{ customer: Customer }>(),
    added: props<{ customer: Customer }>(),
    update: props<{ customer: Customer }>(),
    updated: props<{ customer: Customer }>(),
    remove: props<{ id: number }>(),
    removed: emptyProps(),
  },
});

And finally, the two selectors, where NgRx provides the first one automatically via its createFeature function:

const selectAll = customersFeature.selectCustomers;

const selectById = (id: number) => createSelector(
  selectAll, 
  (state: Customer[]) =>
    state.find((p) => p.id === id)
);

export const fromCustomers = {selectAll, selectById};

Without the Facade, our container components would dispatch actions and select from the state via the Store service.

export class EditCustomerComponent implements OnInit {
  #store = inject(Store);
  protected customers$: Observable<Customer[]> | undefined;

  ngOnInit() {
    this.customers$ = this.#store
      .select(fromCustomers.selectAll());
  }

  submit(customer: Customer) {
    this.#store.dispatch(customersActions.update({ customer }));
  }
}

Now we implement the Facade. It provides methods for the actions we want to make publicly available.

@Injectable({ providedIn: 'root' })
export class CustomersFacade {
  #store = inject(Store);

  update(customer: Customer) {
    this.#store.dispatch(customersActions.update({ customer }));
  }
}

And for the selectors, we expose "read-only properties" in the form of a getter function.

@Injectable({ providedIn: 'root' })
export class CustomersFacade {
  #store = inject(Store);

  get customers$(): Observable<Customer[]> {
    return this.#store.select(fromCustomers.selectAll);
  }

  update(customer: Customer) {
    this.#store.dispatch(customersActions.update({ customer }));
  }
}

We should also limit the number of actions, we want to expose to our container components. An example is the updated action. That is an internal action because only our NgRx Effect dispatches it. CustomersFacade doesn't provide a method for it, so it is unavailable.

export class EditCustomerComponent implements OnInit {
  #facade = inject(CustomersFacade);

  protected customer$: Observable<Customer> | undefined;

  ngOnInit() {
    this.customer$ = this.#facade.customers$;
  }

  update(customer: Customer) {
    this.#facade.update(customer);
  }
}

We see that the component no longer knows that NgRx runs under the hood. From the component's perspective, the CustomersFacade provides an Observable of Customer[]. When it comes to the update, it is a simple method call.

The same is true for all other components or services that use NgRx. We have to refactor them, so that they only use the Facade.

Next, we'll cover why we want this decoupling and what further advantages we get.

Advantages

Decoupling

We don't want to have NgRx everywhere in our codebase. This allows us to switch to another state management library quite easily.

I know many Angular developers (whom I highly respect) who are unhappy with the Facade pattern.

A common argument says that replacing NgRx with another state management library is implausible if we already use it.

Another argument is that the Facade opens the door for anti-patterns. For example, we could have a method which dispatches the action and calls a selector at the same time.

I will discuss the reasons for replacing NgRx in the next chapter.

For the argument about misusing the Facade, it is really up to the discipline and internal code reviews of the development team.
We can overengineer every pattern, but that doesn't mean we shouldn't use it.

Changed Requirements

Requirements for our applications change. It is common that some of the frontend logic moves to the backend or diminishes. If everything we have to do is send a single HTTP request to the API, it is better to slim down our codebase and remove NgRx for that feature.

With a Facade, we could replace NgRx under the hood with a BehaviorSubject. Our components don't have to change at all. Everything is fine as long as the Facade returns the same Observables and the methods keep their signature.

The Facade also allows us to mix simple HTTP requests with NgRx. Let's say there is some data in our domain which doesn't need state management. The Facade could still provide it as a normal property of type Observable. Instead of calling a selector, it triggers an HTTP request.

Summarised, the Facade gives us the flexibility to change with dynamic requirements efficiently.

Prepared for more

Especially teams, who start to learn Angular and use NgRx immediately, can be overwhelmed or even intimated by NgRx.

If those teams decide to postpone the usage of NgRx, there is a high chance that they will miss the point where they have to integrate it. They'll end up with a self-written state management library. That's a liability. They must fix bugs, maintain and extend it. We don't want to be in this position.

The Facade helps here as well. It uses a BehaviorSubject internally first. When the time comes to change, just replace that BehaviorSubject with NgRx.

Again, this change only needs to happen inside the data module. The components and services are unaffected.

The Facade works here as some kind of insurance. It is definitely worth the investment.

Reversing Overengineering

The opposite can happen as well. Teams with little NgRx experience might overuse it. The Facade helps here in the same way as it did in the other extreme.

NgRx Component & Signal Store

In my opinion, we rarely switch to another library for state management once we go with NgRx.

Nevertheless, NgRx has meanwhile created its own competition.
According to the official download statistics, the NgRx Component Store is already in place 2. One can see the Component Store as a "lightweight Global Store".

Because of that, many developers are considering the switch to the Component Store. The third self-made contender is the Signal Store, built with Signals in mind.

Given the two alternatives in the NgRx ecosystem, moving away from the Global Store is no longer uncertain.

You know already what comes next ;).

The Facade is the universal remedy for those scenarios as well. When you switch to the Signal Store, your properties will return a Signal instead of an Observable. Given that the Global and Component already supports signal-based selectors, this change is relatively easy.

Built-in Logic

A Facade doesn't just have to hide NgRx. It could also add logic, which would be impossible with native NgRx.

In our current application, container components need to call the load method of the CustomersFacade. It dispatches an action to fetch the data from the backend.

If more components depend on loaded customers, every component would have to call load.

Therefore, implementing a "loading on-demand" feature in NgRx is impossible. A selector cannot dispatch an action. But our Facade can.

The Facade must track if it has already dispatched the load action once. Since the properties of our CustomersFacade are getters (function with get prefix), we could place that check directly into it:

export class CustomersFacade {
  #isLoaded = false;
  #store = inject(Store);

  get customers$(): Observable<Customer[]> {
    this.#assertLoaded();
    return this.#store.select(fromCustomers.selectAll);
  }

  byId(id: number): Observable<Customer | undefined> {
    this.#assertLoaded();

    return this.#store.select(fromCustomers.selectById(id));
  }

  #assertLoaded() {
    if (!this.#isLoaded) {
      this.#store.dispatch(customersActions.load());
      this.#isLoaded = true;
    }
  }
}

With that version, no component needs to call the load method anymore. Our Facade could even remove it.

We find another attractive option in extending the possibilities of selectors.

An NgRx's Store select method returns an Observable that synchronously emits the first value. It is not possible to add pipe operators to the selector (what createSelector returns) itself.

Still, we want to clone the state to have protection against mutable changes. Another requirement is that we want to filter out values of undefined.

Without the Facade, our components must deal with a potential undefined and ensure they don't apply mutable changes. The reason is obvious. They are the ones that run store.select() and therefore get the Observable.

Facade to help! It returns already an Observable and can therefore add pipe operators to it.

Let's say we want that byId doesn't return an Observable<Customer | undefined> but Observable<Customer>. We also want to clone the value because we use a template-driven form which does mutable changes.

Our improved version of the CustomersFacade would look like this:

@Injectable({ providedIn: 'root' })
export class CustomersFacade {
  #isLoaded = false;
  #store = inject(Store);

  byId(id: number): Observable<Customer> {
    this.#assertLoaded();

    return this.#store
      .select(fromCustomers.selectById(id))
      .pipe(filterDefined, deepClone);
  }
}

filterDefined and deepClone are custom operators.

Easier Component Tests

If we want to test a component that uses NgRx, we have quite handy testing utilities. For example, the provideMockStore provides a fully functional but stubbed NgRx feature state.

A test for a component which lists all customers might look like that:

it('should show customers', () => {
  const fixture = TestBed.configureTestingModule({
    imports: [CustomersContainerComponent],
    providers: [
      provideRouter([]),
      provideMockStore({
        initialState: {
          customers: {
            customers: [
              {
                firstname: 'Sabine',
                name: 'Miscovics',
                country: 'AT',
                birthday: '1993-05-09',
              },
            ],
          },
          currentPage: 1,
          pageCount: 1,
        },
      }),
    ],
  }).createComponent(CustomersContainerComponent);
  fixture.detectChanges();

  expect(
    document
      .querySelector('[data-testid=row-customer] p.name')
      ?.innerHTML.trim()
  ).toBe('Sabine Miscovics');
});

What's the issue with provideMockStore?

In our component test, we must know the feature state's structure. One of the ideas behind selectors is to hide that from our components. Now we have it right in our tests back again. We also need to know the feature's key. That's why we have two times the property customers.

In this type of test, we only want to test the component and don't deal with NgRx.

Again, the Facade can help. If we want to mock NgRx, we only need to mock a simple service. We can even make use of the common mocking libraries (testing-library, ng-mocks, [jest|jasmine]-auto-spies, ts-mockito) for further simplification:

it('should show customers', () => {
  const facadeMock: Partial<CustomersFacade> = {
    get customers$(): Observable<Customer[]> {
      return of([
        {
          id: 1,
          firstname: 'Sabine',
          name: 'Miscovics',
          country: 'AT',
          birthdate: '1993-05-09',
        },
      ]);
    },
  };
  const fixture = TestBed.configureTestingModule({
    imports: [CustomersContainerComponent],
    providers: [
      provideRouter([]),
      { provide: CustomersFacade, useValue: facadeMock },
    ],
  }).createComponent(CustomersContainerComponent);
  fixture.detectChanges();

  expect(
    document
      .querySelector('[data-testid=row-customer] p.name')
      ?.innerHTML.trim()
  ).toBe('Sabine Miscovic');
});

We use facadeMock as mock for the CustomersFacade. It only returns the Observable<Customer>, which is everything we need in this test.

We don't have to know about the feature state's structure, the feature key, or that we use NgRx.

Summary

The Facade is a pattern which is easy to implement but very powerful in its possibilities. We just have to replace a dispatched action with a method call, and the selectors become a property with an Observable to the outside.

Decoupling NgRx from the rest of our application is the main advantage. The Facade significantly reduces the amount of work when we want to replace NgRx.

We can also add logic or additional features to our Facade, which is not possible with plain NgRx.

Examples are adding pipe operators to our selectors or developing an on-demand loading mechanism for our entities.

Last, we also end up with easier tests. We don't need to learn and use the utility testing methods that NgRx offers to mock a feature state. We only have to mock the Facade, as with any other Service mock.

The Facade looks like an overengineered pattern at first sight. It is not. It is a pattern that offers a lot of advantages with minimal effort. You should give it a try!


You can find a Github repository containing all possible variations in Angular 16.

https://github.com/rainerhahnekamp/ngrx-facades

If you are interested in more content about NgRx, you might want to join one of our upcoming Professional NgRx workshops:

English Workshops:

https://www.angulararchitects.io/en/angular-workshops/professional-ngrx/

German Workshops:

https://www.angulararchitects.io/schulungen/professional-ngrx/


What is your opinion? Do you think the Facade can be a benefit to your application? Maybe you use the Facade already. Did you find you find further use cases for it?

Let me know in the comments!

Leave a Reply