This article will give you an in-depth understanding of Angular's state manager NgRx and introduce how to use NgRx. I hope it will be helpful to you!
NgRx is a Redux architecture solution for global state management in Angular applications. [Recommended related tutorials: "angular tutorial"]
@ngrx/store: global state management module
@ngrx/effects: Handling side effects
@ngrx/store-devtools: Browser debugging tool, needs to rely on Redux Devtools Extension
@ngrx/schematics: command line tool to quickly generate NgRx files
@ngrx/entity: Improve developers’ efficiency in operating data in Reducer
@ngrx/router-store: Synchronize routing status to global Store
1. Download NgRx
npm install @ngrx/store @ngrx/effects @ngrx/entity @ngrx/router-store @ngrx/store-devtools @ngrx/schematics
2. Configure NgRx CLI
ng config cli.defaultCollection @ngrx/schematics
// angular.json "cli": { "defaultCollection": "@ngrx/schematics" }
3. Create Store
ng g store State --root --module app.module.ts --statePath store --stateInterface AppState
4. Create Action
ng g action store/actions/counter --skipTests
import { createAction } from "@ngrx/store" export const increment = createAction("increment") export const decrement = createAction("decrement")
5. Create Reducer
ng g reducer store/reducers/counter --skipTests --reducers=../index.ts
import { createReducer, on } from "@ngrx/store" import { decrement, increment } from "../actions/counter.actions" export const counterFeatureKey = "counter" export interface State { count: number } export const initialState: State = { count: 0 } export const reducer = createReducer( initialState, on(increment, state => ({ count: state.count + 1 })), on(decrement, state => ({ count: state.count - 1 })) )
6. Create Selector
ng g selector store/selectors/counter --skipTests
import { createFeatureSelector, createSelector } from "@ngrx/store" import { counterFeatureKey, State } from "../reducers/counter.reducer" import { AppState } from ".." export const selectCounter = createFeatureSelector<AppState, State>(counterFeatureKey) export const selectCount = createSelector(selectCounter, state => state.count)
7. Component class triggers Action and obtains status
import { select, Store } from "@ngrx/store" import { Observable } from "rxjs" import { AppState } from "./store" import { decrement, increment } from "./store/actions/counter.actions" import { selectCount } from "./store/selectors/counter.selectors" export class AppComponent { count: Observable<number> constructor(private store: Store<AppState>) { this.count = this.store.pipe(select(selectCount)) } increment() { this.store.dispatch(increment()) } decrement() { this.store.dispatch(decrement()) } }
8. Component template display status
<button (click)="increment()">+</button> <span>{{ count | async }}</span> <button (click)="decrement()">-</button>
1. Use dispatch in the component to pass parameters when triggering Action, and the parameters will eventually be placed in the Action object.
this.store.dispatch(increment({ count: 5 }))
2. When creating the Action Creator function, obtain the parameters and specify the parameter type.
import { createAction, props } from "@ngrx/store" export const increment = createAction("increment", props<{ count: number }>())
export declare function props<P extends object>(): Props<P>;
3. Obtain parameters through Action object in Reducer.
export const reducer = createReducer( initialState, on(increment, (state, action) => ({ count: state.count + action.count })) )
metaReducer is a hook between Action -> Reducer, allowing developers to preprocess Action (called before ordinary Reducer function calls).
function debug(reducer: ActionReducer<any>): ActionReducer<any> { return function (state, action) { return reducer(state, action) } } export const metaReducers: MetaReducer<AppState>[] = !environment.production ?[debug] : []
Requirement: Add a button to the page. After clicking the button, delay one second for the value to increase.
1. Add a button for asynchronous value increment in the component template. After the button is clicked, the increment_async
method is executed.
<button (click)="increment_async()">async</button>
2. Add the increment_async
method to the component class and trigger the Action to perform asynchronous operations in the method.
increment_async() { this.store.dispatch(increment_async()) }
3. Add an Action to perform asynchronous operations in the Action file.
export const increment_async = createAction("increment_async")
4. Create Effect, receive Action and perform side effects, and continue to trigger Action
ng g effect store/effects/counter --root --module app.module.ts --skipTests
The Effect function is provided by the @ngrx/effects module, so the relevant module dependencies need to be imported in the root module.
import { Injectable } from "@angular/core" import { Actions, createEffect, ofType } from "@ngrx/effects" import { increment, increment_async } from "../actions/counter.actions" import { mergeMap, map } from "rxjs/operators" import { timer } from "rxjs" // createEffect // Used to create Effect, Effect is used to perform side effects. // Pass the callback function when calling the method, and return the Observable object in the callback function. The Action object to be triggered after the side effects are executed // The return value of the callback function is continued to be returned inside the createEffect method, and the final return value is stored in In the properties of the Effect class // After instantiating the Effect class, NgRx will subscribe to the Effect class properties. When the side effects are completed, it will obtain the Action object to be triggered and trigger the Action. //Actions // When a component triggers an Action, the Effect needs to receive the Action through the Actions service, so in the Effect class, the instance object of the Actions service class is injected into the Effect class through the constructor parameter. // The instance object of the Actions service class is Observable. Object, when an Action is triggered, the Action object itself will be emitted as a data stream // ofType // Filter the target Action object. // The parameter is the Action Creator function of the target Action // If the target Action object is not filtered out, the data stream will not continue to be sent this time // If the target Action object is filtered out, the Action object will continue to be sent as a data stream @Injectable() export class CounterEffects { constructor(private actions: Actions) { // this.loadCount.subscribe(console.log) } loadCount = createEffect(() => { return this.actions.pipe( ofType(increment_async), mergeMap(() => timer(1000).pipe(map(() => increment({ count: 10 })))) ) }) }
1. Overview
Entity is translated as entity, and entity is a piece of data in the collection.
NgRx provides entity adapter objects. Under the entity adapter objects, various methods for operating entities in the collection are provided. The purpose is to improve the efficiency of developers operating entities.
2. Core
1. EntityState: Entity type interface
/* { ids: [1, 2], entities: { 1: { id: 1, title: "Hello Angular" }, 2: { id: 2, title: "Hello NgRx" } } } */ export interface State extends EntityState<Todo> {}
2. createEntityAdapter: Create entity adapter object
3. EntityAdapter: Entity adapter object type interface
export const adapter: EntityAdapter<Todo> = createEntityAdapter<Todo>() // Get the initial state. You can pass object parameters or not. // {ids: [], entities: {}} export const initialState: State = adapter.getInitialState()
3. Instance method
https://ngrx.io/guide/entity/adapter#adapter-collection-methods
4. Selector
// selectTotal gets the number of data items // selectAll gets all the data and presents it in the form of an array // selectEntities gets the entity collection and presents it in the form of a dictionary // selectIds gets the id collection and presents it in the form of an array const { selectIds, selectEntities, selectAll, selectTotal } = adapter .getSelectors();
export const selectTodo = createFeatureSelector<AppState, State>(todoFeatureKey) export const selectTodos = createSelector(selectTodo, selectAll)
1. Synchronize routing status
1)Introduce modules
import { StoreRouterConnectingModule } from "@ngrx/router-store" @NgModule({ imports: [ StoreRouterConnectingModule.forRoot() ] }) export class AppModule {}
2) Integrate routing status into Store
import * as fromRouter from "@ngrx/router-store" export interface AppState { router: fromRouter.RouterReducerState } export const reducers: ActionReducerMap<AppState> = { router: fromRouter.routerReducer }
2. Create a Selector to obtain routing status
// router.selectors.ts import { createFeatureSelector } from "@ngrx/store" import { AppState } from ".." import { RouterReducerState, getSelectors } from "@ngrx/router-store" const selectRouter = createFeatureSelector<AppState, RouterReducerState>( "router" ) export const { // Get information related to the current route (routing parameters, routing configuration, etc.) selectCurrentRoute, // Get the content after the # number in the address bar selectFragment, // Get routing query parameters selectQueryParams, // Get a specific query parameter selectQueryParam('name') selectQueryParam, // Get dynamic routing parameters selectRouteParams, // Get a specific dynamic routing parameter selectRouteParam('name') selectRouteParam, // Get route custom data selectRouteData, // Get the actual access address of the route selectUrl } = getSelectors(selectRouter)
// home.component.ts import { select, Store } from "@ngrx/store" import {AppState} from "src/app/store" import { selectQueryParams } from "src/app/store/selectors/router.selectors" export class AboutComponent { constructor(private store: Store<AppState>) { this.store.pipe(select(selectQueryParams)).subscribe(console.log) } }