This is the 22nd day of my participation in Gwen Challenge. For details, see Gwen Challenge.

NGRX/Store is an RXJs-based state management library inspired by Redux. In NgRx, the state consists of a map containing action and Reducer functions. The Reducer function is called by the distribution of the action and the current or initial state, and finally the Reducer returns an immutable state.

State management

State management for large, complex Angular/AngularJS projects has always been a headache on the front end. In AngularJS (1.x), state management is usually handled by a mix of services, events, and $rootScope. In Angular (version 2+), component communication makes state management a little clearer, but it’s still a bit more complicated, with many approaches depending on the data flow.

Basic principles in NGRX/Store

The view layer initiates an action through Dispatch, receives the action by Reducer, determines the execution according to the type of action.type, changes the state, returns a new state to the store, and the Store updates the state.

  • State(state) is state (state) storage
  • Action(behavior) describes a change in state
  • Reducer(reducer/reducer function) calculates the new state according to the previous state and the current behavior. The method in the reducer is pure function
  • State withStateOf observable objects,ActionThe observer —StoreTo access the

Actions

Actions is the carrier of information and sends data to the Reducer, which then updates the Store. Actions is the only way the store can accept data.

In NGRX/Store, the Action interface looks like this:

// Actions Includes the Action type and the corresponding data carrier export interface Action {type: string; payload? : any; }Copy the code

Type Indicates the expected state change type. For example, add ADD_TODO to to-do list and increase DECREMENT. Payload is the data that is sent to the store to be updated. The code for action distributed by store is similar to the following:

// dispatch an action to update store store.dispatch({type: 'ADD_TODO', payload: 'Buy milk'});Copy the code

Reducers

Reducers specify specific state changes corresponding to behaviors. Is a pure function that changes state by receiving the previous state and dispatching an action that returns a new Object as the next state. New objects are usually implemented using object. assign and extension syntax.

Export const todoReducer = (state = [], action) => { switch(action.type) { case 'ADD_TODO': return [...state, action.payload]; default: return state; }}Copy the code

Pay special attention to function purity when developing. Because pure functions:

  • It doesn’t change its state outside its domain
  • The output depends only on the input
  • The same input, always the same output

Store

The Store stores all immutable state in an application. The store in NGRX/Store is an observable of RxJS state, as well as an observer of behavior.

You can use stores to distribute behavior. You can also use the Store’s Select () method to get observables, subscribe to observations, and react to state changes.

We described the basic flow above. In the actual development process, API request, browser storage and other asynchronous operations will be involved, so effects and Services are needed. Effects will be triggered by action, and one or more actions that need to be added to the queue will be issued after some column logic, and then processed by reducers.

Developing applications using the NGRX/Store framework always maintains one state and reduces calls to the API.

A simple example

This paper briefly introduces the login module of a management system.

Create a Form

  1. Add component: LoginComponent, which is basically the layout and code for component logic

  2. Define the User: User Model

export class User { id: number; username: string; password: string; email: string; avatar: string; clear(): void { this.id = undefined; this.username = ""; this.password = ""; this.email = ""; this.avatar = "./assets/default.jpg"; }}Copy the code
  1. Add form: in componentLoginComponentincreaseFormThe form

NGRX Store

Define Actions according to the above four principles

  • Reducers define states

    Create the state in the file auth.reducers. Ts and initialize it

    export interface AuthState {
        isAuthenticated: boolean;
        user: User | null;
        errorMessage: string | null;
    }
    
    export const initialAuthState: AuthState = {
        isAuthenticated: false,
        user: null,
        errorMessage: null
    };
    Copy the code
  • Actions Define behavior

    export enum AuthActionTypes {
        Login = "[Auth] Login",
        LoginSuccess = "[Auth] Login Success",
        LoginFailure = "[Auth] Login Failure"
    }
    
    export class Login implements Action {
        readonly type = AuthActionTypes.Login;
        constructor(public payload: any) {}
    }
    Copy the code
  • Service implements data interaction (server)

    @Injectable() export class AuthService { private BASE_URL = "api/user"; constructor(private http: HttpClient) {} getToken(): string { return localStorage.getItem("token"); } login(email: string, pwd: string): Observable<any> { const url = `${this.BASE_URL}/login`; return this.http.post<User>(url, { email, pwd }); }}Copy the code
  • Effects listens for actions scheduled from the Store, performs some logic, and then dispatches new actions

    • This is normally the only place to call the API

    • Change the store state by returning an action to the Reducer

    • Effects always return one or more actions (unless @effect with {dispatch: false})

    @effect () Login: Observable<any> = this.actions.pipe(ofType(authActiontypes.login), // Execute the Login response map((action: Login) => action.payload), switchMap(payload => { return this.authService.login(payload.email, payload.password).pipe( map(user => { return new LoginSuccess({ uid: user.id, email: payload.email }); }), catchError(error => { return of(new LoginFailure(error)); })); })); @effect ({dispatch: false}) LoginFailure: Observable<any> = this.actions.pipe(ofType(AuthActionTypes.LoginFailure)); @effect ({dispatch: false}) LoginSuccess: Observable<any> = this.actions.pipe( ofType(AuthActionTypes.LoginSuccess), tap(user => { localStorage.setItem("uid", user.payload.id); this.router.navigateByUrl("/sample"); }));Copy the code

After the