NgRx Facade Pattern

NgRx 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 decouples NgRx from the rest of our application. It does that by masking NgRx-actions as method calls and the selectors as properties of type Observable.

In case you prefer a video: https://youtu.be/K4dpVXuhm14

Theory

The Facade pattern is an architectural pattern.

We divide our application into different modules and organize them within two layers. The first divides our application into multiple domains. Next to the domains, we have the application shell and shared modules.

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 module has the NgRx feature state.

Our modules need to support encapsulation and dependency rules. Encapsulation means that we can hide certain elements from the outside.

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

The Facade makes use of encapsulation. 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.

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:

export const customersActions = createActionGroup({
source: ‘Customers’,
events: {
load: emptyProps(),
// further CRUD-based actions
},
});

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. The same is true for parameterized selectors. It exposes the unparameterized selectors as “read-only” properties:

@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 }));
}
}

From the component’s perspective, the usage looks like this:

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. 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 (who 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.

For the argument about misusing the Facade, it is really up to the discipline and internal code reviews of the development team.

We can over-engineer every pattern, but that doesn’t mean we shouldn’t use it.

Why would we consider replacing NgRx at all? Why not just see it as a core dependency like the Angular framework?

There are several reasons for that:

Changed Requirements: During the lifetime of an application, requirements 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.Prepare 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.
The Facade helps here as well. It uses a BehaviorSubject internally first. When the time comes for NgRx, we just replace that BehaviorSubject.Fix Over-engineering: 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 or Signal Store: This article uses the NgRx Global Store. Next to it, NgRx also provides a Component Store (lightweight alternative) and an upcoming Signal Store (built on top of Signals).
Given the two attractive alternatives from the same ecosystem, moving away from the Global Store is no longer that uncertain.

Built-in Logic

A Facade doesn’t just hide NgRx. It can 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 must call load.

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.

That’s just one use case. The author favours the “extended selector” extension. Here, the Facade adds pipe operators to the Observable that results from a selector.

As mentioned in the beginning, we must be careful when adding logic. It opens the door for over-engineering and misuse.

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:

const sabine: Customer = {
id: 1,
firstname: ‘Sabine’,
name: ‘Miscovics’,
country: ‘AT’,
birthdate: ‘1993-05-09’
};

it(‘should show customers’, () => {
const fixture = TestBed.configureTestingModule({
imports: [CustomersContainerComponent],
providers: [
provideRouter([]),
provideMockStore({
initialState: {
customers: {
customers: [ sabine ],
},
currentPage: 1,
pageCount: 1,
},
}),
],
}).createComponent(CustomersContainerComponent);
fixture.detectChanges();

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

We see the usage of provideMockStore. What’s the issue with it?

In our component test, we must know the feature state’s structure. One of the ideas behind selectors is to hide it from our components. Now we have it 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.

it(‘should show customers’, () => {
const facadeMock: Partial<CustomersFacade> = {
get customers$(): Observable<Customer[]> {
return of([ sabine ]);
},
};
const fixture = TestBed.configureTestingModule({
imports: [CustomersContainerComponent],
providers: [
provideRouter([]),
{ provide: CustomersFacade, useValue: facadeMock },
],
}).createComponent(CustomersContainerComponent);

// rest of the test

});

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

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. In addition, we can also add logic or additional features to our Facade, which is impossible with plain NgRx. Last, we also end up with easier tests.

The Facade looks like an over-engineered 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!

NgRx Facade Pattern was originally published in ngconf on Medium, where people are continuing the conversation by highlighting and responding to this story.

Leave a Comment

Your email address will not be published. Required fields are marked *