What is NgRx and why is it used in Angular apps?

This post will show you what NgRx is and why it is used in modern Angular frontend architectures.

What is NgRx and why is it used in Angular apps?

As a frontend developer, you will often encounter the NgRx framework in modern Angular frontend architectures. If not, it's worth taking a look if you are building a complex web application. I will show you what NgRx is and why it is used in modern Angular frontend architectures with practical examples.

Table of contents

What is NgRx?

NgRx is a framework for building reactive web applications in Angular.

๐Ÿ’ก
What are reactive applications?
According to reactive manifesto, reactive applications are:

Responsive - Responds in a timely manner if at all possible.
Resilient - Stays responsive in the face of failure.
Elastic - Can react to changes in the input rate by increasing or decreasing the resources allocated to service these inputs.
Message Driven - Rely on asynchronous message-passing to establish a boundary between components that ensures loose coupling, isolation and location transparency.

NgRx implements the Flux-Pattern. At a high level, it helps you unify all events and derive a common state in your Angular app. With NgRx, you store a single state and use actions to express state changes. It is ideal for apps with many user interactions and multiple data sources.

What problems does NgRx solve?

At a high-level, it mainly solves mainly the two main scenarios:

  • Sharing data between different components
  • A global state for the reuse of data

Sharing Data between different components

In a complex web application, you have different sections. Imagine the following scenario: In a web shop, you have an item list and your shopping cart. These two sections of the web shop are different component trees, probably in different Angular modules. In the item list, the user clicks on a particular item "Add to my cart". After the click, the item appears in the shopping cart.

Communication between different component trees

How does this item get into the components of the shopping cart? It is necessary to exchange your data between these distancing components.

How do you get the selected data from component tree A to component tree B?

Provide a global state for reusing data

In the above example, you also do not want to load the same data twice. In this example, you want to reuse the item data like the image, item title and price in the shopping cart components, right? So there is a need for a common data store, a global state.

How do you handle common data, that you don't need to load them twice?

NgRx provides a global store and various building blocks around the store, such as Actions, Reducers, Selectors, and Effects, to manage this store as shown in the following figure:

How does NgRx work?

NgRx implements the flux pattern. The flux pattern follows the principle of having data flow in a single direction.

To understand the basics of NgRx, it is worth watching the following video ๐Ÿ‘‡

The following illustration shows the links between the main building blocks of NgRx:

The main blocks of NgRx (green)

What are Actions in NgRx?

Actions express unique events that occur while using your web application. These can be user interaction with a particular page or external interaction through network requests or direct interaction with, for example, the device API.

Actions are dispatched via Store in NgRx and observed by NgRx's Reducers and Effects.

You can define the NgRx Actions as follows:

import {createAction, props} from '@ngrx/store';
import {Item} from "../model/items.model";

export const getItems = createAction(
  '[Item List] Get items');

export const addToCart = createAction(
  '[Item List] Add item to cart',
  props<{ item: Item }>()
);

Definition of actions

To group actions, NgRx has introduced the ActionGroup creator. The above example can be rewritten with the ActionGroup creator to reduce boilerplate code.

export const ItemListActions = createActionGroup({
  source: 'Item List',
  events: {
    'Get Items': emptyProps(),
    'Add To Cart': props<{item: Item}>(),
  }
});

Group actions with an ActionGroup

What is the Store in NgRx?

The Store is the most important component of NgRx. It provides a single store to express a global, application-wide state.
The Store is based on a single, immutable data structure. The Store is also optimized for retrieving specific state data.

To access the Store, you can simply inject it. In the store, you can select data using selectors or dispatch actions.

Dispatch Actions via Store

import {Store} from "@ngrx/store";
import {getItems} from "../../actions/items-page.actions";
 
this.store.dispatch(getItems());

Dispatch Actions via Store

What are Reducers in NgRx?

Reducers are responsible for state transitions from your store. The great thing is that reducers are pure functions, meaning they produce the same output for a given input.

State transition with a reducer

export const itemsReducer = createReducer(
  initialState,
  on(itemsLoadedSuccessfully, (store, result) => ({
    ...store,
    items: result.data
  }))
);

State transition with a reducer

What are Selectors in NgRx?

Selectors give you slices of a store state. They also help you to compose different selectors.

Provide a selector to select data from the store

export const selectItemsState = (state: ShopState) => state.itemsFeature;

export const selectItems = createSelector(
  selectItemsState,
  (itemsFeature: ItemsFeatureState | undefined) => {
    return itemsFeature?.items;
  }
);

Provide a selector to select data from the store

๐Ÿ’ก
With the @ngrx/component package you get a directive called *ngrxLet. With this directive you can simple bind data, e.g. from a selector in the markup

How to bind data from a selector in a markup?

With the @ngrx/component package you get a directive called *ngrxLet. With this directive you can easily bind data, e.g. from a selector in the markup

<ng-container *ngrxLet="number$ as n">
  <app-number [number]="n"></app-number>
</ng-container>

<ng-container *ngrxLet="number$; let n">
  <app-number [number]="n"></app-number>
</ng-container>

Binding with the *ngrxLet directive

What are Effects in NgRx?

Effects are an RxJS based side effect model for the store. Effects use streams to provide new sources for actions. Effects isolate side effects from components. This gives us "purer" components that select state and perform actions.

Provide new sources of actions with effects

  loadArticles$ = createEffect(() => this.actions$.pipe(
      ofType(getItems),
      mergeMap(() => this.articleService.getItems()
        .pipe(
          map(items => (itemsLoadedSuccessfully({data: items}))) 
        ))
    )
  );

Provide new sources of actions with effects

When should you use NgRx? Is NgRx worth it?

NgRx is a popular solution in the Angular ecosystem if you are building complex web applications with sophisticated state management. Characteristics of the need for NgRx are many user interactions and multiple data sources.

NgRx is a good choice, if:

  • the state should be accessed by many components and services
  • the state needs to be available when re-entering routes
  • the state is impacted by actions from different sources.

When should you not use NgRx?

NgRx is not a good choice, if:

  • you are building small Web Applications with isolated components.
  • your team is not really familiar with the reactive approach.

Are there examples of how to use NgRx?

There are several live examples of the use of NgRx.

NgRx example for this blog post

Following this blog post, I have created an NgRx example that is specific to the described building blocks of NgRx. This example will help you understand how to use the NgRx framework.

The example implements a catalog and shopping cart for a web store. The catalog and shopping cart are meant to demonstrate the distant angular component trees I discussed in the section above.

It contains the following technical aspects:

  • Using Store, Actions, Selectors, Reducers, and Effects in an Angular application.
  • Introduction of feature stores.
  • Sample tests for these NgRx elements.

In addition to Angular Framework and NgRx, it uses the following supporting frameworks & tools:

The live example of this blog post

Take a look at it ๐Ÿ‘‡:

Example by ngrx.io

There is also an example app in the official NgRx platform repository.

This is a good example. Take a look at it ๐Ÿ‘‡:

platform/projects/example-app at master ยท ngrx/platform
Reactive libraries for Angular. Contribute to ngrx/platform development by creating an account on GitHub.

Are there alternatives to NgRx?

Not everyone likes NgRx, but the Redux concept does. For details, see this blog post about the possible reasons. There are some alternatives to NgRx ๐Ÿ‘‡

ngxs

GitHub - ngxs/store: ๐Ÿš€ NGXS - State Management for Angular
๐Ÿš€ NGXS - State Management for Angular. Contribute to ngxs/store development by creating an account on GitHub.

akita

GitHub - salesforce/akita: ๐Ÿš€ State Management Tailored-Made for JS Applications
๐Ÿš€ State Management Tailored-Made for JS Applications - GitHub - salesforce/akita: ๐Ÿš€ State Management Tailored-Made for JS Applications

state-adapt

Introducing StateAdapt: reusable, reactive state management
NgRx has lots of boilerplate. Existing alternatives โ€œsolveโ€ this by simply eliminating one or more layers: Sick of writing reducers? Getโ€ฆ
GitHub - state-adapt/state-adapt
Contribute to state-adapt/state-adapt development by creating an account on GitHub.
๐Ÿ’ก
Mike Pearson has converted the ngrx-example from above to state-adapt. Take a look at his fork ๐Ÿ‘‡
GitHub - mfp22/ngrx-example at state-adapt
NgRx example. Contribute to mfp22/ngrx-example development by creating an account on GitHub.