‘NgRx‘ is a library for state management in Angular applications based on the principles of the Redux pattern. It helps manage application state in a predictable and efficient way.
Here’s how you can implement state management in Angular applications using NgRx:
1. **Install NgRx packages:**
You will need to install the following packages:
- ‘@ngrx/store‘: The core package for state management.
- ‘@ngrx/effects‘: A side-effect model for processing asynchronous actions.
- ‘@ngrx/entity‘: A package to reduce boilerplate for managing entity state.
- ‘@ngrx/store-devtools‘: A developer tool that helps debugging.
To install the packages, run:
npm install @ngrx/store @ngrx/effects @ngrx/entity @ngrx/store-devtools
2. **Define State and Actions:**
State is a single immutable data structure representing the state of your application.
interface AppState {
products: Product[];
selectedProduct: Product | null;
}
Actions represent the different ways your state can change. Every action has a type and an optional payload.
import { createAction, props } from '@ngrx/store';
export const loadProducts = createAction('[Product] Load Products');
export const loadProductsSuccess = createAction(
'[Product] Load Products Success',
props<{ products: Product[] }>()
);
export const loadProductsFail = createAction(
'[Product] Load Products Fail',
props<{ error: any }>()
);
export const selectProduct = createAction(
'[Product] Select Product',
props<{ productId: number }>()
);
3. **Create Reducers:**
Reducers are pure functions that take the current state and an action, and return a new state.
import { createReducer, on } from '@ngrx/store';
import { Product } from './product.model';
import {
loadProducts,
loadProductsSuccess,
loadProductsFail,
selectProduct
} from './product.actions';
export interface ProductState {
products: Product[];
selectedProduct: Product | null;
}
export const initialState: ProductState = {
products: [],
selectedProduct: null
};
export const productReducer = createReducer(
initialState,
on(loadProducts, state => state),
on(loadProductsSuccess, (state, { products }) => ({ ...state, products })),
on(loadProductsFail, (state, { error }) => ({ ...state, error })),
on(selectProduct, (state, { productId }) => {
const selectedProduct = state.products.find(p => p.id === productId);
return { ...state, selectedProduct };
})
);
4. **Register Reducers:**
In order to use the reducers, they need to be registered in the ‘StoreModule‘.
Import the ‘StoreModule‘ and ‘EffectsModule‘ in the ‘app.module.ts‘.
import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { productReducer } from './store/product.reducer';
import { ProductEffects } from './store/product.effects';
@NgModule({
// ...
imports: [
// ...
StoreModule.forRoot({ product: productReducer }),
EffectsModule.forRoot([ProductEffects]),
// If you want to use the Store Devtools
!environment.production ? StoreDevtoolsModule.instrument() : [],
],
// ...
})
export class AppModule {}
5. **Create and Register Effects:**
Effects are where you handle side effects like API calls, WebSocket connections, and other asynchronous processes.
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { EMPTY } from 'rxjs';
import { catchError, map, mergeMap } from 'rxjs/operators';
import { ProductService } from '../product.service';
import {
loadProducts,
loadProductsSuccess,
loadProductsFail
} from './product.actions';
@Injectable()
export class ProductEffects {
loadProducts$ = createEffect(() =>
this.actions$.pipe(
ofType(loadProducts),
mergeMap(() =>
this.productService.getProducts().pipe(
map(products => loadProductsSuccess({ products })),
catchError(error => of(loadProductsFail({ error })))
)
)
)
);
constructor(
private actions$: Actions,
private productService: ProductService
) {}
}
6. **Dispatch Actions from Components and Services:**
Now you can dispatch actions from your Angular components and services to change the state.
import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { loadProducts, selectProduct } from './store/product.actions';
@Component({
selector: 'app-root',
template: `...`,
})
export class AppComponent {
products$ = this.store.select('product', 'products');
selectedProduct$ = this.store.select('product', 'selectedProduct');
constructor(private store: Store) {
this.store.dispatch(loadProducts());
}
onSelect(productId: number): void {
this.store.dispatch(selectProduct({ productId }));
}
}
By following these steps, you can efficiently manage your Angular app’s state using NgRx. This allows you to handle complex application state in a predictable and maintainable way, with a traceable history of state changes.