angular 9打造用户系统 - 4使用ngrx effect从http api加载数据

楚天乐 3529 3 条

本篇要做什么

本文将使用effect从http api加载数据来显示,user-list.component改用action对象发布事件

github代码:https://github.com/shyandsy/angular-9-ngrx-user-mamagement

11.png

原教程视频连接

步骤

  1. 新建src\app\app.models.ts

通用的抽象response接口

export interface Response<T> {
    code: number,
    msg: string,
    data: T
}
  1. 实现UserService, src\app\users\user.service.ts
import { Injectable } from "@angular/core";
import { HttpClient } from "@angular/common/http";

import { Observable } from "rxjs";

import { Response } from './../app.models';
import { User, } from "./user.model";

@Injectable({
  providedIn: "root"
})
export class UserService {
  private customersUrl = "http://localhost:8000/user";

  constructor(private http: HttpClient) {}

  // http接口返回 {code: 0, messgae: "...", data: [{id: 1, username: "...", ...}, ....]}
  // 直接映射到Response<User[]>
  getUsers(): Observable<Response<User[]>> {
    return this.http.get<Response<User[]>>(this.customersUrl);
  }
}
  1. 添加src\app\users\state\user.effects.ts
import { Injectable } from '@angular/core';

import { Actions, Effect, ofType, createEffect } from '@ngrx/effects';
import { Action } from '@ngrx/store';

import { Observable, of } from 'rxjs';
import { map, mergeMap, catchError } from 'rxjs/operators';

import { UserService } from '../user.service';
import * as userActions from '../state/user.actions';

import { Response } from './../../app.models';
import { User } from '../user.model';

@Injectable()
export class UserEffect{
    constructor(
        private actions$: Actions,
        private userService: UserService
    ){}

    @Effect()
    loadUsers$: Observable<Action> = createEffect(() => {
        return this.actions$.pipe(
            ofType<userActions.LoadUsers>(
                userActions.UserActionTypes.LOAD_USERS
            ),
            mergeMap((actions: userActions.LoadUsers) => 
                this.userService.getUsers().pipe(
                    map(
                        // 解析Response<User[]>, 如果code是0(成功),提取data中的User[]
                        (usersResponse: Response<User[]>) => {
                            if(usersResponse.code == 0){
                                return new userActions.LoadUsersSuccess(usersResponse.data);
                            }else{
                                return new userActions.LoadUsersFailed(usersResponse.msg);
                            }
                        }
                    ),
                    catchError(err => of(new userActions.LoadUsersFailed(err)))
                )
            )
        )
    });
}
  1. 修改src\app\users\state\user.reducer.t
import * as userAction from './user.actions';

// 使用selector
import {createFeatureSelector, createSelector, Store} from '@ngrx/store';

import * as fromRoot from '../../state/app-state';
import { User } from '../user.model';

export interface UserState{
    users: User[],
    loading: boolean,
    loaded: boolean,
    error: string,
}

export interface AppState extends fromRoot.AppState{
    users: UserState;
}

export const initialState: UserState= {
    users: [],
    loading: false,
    loaded: true,
    error: "",
}

export function userReducer(state = initialState, action: userAction.ACTION): UserState{
    switch(action.type){
        case userAction.UserActionTypes.LOAD_USERS:{
            return {
                ...state,
                loading: true,
            }
        }
        case userAction.UserActionTypes.LOAD_USERS_SUCESS:{
            return {
                ...state,
                loading: false,
                loaded: true,
                users: action.payload
            }
        }
        case userAction.UserActionTypes.LOAD_USERS_FAILED:{
            return {
                ...state,
                loading: true,
                loaded: false,
                error: action.payload
            }
        }
        default: {
            return state;
        }
    }
}

// 加入以下代码,创建getUserFeatureState
const getUserFeatureState = createFeatureSelector<UserState>(
    "users"
)

// 创建selecotor getUsers
export const getUsers = createSelector(
    getUserFeatureState,
    (state: UserState) => state.users
)
export const getUsersLoading = createSelector(
    getUserFeatureState,
    (state: UserState) => state.loading
)
export const getUsersLoaded = createSelector(
    getUserFeatureState,
    (state: UserState) => state.loaded
)
export const getError = createSelector(
    getUserFeatureState,
    (state: UserState) => state.error
)
  1. 修改src\app\users\users.module.ts
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule, Routes } from "@angular/router";

// 加入EffectsModule, Actions 
import { EffectsModule, Actions } from '@ngrx/effects';

import { StoreModule } from '@ngrx/store';
import { userReducer } from './state/user.reducer';

// 加入UserEffect
import { UserEffect } from './state/user.effects';

import { UserComponent } from './user/user.component';
import { UserAddComponent } from './user-add/user-add.component';
import { UserEditComponent } from './user-edit/user-edit.component';
import { UserListComponent } from './user-list/user-list.component';

const userRoutes: Routes = [
  {path:"", component: UserComponent,},
  //{path:"/users/add", component: UserAddComponent}
];

@NgModule({
  declarations: [
    UserComponent, 
    UserAddComponent, 
    UserEditComponent, 
    UserListComponent
  ],
  imports: [
    CommonModule,
    RouterModule.forChild(userRoutes),
    StoreModule.forFeature("users", userReducer),

    // 使用UserEffect
    EffectsModule.forFeature([UserEffect])
  ],
  schemas: [
    CUSTOM_ELEMENTS_SCHEMA
  ],
  exports:[
    UserAddComponent,
    UserEditComponent,
    UserListComponent
  ]
})
export class UsersModule { }
  1. 修改src\app\app.module.ts,加入HttpCientModule
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';    // 加入

import { StoreModule } from '@ngrx/store';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { EffectsModule } from '@ngrx/effects';              // 加入

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HomeComponent } from './home/home.component';
import { NavbarComponent } from './navbar/navbar.component';

@NgModule({
  declarations: [
    AppComponent,
    HomeComponent,
    NavbarComponent,
  ],
  imports: [
    BrowserModule,
    StoreModule.forRoot({}), // 在module中添加reducer
    StoreDevtoolsModule.instrument(),
    EffectsModule.forRoot([]),   // 加入
    HttpClientModule,            // 加入
    AppRoutingModule,
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule { }
  1. 修改src\app\users\user-list\user-list.component.ts,加载数据
import { Component, OnInit } from '@angular/core';

import { Store } from '@ngrx/store';
import { THIS_EXPR } from '@angular/compiler/src/output/output_ast';

// 使用action对象
import * as userActions from '../state/user.actions';

import {User} from './../user.model';

@Component({
  selector: 'app-user-list',
  templateUrl: './user-list.component.html',
  styleUrls: ['./user-list.component.css']
})
export class UserListComponent implements OnInit {
  users: User[];

  constructor(private store: Store<any>) { }

  ngOnInit(): void {

    // 这行改为通过action对象发布事件
    this.store.dispatch(new userActions.LoadUsers());

    this.store.subscribe(state => (this.users = state.users.users));
  }
}
  1. 修改src\app\users\user-list\user-list.component.html
          <tr *ngFor="let user of (users$ | async)">
            <th scope="row">{{user.username}}</th>
            <td>{{user.role_id}}</td>
            <td>{{user.cnname}}</td>
            <td>{{user.enname}}</td>
            <td>{{user.email}}</td>
            <td>{{user.telephone}}</td>
            <td>{{user.mobile}}</td>
            <td>{{user.fax}}</td>
            <td>{{user.address}}</td>
            <td>{{user.post}}</td>
            <td>{{user.status}}</td>
            <th>
                <a>修改</a>
                <br>
                <a>删除</a>
            </th>
        </tr>

效果

11.png

12.png

遗留问题

文件:src\app\users\state\user.effects.ts

effect发起了两次userActions.LoadUsers,产生了两次LoadUsersSuccess

暂时不清楚产生原因

    @Effect()
    loadUsers$: Observable<Action> = createEffect(() => {
        // 执行了一次
        console.log(111);
        return this.actions$.pipe(
            ofType<userActions.LoadUsers>(
                userActions.UserActionTypes.LOAD_USERS
            ),
            mergeMap((actions: userActions.LoadUsers) => {

                // 执行了两次!!!!
                console.log(actions);
                return this.userService.getUsers().pipe(
                    map(
                        (usersResponse: Response<User[]>) => {
                            if(usersResponse.code == 0){
                                return new userActions.LoadUsersSuccess(usersResponse.data);
                            }else{
                                return new userActions.LoadUsersFailed(usersResponse.msg);
                            }
                        }
                    ),
                    catchError(err => of(new userActions.LoadUsersFailed(err)))
                )

            })
        )
    });

2020-05-26补充1:

LoadUserSuccess产生两次的原因: creareEffect()和@Effect标注使用一个就好,两个一起使用就会产生两个一样的effect,最终导致UseLoginSuccess action生成两次。

effect的两种写法

// 第一种
loadUsers$: Observable<Action> = createEffect(() => {
    return this.actions$.pipe(
       ....
    );
}); 

// 第二种
@Effect()
loadUsers$: Observable<Action> = this.actions$.pipe(
    return this.actions$.pipe(
       ....
    );
});

2020-05-26补充2:

要在module中使用reducer和effect,需要把用到的reducer和effect加入module的import中。
比如我们需要在setting module(本例子中没有)中使用Role

@NgModule({
 .......
  imports: [
    CommonModule,
    RouterModule.forChild(userRoutes),
    StoreModule.forFeature('users', userReducer),      
    StoreModule.forFeature('roles', settingRoleReducer),    // 加入role
    EffectsModule.forFeature([
      UserEffect,
      SettingRoleEffect                                     // 加入role effect
    ]),
    FormsModule, ReactiveFormsModule
  ],
  ......
})


网友最新评论( 3 )
伯安

大佬,有ng打包速度,代码体积等优化方向的文章么?

November 9th, 2020
伯安

没有server代码。。。差评

November 9th, 2020
发表我的评论
昵称 (必填)
邮箱 (必填)
网址