Getting started with NGRX in Angular 7 – Quick tutorials

I spent hours trying to understand how NGRX works. So, I thought it was a good idea to write a quick tutorial to understand it. I Hope it will help you to set your project up quickly. For this tutorial we will generate an Angular 7 project and for a better understanding, let’s resolve a simple problem.

Overview

NGRX allows you to manage the state of your application by stocking JSON data. So, any component in your angular application can share data by subscribing to the “NGRX Store” and any functions can change the state of this store by triggering “NGRX Actions”. So, if you used to use Input() and Output() functions of angular and your application became literally a mess, NGRX can really change your life. Here is a simplified schema of NGRX.

NGRX

NGRX is separated into three steps. Action, Reducer and Store. Basically, the action is an event you trigger. For example, you can trigger an action when you click on a button. Actions can be called for any event into your application. Reducer is the operation you want to do with your action on your data stored in the NGRX store. And finally, the store is the place where data are stored. Any component can subscribe to the store and any of them can change its state. Besides, All operations are asynchronous.

Here is the problem: We assume that we want to create a market place. So the first thing we need to do is to add and remove item in the basket. To do that, we will use NGRX

1 – Requirements

To resolve this problem, let’s install angular-cli:

 sudo npm install -g @angular/cli 

Let’s generate the project:

ng new market-place

Now, let’s install the last version of NGRX:

npm install @ngrx/store@6.1.2 --save

2 – Action, Reducer, State

Let’s assume that an item is just a name and an id like this:

export interface Item {
name: string;
id: string;
}

Let’s create a basket.action.ts file:

import { Action } from '@ngrx/store';
import {Item} from '../../app/item/item.component';

export namespace BasketActionTypes {
export const ADD = '[Basket] add';
export const REMOVE = '[Basket] remove';
}

export class BasketAddAction implements Action {
readonly type = BasketActionTypes.ADD;
public payload: Item;

constructor(public item: Item) {
this.payload = item;
}
}

export class BasketRemoveAction implements Action {
readonly type = BasketActionTypes.REMOVE;
public payload: Item;

constructor(public item: Item) {
this.payload = item;
}
}

export type BasketAction = BasketAddAction
| BasketRemoveAction;

So, we create the namespace BasketActionTypes. In this namespace we put all actions we want to trigger in the basket. Here, it’s ‘ADD’ and ‘REMOVE’ actions. Then, we create two classes that implement Action. Basically, an action is a type and a payload. The type is just a string to recognize which action is triggered, it’s an id so it must be unique. The payload field is the information you want to process in the reducer. For BasketAddAction, the type is ‘[Basket] add’ and the payload is the item in the constructor. Finally, we export the type BasketAction which can be a BasketAddAction or a BasketRemoveAction.

Now let’s see the state in the basket.state.ts file:

import {Item } from '../../app/item/item.component';

export interface BasketState {
items: Item[];
}

Basically, the BasketState defines the object which will be stored in the NGRX store. Here, we can see that a BasketState is an array of Item.

And to finish with the state, we need to add the app.state.ts like this

import {BasketState} from './basket.state';

export interface AppState {
basket: BasketState;
}

The AppState object allows you to have access to any kind of objects in the store. Here, the only object is the BasketState. The last part is the reducer we can find in the basket.reducer.ts:


import {BasketState} from '../state/basket.state';
import {BasketAction, BasketActionTypes} from '../action/basket.action';

const initialBasketState: BasketState = {
items: []
};

export function basketActionReducer(state: BasketState = initialBasketState, action: BasketAction): BasketState {
switch (action.type) {
case BasketActionTypes.ADD:
state.items.push(action.payload);
return {
items: state.items
};
case BasketActionTypes.REMOVE:
const index: number = state.items.indexOf(action.payload);
if (index !== -1) {
state.items.splice(index, 1);
}
return {
items: state.items
};
}
}

The basketActionReducer is called each time an action is dispatched to the basket. The ‘state’ parameter represents the previous state of the basket and the return value of this function is the next step. The ‘action’ parameter allows you to know which action is triggered. So when we trigger an ‘ADD’ basket action, we push the payload (the new item) in the previous basket state, then we return the new state of the basket. For the ‘REMOVE’ basket action, we look for the index of the searched item then we remove it from the previous basket state and we return the new basket state (without the removed item).

Do not forget to update your app.module.ts like this:


import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { StoreModule } from '@ngrx/store';
import { basketActionReducer } from '../ngrx/reducer/basket.reducer';

@NgModule({
declarations: [
AppComponent,
],
imports: [
BrowserModule,
StoreModule.forRoot({ basket: basketActionReducer }),
AppRoutingModule,
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

The StoreModule stores the object which will be in the store. It means when we access to the store
we will have access to a basket parameter that returns a BasketState object (the return value of
the basketActionReducer function). Now everything is set up we can use NGRX to trigger actions and store data
in the store.

3 – Implementation

In the app.component.ts:


import {Component, OnInit} from '@angular/core';
import {Item} from './item/item.component';
import {select, Store} from '@ngrx/store';
import {BasketAddAction, BasketRemoveAction} from '../ngrx/action/basket.action';
import {AppState} from '../ngrx/state/app.state';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
items: Item[] = [];
currentBasket: Item[];

constructor(public store: Store<AppState>) {
this.store.pipe(select(state => state.basket))
.subscribe(basket => {
if (basket && items) {
this.currentBasket = basket.items;
}
});
}

addItem(item: Item) {
this.store.dispatch(new BasketAddAction(item));
}

removeItem(item: Item) {
this.store.dispatch(new BasketRemoveAction(item));
}

ngOnInit() {
this.items.push({ id: '1', name: 'item1'}, { id: '2', name: 'item2'}, { id: '3', name: 'item3'});
}
}

And:


<div *ngFor="let item of items">
{{ item.id }} - {{ item.name }}<br/>
<button (click)="addItem(item)">Add</button>
<button (click)="removeItem(item)">Remove</button>
</div>
<br/>
<div *ngFor="let item of currentBasket">
{{ item.name }}<br/>
</div>

If you do understand everything I explained earlier, this part should be easy. In the constructor, we subscribe to the store to be notified each time the basket change. This is exactly the strength of NGRX because we can subscribe to the store in any component into your application. Then, when we want to add or remove item in the basket, we just need to dispatch actions (BasketAddAction and BasketRemoveAction). Here is the result:

You can find the source code on Github.

Be the first to comment

Leave a Reply

Your email address will not be published.


*