State Management in Angular

Tacettin Sertkaya
5 min readNov 19, 2023

--

What is this Ngrx?

NgRx is a powerful state management library that takes its inspiration from Redux. It follows the Redux principles and provides a structured and predictable way of managing the state of Angular applications. NgRx employs various concepts, such as actions, reducers, effects, and selectors, that allow developers to handle the state in a controlled and organized manner.

NgRx consists of 5 basic concepts:

Actions: Describe unique events that are dispatched from components and services.

Reducers: Reducers are pure functions that take the current state and action to compute a new state.

Selectors: are pure functions used to select, derive and compose pieces of state.

State: is accessed with the Store, an observable of state and an observer of actions.

Let’s create sample to make clear

npm install @ngrx/store --save

Inside the “product” folder, let’s create a file called “app.state.ts”. This file will serve as the initial state model.

import { Product } from "src/app/models/product";
export interface AppState {
products: Product[];
}

We have defined an interface named “AppState” that includes a property called “products”. This property represents the initial state of our application, which is an array of “Product” type. It is essential to create this interface in our project. I created it in “src/app/models/product.model”, but you can create it anywhere you prefer. The crucial thing is to import it correctly in our “app.state.ts” file.

export interface Product {
id: number;
name: string;
description: string;
price: number;
}

Create Action

In this step, we need to create an action inside the “states\product\action” folder. The name of the file should be “production.action.ts”. This file will contain functions that represent all the actions that will modify the state of the data we are working on. Check out the code snippet below for an example:

import { createAction, props } from "@ngrx/store";
import { Product } from "src/app/models/product";
export const add = createAction('[Product] Add', props<{ product: Product }>());
export const update = createAction('[Product] Update', props<{ product: Product }>());
export const remove = createAction('[Product] Remove', props<{ product: Product }>());
export const updateAllState = createAction('[Product] Update all state of products',
props<{ products: Product[] }>());
export const clear = createAction('[Product] Clear');

It should be noted that the “createAction” function in the “@ngrx/store” module requires an action description as its first parameter and the data to be processed as the second parameter. We will be defining the following actions for our “product” state: add, remove, updateAllState, and clear.

Create Reducer

To proceed, let’s create the “product.reducer.ts” file in the “states\product\reducer” folder. This file will hold the initial state of the product list and execute the necessary business logic for each action before updating the state. For a better understanding, please refer to the code snippet provided below:

import { createReducer, on } from '@ngrx/store';
import { add, clear, remove, update, updateAllState } from '../action/product.action';
import { AppState } from '../product.state';
export const initialState: AppState = {
products: [
{ id: 1, name: 'Product 1', description: 'This is product 1', price: 100 },
{ id: 2, name: 'Product 2', description: 'This is product 2', price: 200 },
{ id: 3, name: 'Product 3', description: 'This is product 3', price: 300 },
],
};
export const productReducer = createReducer(
initialState,
on(add, (state, { product }) => (
{
...state,
products: [...state.products, product]
}
)
),
on(update, (state, { product }) => {
return {
...state,
products: state.products.map(item => item.id === product.id ? product : item)
}
}),
on(remove, (state, { product }) => ({
...state,
products: state.products.filter((p) => product.id != p.id)
})),
on(updateAllState, (state, { products }) => (
{
...state,
products
}
)
),
on(clear, state => initialState)
);

It’s important to note that the initial state is initialized with an empty array of products. Additionally, the “createReducer” function from the “@ngrx/store” module is receiving the initial state and several “ons” functions that link actions with state changes.

Create Selector

Now, to retrieve data from the storage, we need to create our selector. For this, we must create the file ‘product.selector.ts’ in the folder ‘states\product\selector’. Please see the following code for more details:

import { createFeatureSelector, createSelector } from '@ngrx/store';
import { Product } from 'src/app/models/product';
import { AppState } from '../product.state';
// get the feature selector
export const selectAppState = createFeatureSelector<AppState>('products');
// get all products
export const selectProducts = createSelector(
selectAppState,
(state: AppState) => state.products
);
// get product by ID
export const selectProductById = createSelector(
selectProducts,
(products: Product[], props: { productId: number }) =>
products.find(product => product.id === props.productId)
);

It’s important to note that the first function retrieves the complete state from the ‘product’ storage, while the second function uses this entire state to access only the products. Lastly, the third function uses the state of the products to obtain a specific product by its ID. These functions can be used in our application to retrieve the required data.

Before using our state management, we must import the StoreModule into our module and include the reducer.

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { StoreModule } from '@ngrx/store';
import { productReducer } from './states/products/reducer/product.reducer';
@NgModule({
declarations: [
AppComponent,
],
imports: [
BrowserModule,
BrowserAnimationsModule,
AppRoutingModule,
StoreModule.forRoot({ products: productReducer })
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

Let’s use our action

export class ProductsComponent {
id: number = 0;
actions = actions
products$: Observable<Array<Product>> = this.store.pipe(select(selectProducts))
constructor(private store: Store<AppState>) {
  }

Conclusion

NgRx is an Angular state management library that provides a scalable and robust solution for centrally managing application state. It utilizes actions, reducers, and selectors to efficiently handle state changes and maintain separation of concerns. Implementing NgRx can lead to more maintainable and scalable Angular applications.

Github Link: https://github.com/tacettinsertkaya/AngularNgRxCrud

--

--