@ngrx/store - data flow

Understanding the basics of @ngrx/store data flow and the Redux pattern

Let's try to keep things simple and focus on the core of @ngrx/store and the basic data flow in an angular and NGRX application.
We will target this lesson to understand 3 key elements from @ngrx/store:

  • Store
  • Action
  • Reducer

With these elements and our angular components, there is a simple flow of data that is described in the following diagram:

@ngrx/store - data flow

This diagram does not represent the full diagram of NGRX, think of it as representing the engine of the car, to this diagram we will gradually add additional parts until we understand the entire car, but it's best to start with a simple version and build our way from there.

Store

We will explore the diagram and start with the Store. The Store is a service that is provided by @ngrx/store.
Which means after you install @ngrx/store you can inject the Store service using Angular's DI.

npx ng add @ngrx/store@latest
some.component.ts
import {Component} from '@angular/core'
import {Store} from '@ngrx/store'

@Component({
		selector: 'some-component',
		...
})
export class SomeComponent {
		constructor(private store: Store) {}
}
Read-only

In this example a component is asking for the Store service.

There is a single Store service in the entire angular+ngrx application. The Store is holding the entire state of the application, where the main usage of the Store is:

  • Inject the Store service to read the state
  • Inject the Store change the state.

Reading the state

An Angular component that is reading the state is represented in this part of the diagram:

@ngrx/store - data flow - reading the state

We can read the state by injecting the Store service and calling the select method.

import { Component, ChangeDetectionStrategy } from "@angular/core";
import { Store } from "@ngrx/store";
import { State } from "./state";

@Component({
	changeDetection: ChangeDetectionStrategy.OnPush,
	selector: "app-root",
	template: `<h1>Reading counter from state: {{ counter$ | async }}</h1>`
})
export class AppComponent {
	counter$ = this._store.select((state) => state.counter);
	constructor(private _store: Store<State>){}
}

Note that when you store.select data from the state, you are getting an Observable of the data, which means you need to use the async pipe in the template.

Changing the state

After you inject the Store service, you can trigger a change of state process using the store.dispatch method. This part of the flow is a bit tricky, so let's break it down.

Trigger state change

Change will start from something that triggers a state change (In the diagram example we see above it's a button click).

Popular things that will start a state change process are:

  • user actions (like clicking a button)
  • server request or response
  • timer events
  • Effects (which we will discuss about in this lesson)

Keeping things simple we will use a button click to trigger a state change process. Take a look at this example which increments a counter every button click.

import { Component, ChangeDetectionStrategy } from "@angular/core";
import { Store } from "@ngrx/store";
import { State } from "./state";

@Component({
	changeDetection: ChangeDetectionStrategy.OnPush,
	selector: "app-root",
	template: `<button (click)="increment()">Increment: {{ counter$ | async }}</button>`
})
export class AppComponent {
	counter$ = this._store.select((state) => state.counter);
	constructor(private _store: Store<State>){}
	
	increment() {
		this._store.dispatch({ type: 'INCREMENT' });
	}
}

we use this._store.dispatch(...); function to send an Action, we will have a full lesson about an action, but for now let's look at an action as a simple Object which describes what happens with an identifier (in our case the identifier is: type: 'INCREMENT') and optional payload data if the event needs to send more information.

store.dispatch

When we want to start a change in the state, we inject the Store service and call store.dispatch(action) sending information about the action that happened.

Reducer

The Reducer will get the current state and the action that happened and will return a new state. Take a look at the following simple reducer:

export function counterReducer(state, action) {
	if (action.type === 'INCREMENT') {
		return state + 1;
	} else {
		return 0;
	}
}

The reducer decides what is the new state by getting the current state and the action that happened, and returns the new state.

reducer

And that is the last piece of the puzzle of NGRX data flow.

Summary

In this lesson we learned about the data flow in NGRX which consists of the following parts:

  1. Store is holding the current State
  2. Component can inject the store to read the state.
  3. Component can trigger store.dispatch(...) on events to start a state change process.
  4. The store.dispatch(Action) is getting an Action object which describes what happened.
  5. The Reducer will get the current state and the action and will return a new state.
  6. The Store will change the current state to the new state.
  7. The component will get the new state and will re-render the template.