{{announcement.body}}
{{announcement.title}}

Angular Tutorial: State Management With NgRx

DZone 's Guide to

Angular Tutorial: State Management With NgRx

A tutorial on how to use NgRx to work with state in an Angular appliation, with all the TypeScript you need to get started.

· Web Dev Zone ·
Free Resource

Do we need state management in every Angular application? Maybe not always, so how do we implement state management elegantly for the applications in which we do need it? NgRx is one of the libraries used for application state management. It is a Redux implementation for Angular.

First, let’s look at the problem we are trying to solve, and then understand the concepts behind the NgRx, and, finally, we'll jump into the coding.

Application State

What is application state? Theoretically, it is the entire memory of the application, but, typically, it is the data received via API calls, user inputs, presentation UI State, app preferences, etc. Simply put, it is the data that can differentiate two instances of the same application. A simple concrete example of application state would be a list of customers maintained in an application.

The Problem We're Trying to Solve

For simplicity, let's assume we have a list of customers in the application, and that is the state that we are trying to manage. Some API calls and user inputs could change the state ( i.e. the list ) by adding or removing customers. The state change should be reflected in the UI and other dependent components. Surely, in this particular case, we can have a global variable to hold the list and then add/remove customers from/to it and then write the code to update the UI and dependencies. But, there are many pitfalls in that design which are not the focus of this article. The NgRx is a great design for most of state management requirements. Indeed, it has a bit of boilerplate in comparison with other competitors like NGXS, Akita, or plain RxJS.

NgRx App State Management

Let’s look at the NgRx implementation — there are several components to understand. 

  • Store: Store is what holds the app's state. 

  • Action: A unique event dispatched from components and services that describe how the state should be changed. For example, ‘Add Customer’ can be an action that will change the state (i.e. add a new customer to the list).

  • Reducer: All the state changes happen inside the reducer; it responds to the action and, based on that action, it will create a new immutable state and return it to the store.

  • Selector: Selector is a function used for obtaining a part of the state from the store.

  • Effect: A mechanism that listens for dispatched actions in an observable stream, processes the server response, and returns new actions either immediately or asynchronously to the reducer to change the state. Please note that we are not using effect in this example app.

This is the interaction between those components in NgRx.

NgRx Architecture

The user interface and other components dispatch actions. Actions can have a payload which needs to change the state. The reducer creates a new state as described by the specified action and returns it to the store. Once the store updates with the new state, it will notify the UI and all dependent components. Each UI reacts to the state change and its view gets updated to reflect the changes. 

This is the representation of our example state, the customer list.

Application State

The “Add New Customer” UI control dispatches the AddCustomer action with the new customer as a payload in that action. The reducer takes the AddCustomer action and the new customer comes as a payload and creates a new list with the existing customers. Then, it will update the store with the new customer list. Finally, it will notify the UI and it will render the new list.

Finally, the Coding

Prerequisites:

Make sure Node.js and Angular CLI are installed. You can run ng --version to find out the versions you have installed on your machine.

The code for this app is available here.

1. Create an Angular App With Angular CLI

ng new angular-state-management 

Select 'No' and 'CSS' 

? Would you like to add Angular routing? No 
? Which stylesheet format would you like to use? CSS 

It will create all the required files and install the dependencies. This will take a few minutes.

2. Load the Project Into the IDE (I'm Using IntelliJ IDEA)

3. Run the App

Let's run the app created by the CLI, just to make sure everything has been created correctly.

npm start

Check that the app is running on http://localhost:4200/.

4. Install NgRx

Let’s install the NgRx now (you can use new the terminal window or exit out from the one you are on by pressing the ctrl+C key )

npm install @ngrx/store --save 

Alternatively, you can run ng add @ngrx/store if you have a CLI 6+ version.

Notice that @ngrx/store has been added to the package.json file.

5. Create a Customer model

Now we are starting to add some code. First, let’s create a  customer file using the CLI.

ng g class models/customer 

As another option, you can add it using the editor. 

The customer.ts file has been created in the src\app\models folder. Add a name property to it.

export class Customer { 

name: String = ''; 

} 

6. Add Actions

Now, we are going to add NgRx-related code. As our diagram above shows, the state that we are going to manage is the collection of customers. We can change the collection of the customer's state using the actions. For this particular case, we have two actions that can change the state:

  • AddCustomer 

  • RemoveCustomer 

Create a TypeScript file, customer.actions.ts, in the src/app folder for customer actions using the editor. 

Add the following code to the customer.actions.ts file:

import {Action} from '@ngrx/store';

export enum CustomerActionTypes {
  Add = '[Customer Component] Add',
  Remove = '[Customer Component] Remove'
}

export class ActionEx implements Action {
  readonly type;
  payload: any;
}

export class CustomerAdd implements ActionEx {
  readonly type = CustomerActionTypes.Add;

  constructor(public payload: any) {
  }
}

export class CustomerRemove implements ActionEx {
  readonly type = CustomerActionTypes.Remove;

  constructor(public payload: any) {
  }
}

7. Add a Customer Reducer

Let’s add the reducer; all state changes are happening inside the reducer based on the selected ‘Action.’ If the state is changing, then the reducer will create a new customer rather than mutating the existing customer list. In the case of the state change, the reducer is always going to return a newly created customer list object. 

Create a TypeScript file, customer.reducer.ts, in the src/app folder for CustomerReducer using the editor.

import {ActionEx, CustomerActionTypes} from './customer.actions';

export const initialState = [];

export function CustomerReducer(state = initialState, action: ActionEx) {
  switch (action.type) {
    case CustomerActionTypes.Add:
      return [...state, action.payload];

    case CustomerActionTypes.Remove:
      return [
        ...state.slice(0, action.payload),
        ...state.slice(action.payload + 1)
      ];

    default:
      return state;
  }
}

8. Add an NgRx Store to the App

Let’s add the store module to the app. 

Add the imports to the app.module.ts:

import { StoreModule } from '@ngrx/store'; 
import { CustomerReducer } from './customer.reducer'; 

And the store module

StoreModule.forRoot({ customers: CustomerReducer }) 

Now the app.module.ts should look like this.

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

import { AppComponent } from './app.component'; 

import { StoreModule } from '@ngrx/store'; 
import { CustomerReducer } from './customer.reducer'; 

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

9. Add a UI Component for View Customers

ng g c  CustomersView 

Add code to the customers-view.compoment.ts file.

Declare the customers that are observable at the top of the class

customers: Observable<Customer[]>; 

And modify the constructor:

constructor(private store: Store<{ customers: Customer[] }>) { 
  this.customers = store.pipe(select('customers')); 
} 

Import the required dependencies at the top:

import {Customer} from '../models/customer'; 
import {select, Store} from '@ngrx/store'; 
import {Observable} from 'rxjs'; 

Now the customers-view.compoment.ts file should look like this:

import {Component} from '@angular/core';
import {Customer} from '../models/customer';
import {Observable} from 'rxjs';
import {select, Store} from '@ngrx/store';

@Component({
  selector: 'app-customers-view',
  templateUrl: './customers-view.component.html',
  styleUrls: ['./customers-view.component.css']
})
export class CustomersViewComponent {

  customers: Observable<Customer[]>;

  constructor(private store: Store<{ customers: Customer[] }>) {
    this.customers = store.pipe(select('customers'));
  }
}

Add the following code to the customers-view.compoment.html file,

<h4>List of Customers</h4>
<ul class="customers">
  <li *ngFor="let customer of customers | async; let i=index">
    <span >{{i+1}}.</span> {{customer.name}}
  </li>
</ul>

And just to make the list nicer add the CSS code to the customers-view.compoment.css file.

.customers { 
  margin: 0 0 2em 0; 
  list-style-type: none; 
  padding: 0; 
  width: 15em; 
} 
.customers li { 
  background-color: steelblue; 
  color: white; 
  border-radius: 4px; 
  padding: 4px; 
  margin: 2px; 
} 

10. Add UI Controls to Add New Customers

  ng g c  CustomerAdd 

customer-add.component.ts

import {Component} from '@angular/core'; 
import {select, Store} from '@ngrx/store'; 
import {Customer} from '../models/customer'; 
import {Observable} from 'rxjs'; 
import {CustomerAdd} from '../customer.actions'; 

@Component({ 
  selector: 'app-customer-add', 
  templateUrl: './customer-add.component.html', 
  styleUrls: ['./customer-add.component.css'] 
}) 
export class CustomerAddComponent { 

  customers: Observable<Customer[]>; 

  constructor(private store: Store<{ customers: Customer[] }>) { 
    this.customers = store.pipe(select('customers')); 
  } 

  AddCustomer(customerName: string) { 
    const customer = new Customer(); 
    customer.name = customerName; 

    this.store.dispatch(new CustomerAdd(customer)); 
  } 
} 

And the customer-add.component.html file.

<h4>Add New Customer</h4>
<input #box ><button (click)="AddCustomer(box.value)">Add</button>

11. Update the App Component With CustomerView and CustomerAdd Component

Now update the app.component.html file by removing default content and embedding both the 'app-customers-view' and 'app-customer-add' components.

The app.component.html file should look like this:

<div style="text-align:center">
  <h1>
    Welcome to {{ title }}!
  </h1>
</div>
<app-customers-view></app-customers-view>
<app-customer-add></app-customer-add>

12. Run the App

Our coding is done! Let's run the app again (if it is not already running). 

npm start

13. Hook Up a 'Remove Customer' Action

We are going to add a button to the right side of the customer label and hook up the dispatch of the CutomerRemove action.

Add the following to our customers-view.compoment.ts file:

  removeCustomer(customerIndex) {
    this.store.dispatch(new CustomerRemove(customerIndex));
  }

The customers-view.compoment.ts file now looks like this:

import {Component} from '@angular/core';
import {Customer} from '../models/customer';
import {Observable} from 'rxjs';
import {select, Store} from '@ngrx/store';
import {CustomerRemove} from '../customer.actions';
@Component({
  selector: 'app-customers-view',
  templateUrl: './customers-view.component.html',
  styleUrls: ['./customers-view.component.css']
})
export class CustomersViewComponent {
  customers: Observable<Customer[]>;
  constructor(private store: Store<{ customers: Customer[] }>) {
    this.customers = store.pipe(select('customers'));
  }

  removeCustomer(customerIndex) {
    this.store.dispatch(new CustomerRemove(customerIndex));
  }
}

And, in the customers-view.compoment.html file, add the following:

<button style="float: right" (click)="removeCustomer(i)">Remove</button>

The customers-view.compoment.html now looks like this:

<h4>List of Customers</h4>
<ul class="customers">
  <li *ngFor="let customer of customers | async; let i=index">
    <span >{{i+1}}.</span> {{customer.name}}   
    <button style="float: right" (click)="removeCustomer(i)">Remove</button>
  </li>
</ul>

Here is the final version of the app. 

Angular application

Code for this app is available here.

Thank you, your feedback on this article is highly appreciated.

Topics:
ngrx ,state management ,typescript tutorials ,web dev ,angular tutorial

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}