State management is an important part of frontend development today, particularly in big-scale Angular applications. Without effective state management, data consistency between components can be cumbersome and prone to errors. That is where NgRx, a reactive state management library for Angular, comes in.
NgRx is not necessarily the optimal solution for every Angular project. It's necessary to know when it makes sense to use it:
NgRx adheres to the Redux pattern, and it has the following core elements:
Let's walk through the simple steps of implementing NgRx in an Angular application.
To install required packages, execute:
ng add @ngrx/store @ngrx/effects @ngrx/store-devtools @ngrx/entity
Actions specify what can occur in the state.
import { createAction, props } from '@ngrx/store';
export const loadItems = createAction('[Item] Load Items');
export const loadItemsSuccess = createAction(
'[Item] Load Items Success',
props<{ items: string[] }>()
);
export const loadItemsFailure = createAction(
'[Item] Load Items Failure',
props<{ error: string }>()
);
Reducers define how state is changed based on actions.
import { createReducer, on } from '@ngrx/store';
import { loadItemsSuccess } from './item.actions';
export interface ItemState {
items: string[];
}
const initialState: ItemState = {
items: []
};
export const itemReducer = createReducer(
initialState,
on(loadItemsSuccess, (state, { items }) => ({ ...state, items }))
);
Add the reducer and store into AppModule.
import { NgModule } from '@angular/core';
import { StoreModule } from '@ngrx/store';
import { itemReducer } from './store/item.reducer';
@NgModule({
imports: [
StoreModule.forRoot({ items: itemReducer })
]
})
export class AppModule {}
Effects handle async operations like fetching data.
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { of } from 'rxjs';
import { catchError, map, mergeMap } from 'rxjs/operators';
import { ItemService } from '../services/item.service';
import { loadItems, loadItemsSuccess, loadItemsFailure } from './item.actions';
@Injectable()
export class ItemEffects {
loadItems$ = createEffect(() =>
this.actions$.pipe(
ofType(loadItems),
mergeMap(() =>
this.itemService.getItems().pipe(
map(items => loadItemsSuccess({ items })),
catchError(error => of(loadItemsFailure({ error })))
)
)
)
);
constructor(private actions$: Actions, private itemService: ItemService) {}
}
Use selectors to fetch state data.
import { createSelector, createFeatureSelector } from '@ngrx/store';
import { ItemState } from './item.reducer';
export const selectItemState = createFeatureSelector<ItemState>('items');
export const selectItems = createSelector(selectItemState, (state) => state.items);
Inject the store in a component and dispatch actions or select data.
import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { loadItems } from './store/item.actions';
import { selectItems } from './store/item.selectors';
@Component({
selector: 'app-items',
template: `
<div *ngFor="let item of items$ | async">{{ item }}</div>
<button (click)="loadItems()">Load Items</button>
`
})
export class ItemsComponent {
items$: Observable<string[]> = this.store.select(selectItems);
constructor(private store: Store) {}
loadItems() {
this.store.dispatch(loadItems());
}
}
NgRx is a strong state management library for Angular applications, but it should only be used when needed. For small applications, plain services and behavior subjects may be enough. But for big and complicated applications with shared state, NgRx gives scalability, maintainability, and improved debugging.
Ready to transform your business with our technology solutions? Contact Us today to Leverage Our Angular Expertise.