1

State Management In Angular With NGXS

 3 years ago
source link: https://www.c-sharpcorner.com/article/state-management-in-angular-with-ngxs/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

State Management In Angular With NGXS

State Management, in layman's terms, refers to a way we can store data, modify it, and react to its changes and make it available to the UI components.
Recently, I got a chance to work on state management in my angular application. I used NGXS library for the same.
Just a brief note on NGXS, it is a state management pattern + library for Angular.  
It acts as a single source of truth for your application's state, providing simple rules for predictable state mutations. NGXS is modeled after the CQRS pattern popularly implemented in libraries like Redux and NgRx but reduces boilerplate by using modern TypeScript features such as classes and decorators.
There are 4 major concepts to NGXS,
  • Store: Global state container, action dispatcher and selector
  • Actions: Class describing the action to take and its associated metadata
  • State: Class definition of the state
  • Selects: State slice selectors
Now, let's quickly start with our real work 
FYI: I am working with the following configuration,
  • Angular CLI: 11.2.5
  • Node: 12.18.3
  • OS: win32 x64 

Create new Angular Application

  1. ng new state-management-with-ngxs-app  
Enforce stricter type checking -> No
Add angular routing -> Yes
Stylesheet format -> css

Change directory and open solution in VS Code (you can use any editor that you prefer). 

  1. cd state-management-with-ngxs-app  
  2. code .   

Install NGXS store

Now, in cmd or terminal of vs code, use the following command to install NGXS Store
  1. npm install @ngxs/store --save   

Add imports in app.module.ts of an angular application

Import NgxsModule from @ngxs/store in app.module.ts 
  1. import { NgModule } from '@angular/core';  
  2. import { BrowserModule } from '@angular/platform-browser';  
  3. import { AppRoutingModule } from './app-routing.module';  
  4. import { AppComponent } from './app.component';  
  5. import {NgxsModule} from '@ngxs/store';  
  6. @NgModule({  
  7.   declarations: [  
  8.     AppComponent  
  9.   imports: [  
  10.     BrowserModule,  
  11.     AppRoutingModule,  
  12.     NgxsModule.forRoot()  
  13.   providers: [],  
  14.   bootstrap: [AppComponent]  
  15. export class AppModule { }  

Install and import Bootstrap

For the sake of making our components look good in minimum efforts, we will make use of bootstrap.
Install bootstrap.
  1. npm install bootstrap --save
Import bootstrap by putting the following line in styles.css
  1. @import "~bootstrap/dist/css/bootstrap.min.css"
Also, to have a model-driven approach to handle form inputs, we will use Reactive Forms.
Import ReactiveFormsModule in app.module.ts file. So now, your app.module.ts will would look like: 
  1. import { NgModule } from '@angular/core';  
  2. import { BrowserModule } from '@angular/platform-browser';  
  3. import { AppRoutingModule } from './app-routing.module';  
  4. import { AppComponent } from './app.component';  
  5. import {NgxsModule} from '@ngxs/store';  
  6. import { ReactiveFormsModule } from '@angular/forms';  
  7. @NgModule({  
  8.   declarations: [  
  9.     AppComponent  
  10.   imports: [  
  11.     BrowserModule,  
  12.     AppRoutingModule,  
  13.     NgxsModule.forRoot(),  
  14.     ReactiveFormsModule  
  15.   providers: [],  
  16.   bootstrap: [AppComponent]  
  17. export class AppModule { }   

Create components

In src -> app, create a new folder called components.
In cmd / terminal, navigate to the path of this components folder, and generate 2 new components: user-form and user-list.
  1. ng g c user-form  
  2. ng g c user-list
Open the app.component.html file and add following code 
  1. <div class="container">  
  2.   <div class="row">  
  3.       <div class="col-6">  
  4.           <app-user-form></app-user-form>  
  5.       </div>  
  6.       <div class="col-6">  
  7.           <app-user-list></app-user-list>  
  8.       </div>  
  9.   </div>  
  10. </div>  
Put following code in user-form.component.html 
  1. <div class="card mt-5">  
  2.     <div class="card-body">  
  3.         <h5 class="card-title">Add / Edit user</h5>  
  4.         <form [formGroup]="userForm" (ngSubmit)="onSubmit()">  
  5.             <div class="form-group">  
  6.                 <label>Name</label>  
  7.                 <input type="text" class="form-control" formControlName="name" #name/>  
  8.             </div>  
  9.             <div class="form-group">  
  10.                 <label>City</label>  
  11.                 <input type="text" class="form-control" formControlName="city" #city/>  
  12.             </div>  
  13.             <div class="form-group">  
  14.                 <button type="submit" class="btn btn-success mr-2">  
  15.                 </button>  
  16.                 <button class="btn btn-primary" (click)="clearForm()">  
  17.                     Clear  
  18.                 </button>  
  19.             </div>  
  20.         </form>  
  21.     </div>  
  22. </div>  
Put following code in user-form.component.ts
  1. import { Component, OnInit } from '@angular/core';  
  2. import { User } from 'src/app/models/user';  
  3. import { Observable, Subscription } from 'rxjs';  
  4. import { Select, Store } from '@ngxs/store';  
  5. import { UserState } from 'src/app/states/user.state';  
  6. import { FormGroup, FormBuilder } from '@angular/forms';  
  7. import { UpdateUser, AddUser, SetSelectedUser } from 'src/app/actions/user.action';  
  8. @Component({  
  9.   selector: 'app-user-form',  
  10.   templateUrl: './user-form.component.html',  
  11.   styleUrls: ['./user-form.component.css']  
  12. export class UserFormComponent implements OnInit {  
  13.   @Select(UserState.getSelectedUser) selectedUser: Observable<User>;  
  14.     userForm: FormGroup;  
  15.     editUser = false;  
  16.     private formSubscription: Subscription = new Subscription();  
  17.     constructor(private fb: FormBuilder, private store: Store) {  
  18.         this.createForm();  
  19.     ngOnInit() {  
  20.       this.formSubscription.add(  
  21.         this.selectedUser.subscribe(user => {  
  22.           if (user) {  
  23.             this.userForm.patchValue({  
  24.               id: user.id,  
  25.               name: user.name,  
  26.               city: user.city  
  27.             this.editUser = true;  
  28.           } else {  
  29.             this.editUser = false;  
  30.     createForm() {  
  31.       this.userForm = this.fb.group({  
  32.           id: [''],  
  33.           name: [''],  
  34.           city: ['']  
  35.     onSubmit() {  
  36.       if (this.editUser) {  
  37.         this.store.dispatch(new UpdateUser(this.userForm.value, this.userForm.value.id));  
  38.         this.clearForm();  
  39.       } else {  
  40.         this.store.dispatch(new AddUser(this.userForm.value));  
  41.         this.clearForm();  
  42.     clearForm() {  
  43.       this.userForm.reset();  
  44.       this.store.dispatch(new SetSelectedUser(null));  
Add following code in user-list.component.html
  1. <div class="col">  
  2.     <div *ngIf="(users| async)?.length > 0; else noRecords">  
  3.         <table class="table table-striped">  
  4.             <thead>  
  5.             <tr>  
  6.                 <th>Name</th>  
  7.                 <th>City</th>  
  8.                 <th></th>  
  9.                 <th></th>  
  10.             </tr>  
  11.             </thead>  
  12.             <tbody>  
  13.             <tr *ngFor="let user of users | async">  
  14.                 <td>{{ user.name }}</td>  
  15.                 <td>{{ user.city }}</td>  
  16.                 <td>  
  17.                     <button class="btn btn-primary" (click)="editUser(user)">Edit</button>  
  18.                 </td>  
  19.                 <td>  
  20.                     <button class="btn btn-danger" (click)="deleteUser(user.id)">Delete</button>  
  21.                 </td>  
  22.             </tr>  
  23.             </tbody>  
  24.         </table>  
  25.     </div>  
  26.     <ng-template #noRecords>  
  27.         <table class="table table-striped mt-5">  
  28.             <tbody>  
  29.             <tr>  
  30.                 <td><p>No users to show!</p></td>  
  31.             </tr>  
  32.             </tbody>  
  33.         </table>  
  34.     </ng-template>  
  35. </div>  
Put following code in user-list.component.ts 
  1. import { Component, OnInit } from '@angular/core';  
  2. import { User } from 'src/app/models/user';  
  3. import { Observable } from 'rxjs';  
  4. import { Select, Store } from '@ngxs/store';  
  5. import { UserState } from 'src/app/states/user.state';  
  6. import { DeleteUser, SetSelectedUser } from 'src/app/actions/user.action';  
  7. @Component({  
  8.   selector: 'app-user-list',  
  9.   templateUrl: './user-list.component.html',  
  10.   styleUrls: ['./user-list.component.css']  
  11. export class UserListComponent implements OnInit {  
  12.   @Select(UserState.getUserList) users: Observable<User[]>;  
  13.   constructor(private store: Store) {  
  14.   ngOnInit() { }  
  15.   deleteUser(id: number) {  
  16.     this.store.dispatch(new DeleteUser(id));  
  17.   editUser(payload: User) {  
  18.     this.store.dispatch(new SetSelectedUser(payload));  

Create model

In src -> app, create new folder called models.
Inside that folder create a new file called user.ts
Put following code in user.ts file 
  1. export interface User {  
  2.     id: number;  
  3.     name: string;  
  4.     city: string;  

Create Actions

Inside src > app, create a new folder action. Inside this folder, create a new file user.action.ts.
Add following code to user.action.ts 
  1. import { User } from '../models/user';  
  2. export class AddUser {  
  3.     static readonly type = '[User] Add';  
  4.     constructor(public payload: User) {  
  5. export class UpdateUser {  
  6.     static readonly type = '[User] Update';  
  7.     constructor(public payload: User, public id: number) {  
  8. export class DeleteUser {  
  9.     static readonly type = '[User] Delete';  
  10.     constructor(public id: number) {  
  11. export class SetSelectedUser {  
  12.     static readonly type = '[User] Set';  
  13.     constructor(public payload: User) {  

Create States

Inside src > app, create a new folder states. Inside this folder, create a new file user.state.ts.
Add following code to user.state.ts
  1. import { User } from '../models/user';  
  2. import { State, Selector, Action, StateContext } from '@ngxs/store';  
  3. import { AddUser, UpdateUser, DeleteUser, SetSelectedUser } from '../actions/user.action';  
  4. import { Injectable } from '@angular/core';  
  5. export class UserStateModel {  
  6.     Users: User[];  
  7.     selectedUser: User;  
  8. @State<UserStateModel>({  
  9.     name: 'Users',  
  10.     defaults: {  
  11.         Users: [],  
  12.         selectedUser: null  
  13. @Injectable()  
  14. export class UserState {  
  15.     constructor() {  
  16.     @Selector()  
  17.     static getUserList(state: UserStateModel) {  
  18.         return state.Users;  
  19.     @Selector()  
  20.     static getSelectedUser(state: UserStateModel) {  
  21.         return state.selectedUser;  
  22.     @Action(AddUser)  
  23.     addUser({getState, patchState}: StateContext<UserStateModel>, {payload}: AddUser) {  
  24.         const state = getState();  
  25.         const UserList = [...state.Users];  
  26.         payload.id = UserList.length + 1;  
  27.         patchState({  
  28.             Users: [...state.Users, payload]  
  29.         return;  
  30.     @Action(UpdateUser)  
  31.     updateUser({getState, setState}: StateContext<UserStateModel>, {payload, id}: UpdateUser) {  
  32.         const state = getState();  
  33.         const UserList = [...state.Users];  
  34.         const UserIndex = UserList.findIndex(item => item.id === id);  
  35.         UserList[UserIndex] = payload;  
  36.         setState({  
  37.             ...state,  
  38.             Users: UserList,  
  39.         return;  
  40.     @Action(DeleteUser)  
  41.     deleteUser({getState, setState}: StateContext<UserStateModel>, {id}: DeleteUser) {  
  42.         const state = getState();  
  43.         const filteredArray = state.Users.filter(item => item.id !== id);  
  44.         setState({  
  45.             ...state,  
  46.             Users: filteredArray,  
  47.         return;  
  48.     @Action(SetSelectedUser)  
  49.     setSelectedUserId({getState, setState}: StateContext<UserStateModel>, {payload}: SetSelectedUser) {  
  50.         const state = getState();  
  51.         setState({  
  52.             ...state,  
  53.             selectedUser: payload  
  54.         return;  
Now, save everything and start the application with npm start / ng serve.
Open your browser and go to http://localhost:4200
You should be able to see the application as follows:
State management in angular with NGXS
Fill in the name and city and click save.
The new record would be seen in the right-hand side table.
State management in angular with NGXS
Similarly, you can add multiple users.
Also, users can be edited / deleted with the help of the buttons provided in the users list table.

Working

The Store is a global state manager that dispatches actions your state containers listen to and provides a way to select data slices out from the global state.
We injected the store into our components, and used it for dispatching required actions.
State management in angular with NGXS
Actions can either be thought of as a command which should trigger something to happen, or as the resulting event of something that has already happened.
An action may or may not have metadata (payload). 
In our application, we have used various actions for adding users, updating and deleting them, etc.
State management in angular with NGXS
States are classes that define a state container. Our states listen to actions via an @Action decorator.
State management in angular with NGXS
We have also used Select.
Selects are functions that slice a specific portion of state from the global state container. 
State management in angular with NGXS
State management in angular with NGXS
Hope this is helpful. If I have missed anything, let me know in the comments and I'll add it in!
You can get this application on my gihub repository
https://github.com/saketadhav/angular11-state-management-with-ngxs-app
Happy Coding!

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK