Angular With NGXS - State Management

This blog explains the state management in Angular using NGXS. It is a library + state management pattern library for Angular, that acts as a single source of truth for our application's state.

What is NGXS?
 
NGXS is a library + state management pattern library for Angular. It acts as a single source of truth for your application's state. It stores the application state as in-memory data.
 
There are a couple of concepts which we should know before start coding.
  1. Store
  2. Action
  3. State
  4. Select
Store
 
It is nothing but the global state manager. It is the central point that helps in dispatching the actions and provides a way to select the data from the store.
 
Action
 
Actions are nothing but can be thought of as commands which are issued mostly to mutate the state of the application. Actions can be triggered by components and other parts of the application as well.
 
State
 
State are classes that define a global state container. We store all the data in a state and this acts as the single source of truth for all the parts of the application. It has decorators to describe the metadata about the state.
 
Select
 
These are used to read the data from the store. In NGXS, there are two methods to select state - we can either call the select method on the Store service or use the @Select decorator.
 
First, let's look at the @Select decorator.
 
Snapshots
 
You can get a snapshot of the state by calling the store.snapshot() method. This will return the entire value of the store for that point in time. 

Basic Installations to be done are -

npminstall @ngxs/store@dev –save
npminstall @ngxs/logger-plugin –save
npminstall @ngxs/devtools-plugin -save
  • Ngxs/Store is the important one to be installed; the other two are optional and used for logging and debugging the store.
  • Verify the package.json file to find the installations.

In App.Module, we need to import the NGXS module and add it to the imports section of the @NgModule.

Implementation of NGXS

Creating an interface model : (product.model.ts) – this describes the state what it would contain.

  1. export interface Product {  
  2.     id: number;  
  3.     name: string;  
  4.     description: string;  
  5. }  

 Declaring Actions for Store: (production.action.ts)

  1. import {Product} from '../model/productmodel';  
  2.   
  3. export const PRODUCT_ADD = 'AddProduct';  
  4. export const PRODUCT_DELETE = 'DeleteProduct';  
  5. export const PRODUCT_CLEAR = 'ClearProduct';  
  6.   
  7. export class AddProduct {  
  8.     static readonly type = 'PRODUCT_ADD';  
  9.     constructor(public payload:  Product ) {}  
  10. }  
  11.   
  12. export class DeleteProduct {  
  13.     static readonly type = 'PRODUCT_DELETE';  
  14.     constructor(public payload:  { productId: number }) {}  
  15. }  
  16.   
  17. export class ClearProducts {  
  18.     static type = PRODUCT_CLEAR;  
  19.     constructor() {}  
  20. }  
  21.   
  22. export type Actions = AddProduct | DeleteProduct | ClearProducts;  
 Declaring State:  (production.state.ts)
  1. // Section 1 Importing the required imports  
  2.   
  3. import { State, Action, StateContext, Selector } from '@ngxs/store';  
  4. import { Product } from '../model/productmodel';  
  5. import {AddProduct, DeleteProduct, ClearProducts } from '../action/product.action';  
  6.   
  7. // Section 2 - defining the model for state  
  8.   
  9. export class ProductStateModel {  
  10.     products: Product [];  
  11. }  
  12.   
  13. // section 3 -declare the state for the store  
  14.   
  15. @State<ProductStateModel> ({  
  16.     name: 'products',  
  17.     defaults : {  
  18.         products: []  
  19.     }  
  20. })  
  21.   
  22. export class ProductState {  
  23.   
  24.      // Section 4 - declare selectors for retrieving data from the store  
  25.   
  26.      @Selector()  
  27.      static getProducts(state: ProductStateModel) {  
  28.          return state.products;  
  29.      }  
  30.   
  31.      // Section 5 -declare actions to mutate the state of the store  
  32.   
  33.      @Action(AddProduct)  
  34.      add(context: StateContext<ProductStateModel> , { payload }: AddProduct) {  
  35.          const state = context.getState();  
  36.          context.patchState({  
  37.              products: [...state.products, payload]  
  38.          });  
  39.          console.log(context.getState());  
  40.      }  
  41.   
  42.      @Action(DeleteProduct)  
  43.      remove(  
  44.          {getState, patchState }: StateContext<ProductStateModel>,  
  45.          { payload: { productId } }: DeleteProduct) {  
  46.          patchState({  
  47.              products: getState().products.filter(a => a.id !== productId)  
  48.   
  49.          });  
  50.          console.log(productId);  
  51.          console.log(getState());  
  52.      }  
  53. }  

App UI

When you run the app, it will appear as below.
  1. In the "Create Product" section, when we add a product, it gets added to the store and appears in the Products section.
  2. When we delete the product, it gets removed from the store and the Products section is also updated.
  3. The products are pulled from the store using @selector getProducts (check out product-list.component.ts).

Application structure

 
 
Implementation
 
product.component.ts 
  1. import { Component, OnInit } from '@angular/core';  
  2. import { Store } from '@ngxs/store';  
  3. import { AddProduct } from 'src/statemanagement/action/product.action';  
  4.   
  5. @Component({  
  6.   selector: 'app-create-product',  
  7.   templateUrl: './product.component.html',  
  8.   styleUrls: ['./product.component.scss']  
  9. })  
  10.   
  11. export class CreateProductComponent implements OnInit {  
  12.   
  13.   constructor(private store: Store) { }  
  14.   
  15.   ngOnInit() {  
  16.   }  
  17.   
  18.   saveProduct(id, name, description) {  
  19.   
  20.     // Adding Data to store  
  21.     this.store.dispatch(new AddProduct(  
  22.       {  
  23.         id: id,  
  24.         name: name,  
  25.         description: description  
  26.       }  
  27.     ));  
  28.   }  
  29. }  

 Product.component.html

  1. <div style="max-width:300px;">  
  2.     <h3>Create Product</h3>  
  3.     <div class="form-group">  
  4.       <input class="form-control" type="number" placeholder="id" #id>  
  5.     </div>  
  6.     <div class="form-group">  
  7.       <input class="form-control" type="text" placeholder="name" #name>  
  8.     </div>  
  9.     <div class="form-group">  
  10.       <input class="form-control" type="text" placeholder="description" #description>  
  11.     </div>  
  12.     <button class="btn btn-success" (click)="saveProduct(id.value,name.value,description.value)">Save Product</button>  
  13.   </div>  

Product-details.component.ts 

  1. import { Component, OnInit, Input } from '@angular/core';  
  2. import { Store } from '@ngxs/store';  
  3. import { Product } from 'src/statemanagement/model/productmodel';  
  4. import { DeleteProduct } from 'src/statemanagement/action/product.action';  
  5.   
  6. @Component({  
  7.   selector: 'app-product-details',  
  8.   templateUrl: './product-details.component.html',  
  9.   styleUrls: ['./product-details.component.scss']  
  10. })  
  11. export class ProductDetailsComponent implements OnInit {  
  12.   
  13.   @Input() product: Product;  
  14.   
  15.   constructor(private store: Store) { }  
  16.   
  17.   ngOnInit() {  
  18.   }  
  19.   
  20.   deleteproduct(id) {  
  21.   
  22.     // Deleting product from the Store  
  23.     this.store.dispatch(new DeleteProduct({ productId: id }));  
  24.   }  
  25. }  

Product-details.component.html

  1. <div *ngIf="product">  
  2.     <div>  
  3.       <label>Name: </label> {{product.name}}  
  4.     </div>  
  5.     <div>  
  6.       <label>Description: </label> {{product.description}}  
  7.     </div>  
  8.     <button class="button is-small btn-danger" (click)='deleteproduct(product.id)'>Delete Product</button>  
  9.     <hr/>  
  10.   </div>  

 Product-list.component.ts

  1. import { Component, OnInit } from '@angular/core';  
  2. import { Store, Select } from '@ngxs/store';  
  3. import { Observable } from 'rxjs';  
  4.   
  5.   
  6. import { ProductState } from 'src/statemanagement/state/product.state';  
  7. import { Product } from 'src/statemanagement/model/productmodel';  
  8.   
  9.   
  10. @Component({  
  11.   selector: 'app-product-list',  
  12.   templateUrl: './product-list.component.html',  
  13.   styleUrls: ['./product-list.component.scss']  
  14. })  
  15. export class ProductListComponent implements OnInit {  
  16.   
  17.   @Select(ProductState.getProducts) products: Observable<Product[]>;  
  18.   
  19.   constructor(private store: Store) {  
  20.   
  21.   }  
  22.   
  23.   ngOnInit() {  
  24.   }  
  25.   
  26. }  

Product-list.component.html

  1. <div *ngIf="products">  
  2.   <h3>Products</h3>  
  3.   <div *ngFor="let product of products | async">  
  4.     <app-product-details [product]='product'></app-product-details>  
  5.   </div>  
  6. </div>  

 app.component.html<!--The content below is only a placeholder and can be replaced.-->

  1. <div style="text-align:center">  
  2.   <h1>  
  3.     Welcome to {{ title }}!  
  4.   </h1>  
  5.   <!-- <img width="300" alt="Angular Logo" src="https://www.google.co.in/search?q=ngxs+image+link&rlz=1C1GCEA_enIN802IN802&source=lnms&tbm=isch&sa=X&ved=0ahUKEwjAr4exn4jfAhVYyYMKHR2yBKAQ_AUIECgD&biw=1680&bih=909#imgrc=hlHCvEHXyLTMjM:"> -->  
  6. </div>  
  7. <!--   
  8. <router-outlet></router-outlet> -->  
  9. <div class="row">  
  10.   <div class="col-sm-4">  
  11.     <app-create-product></app-create-product>  
  12.   </div>  
  13.   <div class="col-sm-4">  
  14.     <app-product-list></app-product-list>  
  15.   </div>  
  16. </div>  
You can find the sourcecode on GitHub also.
 
Next, we will try to implement the children concept in NGXS.
 
Thanks.