This post will NOT cover the details why you need a central state management system, we just do the setup for a small demo.
The end result will show our customers as a list of buttons and when you click 1 of the customers it is shown at the bottom. So it is a very simple problem, but the same can be implemented in more complex scenarios.

So here we go… Create a new Angular app:
|
1 |
ng new my-store-app |
Navigate to the newly created directory and install the store components:
|
1 2 |
cd my-store-app npm install @ngrx/core @ngrx/store --save |
In our example below we need a customer object that will be defined in the file ‘/src/app/model.ts’:
|
1 2 3 4 5 |
export class Customer { id: string; name: string; address: string; } |
Next we create a directory where we will place all code for the store:
/src/app/state
Now decide for which features you need to store state (examples could be: customer, product, order). Then each feature can keep track of 1 or more state properties (example: for the customer feature we’ll keep track of: allCustomers & selectedCustomer).
Create for each feature a sub directory in the state folder and create the corresponding action and reducer typescript file (feature-actions.ts & feature-reducers.ts) :
|
1 2 3 4 5 6 7 |
/src/app/state/ features1/ features1-actions.ts features1-reducers.ts features2/ features2-actions.ts features2-reducers.ts |
For our example we will create the following structure:
|
1 2 3 4 |
/src/app/state/ customer/ customer-actions.ts customer-reducers.ts |
In this setup you have one reducer file per feature. If you have more reducers for a certain feature you could also create separate files for each reducer. Now you should define your actions inside the ‘feature-actions.ts’ Typescript file. You could line-up all your action(types) as a set of strings, but we try to do it more intelligent by defining our actions inside a class. Here’s an example of our customer-actions.ts file:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import {ActionReducer, Action} from "@ngrx/store"; import * as Model from '../../model'; export class CustomerActions { static LOAD_CUSTOMERS = 'LOAD_CUSTOMERS'; static loadCustomers(): Action { return { type: CustomerActions.LOAD_CUSTOMERS, payload: null } } static SELECT_CUSTOMER = 'SELECT_CUSTOMER'; static selectCustomer(customer: Model.Customer): Action { return { type: CustomerActions.SELECT_CUSTOMER, payload: customer } } } |
For each action we have a string property with the name of the action and a method that will create an action with the correct payload. The benefit of doing it this way is that we have typed methods to create action objects.
Next you need to setup your reducer(s) for the feature at hand. Each reducer will calculate the next state for any given action. Here is an example:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
import {ActionReducer, Action} from "@ngrx/store"; import {CustomerActions} from "./customer-actions"; import * as Model from "../../model"; export const CustomersReducer : ActionReducer<Model.Customer[]> = (state : Model.Customer[] = [], action: Action) => { switch(action.type) { case CustomerActions.LOAD_CUSTOMERS: return [ { id: '_smith', name: 'Mr Smith', address: 'New York' }, { id: '_bouquet', name: 'Ms Bouquet', address: 'London' } ]; default: return state; } }; export const SelectedCustomerReducer : ActionReducer<Model.Customer> = (state : Model.Customer = null, action: Action) => { switch(action.type) { case CustomerActions.SELECT_CUSTOMER: return action.payload; default: return state; } }; |
Now you can boot your store from within your module definition (/src/app/app.module.ts):
- Import the store:
1import { StoreModule } from "@ngrx/store"; - Import all your reducers:
1import { CustomersReducer, SelectedCustomerReducer } from "./state/customer/customer-reducers"; - Setup your store by adding an element to the imports section of your @NgModule:
12345@NgModule({ imports: [StoreModule.provideStore({customers: CustomersReducer,selectedCustomer: SelectedCustomerReducer})] })
As from now you can use the store from within any component or service. As an example we will update the component located at ‘src/app/app.component.ts’ in order to:
- Show all customers as buttons
- Be able to select a customer
Add the following imports:
|
1 2 3 4 |
import {Store} from '@ngrx/store'; import {Observable} from "rxjs/Observable"; import * as Model from "./model"; import {CustomerActions} from "./state/customer/customer-actions"; |
And now update the component:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
export class AppComponent { public customers : Observable<Model.Customer[]>; public selectedCustomer : Observable<Model.Customer>; constructor(private _store : Store<any>) { this.customers = this._store.select('customers'); this.selectedCustomer = this._store.select('selectedCustomer'); this._store.dispatch(CustomerActions.loadCustomers()); } public selectCustomer(customer: Model.Customer) { console.log(customer); this._store.dispatch(CustomerActions.selectCustomer(customer)); } } |
At the same time we bind the property ‘customers’ from the store with a local (observable) property ‘customers’ and the same for ‘selectedCustomer’. And we request the store to load the customers by dispatching the correct action.
Now add the following html to ‘app.component.html’:
|
1 2 3 4 5 6 7 8 |
<h1> Ngrx store demo </h1> <p> All customer:</p> <button *ngFor="let cust of customers | async" (click)="selectCustomer(cust)">{{cust.name}}</button> <p> Selected customer: {{(selectedCustomer | async)?.name}}</p> |
Now execute “ng serve” at the command prompt and you should see:
