angular 9打造用户系统 - 7使用ngrx router store管理路由
本篇要做什么 本系列最后一篇 本文将使用ngrx router store来绑定angular路由到ngrx，同事使用CustomRouteStateSerializer github代码：https://github.com/shyandsy/angular-9-ngrx-user-mamagement 原教程视频连接 https://www.youtube.com/watch?v=XHw4hYkGsHM&list=PLaMbwDs23r4KXoMucJEyUAvamQ-kFNBvC&index=9 安装文档文档位置 https://github.com/ngrx/platform/tree/master/docs/router-store 安装 npm install @ngrx/router-store --save 新建src\app\shared\utils.ts, customer-route-state-serializer。这段代码是上面文档提供的 import { StoreModule, ActionReducerMap } from "@ngrx/store"; import { Params, RouterStateSnapshot} from '@angular/router'; import { RouterStateSerializer, RouterReducerState, routerReducer, } from '@ngrx/router-store'; export interface RouterStateUrl { url: string; params: Params; queryParams: Params; } export interface State { router: RouterReducerState<RouterStateUrl>; } export class CustomSerializer implements RouterStateSerializer<RouterStateUrl> { serialize(routerState: RouterStateSnapshot): RouterStateUrl { let route = routerState.root; while (route.firstChild) { route = route.firstChild; } const { url, root: { queryParams }, } = routerState; const { params } = route; // Only return an object including the URL, params and query params // instead of the entire snapshot return { url, params, queryParams }; } } // In your root reducer export const reducers: ActionReducerMap<State> = { router: routerReducer, }; 步骤 修改src\app\app.module.ts import { StoreRouterConnectingModule, routerReducer, RouterStateSerializer} from '@ngrx/router-store'; import { CustomSerializer } from "./shared/utils" imports: [ BrowserModule, StoreModule.forRoot({}), // 在module中添加reducer StoreDevtoolsModule.instrument(), EffectsModule.forRoot([]), HttpClientModule, AppRoutingModule, ReactiveFormsModule, // 使用FormBuilder需要导入ReactiveFormsModule // 增加 StoreRouterConnectingModule.forRoot({stateKey: "router"}), ], // 加入provider providers: [{ provide: RouterStateSerializer, useClass: CustomSerializer }], 效果
angular 9打造用户系统 - 6使用ngrx entity完成CRUD操作
本篇要做什么 本文将使用effect entity来实现CRUD操作,需要用到ReactiveForm github代码：https://github.com/shyandsy/angular-9-ngrx-user-mamagement 原教程视频连接 https://www.youtube.com/watch?v=XHw4hYkGsHM&list=PLaMbwDs23r4KXoMucJEyUAvamQ-kFNBvC&index=8 步骤 src/app/app.module.ts导入ReactiveFormsModule 不在给出全部代码，需要的话，参照前一篇文章。 // 导入ReactiveFormsModule import { ReactiveFormsModule } from '@angular/forms'; imports: [ BrowserModule, StoreModule.forRoot({}), StoreDevtoolsModule.instrument(), EffectsModule.forRoot([]), HttpClientModule, AppRoutingModule, ReactiveFormsModule, // 加入ReactiveFormsModule，使用FormBuilder需要导入ReactiveFormsModule ], 给出完整的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 usersUrl = "http://localhost:8000/user"; constructor(private http: HttpClient) {} getUsers(): Observable<Response<User[]>> { console.log("user service: Get Users") return this.http.get<Response<User[]>>(this.usersUrl, //{headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'}} ); } getUserById(payload: number): Observable<Response<User>> { console.log("user service: Get User By Id") return this.http.get<Response<User>>(`${this.usersUrl}/${payload}`, //{headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'}} ); } createUser(payload: User): Observable<Response<User>> { console.log("user service: Create User") console.log(payload) return this.http.post<Response<User>>(this.usersUrl, payload, //{headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'}} ); } updateUser(user: User): Observable<Response<User>> { console.log("user service: Update User") return this.http.patch<Response<User>>( `${this.usersUrl}/${user.id}`, user, //{headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'}} ); } deleteUser(payload: number) { console.log("user service: Delete User") return this.http.delete(`${this.usersUrl}/${payload}`, //{headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'}} ); } } src/app/users/users.module.ts导入ReactiveForm // 加入 import { FormsModule, ReactiveFormsModule } from '@angular/forms'; imports: [ CommonModule, RouterModule.forChild(userRoutes), StoreModule.forFeature("users", userReducer), EffectsModule.forFeature([UserEffect]), // 加入这行 FormsModule, ReactiveFormsModule ], 修改user/state中的actions，effects，reducer src\app\users\state\user.actions.ts import { Action } from '@ngrx/store'; import { Update } from '@ngrx/entity'; import { User } from '../user.model'; export enum UserActionTypes { LOAD_USERS = "[User] Load Users", LOAD_USERS_SUCESS = "[User] Load Users Success", LOAD_USERS_FAILED = "[User] Load Users Failed", // 增加单个user load操作 LOAD_USER = "[User] Load User", LOAD_USER_SUCESS = "[User] Load User Success", LOAD_USER_FAILED = "[User] Load User Failed", // 增加create user CREATE_USER = "[User] Create User", CREATE_USER_SUCESS = "[User] Create User Success", CREATE_USER_FAILED = "[User] Create User Failed", // 增加update user UPDATE_USER = "[User] Update User", UPDATE_USER_SUCESS = "[User] Update User Success", UPDATE_USER_FAILED = "[User] Update User Failed", // 增加delete user DELETE_USER = "[User] Delete User", DELETE_USER_SUCESS = "[User] Delete User Success", DELETE_USER_FAILED = "[User] Delete User Failed", } export class LoadUsers implements Action{ readonly type = UserActionTypes.LOAD_USERS; } export class LoadUsersSuccess implements Action{ readonly type = UserActionTypes.LOAD_USERS_SUCESS; constructor(public payload: User[]) {} } export class LoadUsersFailed implements Action{ readonly type = UserActionTypes.LOAD_USERS_FAILED; constructor(public payload: string) {} } // 增加加载单个user，编辑时候加载user export class LoadUser implements Action{ readonly type = UserActionTypes.LOAD_USER; constructor(public payload: number) {} // 使用id来加载 } export class LoadUserSuccess implements Action{ readonly type = UserActionTypes.LOAD_USER_SUCESS; constructor(public payload: User) {} } export class LoadUserFailed implements Action{ readonly type = UserActionTypes.LOAD_USER_FAILED; constructor(public payload: string) {} } // 增加update user export class UpdateUser implements Action{ readonly type = UserActionTypes.UPDATE_USER; constructor(public payload: User) {} // 提交user对象 } export class UpdateUserSuccess implements Action{ readonly type = UserActionTypes.UPDATE_USER_SUCESS; constructor(public payload: Update<User>) {} // 使用ngrx/entity的Update } export class UpdateUserFailed implements Action{ readonly type = UserActionTypes.UPDATE_USER_FAILED; constructor(public payload: string) {} } // 增加create user export class CreateUser implements Action{ readonly type = UserActionTypes.CREATE_USER; constructor(public payload: User) {} } export class CreateUserSuccess implements Action{ readonly type = UserActionTypes.CREATE_USER_SUCESS; constructor(public payload: User) {} } export class CreateUserFailed implements Action{ readonly type = UserActionTypes.CREATE_USER_FAILED; constructor(public payload: string) {} } // 增加delete user export class DeleteUser implements Action{ readonly type = UserActionTypes.DELETE_USER; constructor(public payload: number) {} // 使用id来删除对象 } export class DeleteUserSuccess implements Action{ readonly type = UserActionTypes.DELETE_USER_SUCESS; constructor(public payload: number) {} // 返回id } export class DeleteUserFailed implements Action{ readonly type = UserActionTypes.DELETE_USER_FAILED; constructor(public payload: string) {} } // 增加的action加入到这里 export type ACTION = LoadUsers | LoadUsersSuccess | LoadUsersFailed | LoadUser | LoadUserSuccess | LoadUserFailed | UpdateUser | UpdateUserSuccess | UpdateUserFailed | CreateUser | CreateUserSuccess | CreateUserFailed | DeleteUser | DeleteUserSuccess | DeleteUserFailed ; src\app\users\state\user.effects.ts 这个文件重点说两点 方法前面加@Effect()会导致方法执行两次，就是前面遗留的问题终于搞清楚了。 我的api返回的是{code: 0, message: "", data:[]},code=0表示成功，message是错误消息，data是返回的数据。所以我要在response中判断response.code来决定是发出Success Action还是Fail Action //@Effect() => 如果这行不注释掉，action会调用两次，具体原因不清楚 loadUsers$: Observable<Action> = createEffect(() => { ....... } `` return this.userService.getUsers().pipe( map( (usersResponse: Response<User[]>) => { // 判断api请求是否成功 if(usersResponse.code == 0){ return new userActions.LoadUsersSuccess(usersResponse.data); }else{ return new userActions.LoadUsersFailed(usersResponse.msg); } } ), catchError(err => of(new userActions.LoadUsersFailed(err)))) 完整代码 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 ){} loadUsers$: Observable<Action> = createEffect(() => { console.log("test111"); console.log(this.actions$) return this.actions$.pipe( ofType<userActions.LoadUsers>( userActions.UserActionTypes.LOAD_USERS ), mergeMap((actions: userActions.LoadUsers) => { console.log("xxx111") console.log(actions) return this.userService.getUsers().pipe( map( (usersResponse: Response<User[]>) => { console.log("xxx2222"); if(usersResponse.code == 0){ return new userActions.LoadUsersSuccess(usersResponse.data); }else{ return new userActions.LoadUsersFailed(usersResponse.msg); } } ), catchError(err => of(new userActions.LoadUsersFailed(err))) ) }) ) }); // 加载一个user，用于编辑 loadUser: Observable<Action> = createEffect(() => { return this.actions$.pipe( ofType<userActions.LoadUser>( userActions.UserActionTypes.LOAD_USER ), mergeMap((action: userActions.LoadUser) => { console.log(action); return this.userService.getUserById(action.payload).pipe( map( (usersResponse: Response<User>) => { if(usersResponse.code == 0){ return new userActions.LoadUserSuccess(usersResponse.data); }else{ return new userActions.LoadUserFailed(usersResponse.msg); } } ), catchError(err => of(new userActions.LoadUserFailed(err))) ) }) ) }); // 创建一个user createUser: Observable<Action> = createEffect(() => { return this.actions$.pipe( ofType<userActions.CreateUser>( userActions.UserActionTypes.CREATE_USER ), map((action: userActions.CreateUser) => action.payload), mergeMap((user: User) => { console.log(user); return this.userService.createUser(user).pipe( map( (usersResponse: Response<User>) => { if(usersResponse.code == 0){ return new userActions.CreateUserSuccess(usersResponse.data); }else{ return new userActions.CreateUserFailed(usersResponse.msg); } } ), catchError(err => of(new userActions.CreateUserFailed(err))) ) }) ) }); // 更新一个user updateUser: Observable<Action> = createEffect(() => { return this.actions$.pipe( ofType<userActions.UpdateUser>( userActions.UserActionTypes.UPDATE_USER ), map((action: userActions.UpdateUser) => action.payload), mergeMap((user: User) => { console.log(user); return this.userService.updateUser(user).pipe( map( (usersResponse: Response<User>) => { if(usersResponse.code == 0){ return new userActions.UpdateUserSuccess({ id: usersResponse.data.id, changes: usersResponse.data }); }else{ return new userActions.UpdateUserFailed(usersResponse.msg); } } ), catchError(err => of(new userActions.UpdateUserFailed(err))) ) }) ) }); // 删除一个user deletUser: Observable<Action> = createEffect(() => { return this.actions$.pipe( ofType<userActions.DeleteUser>( userActions.UserActionTypes.DELETE_USER ), map((action: userActions.DeleteUser) => action.payload), mergeMap((id: number) => { console.log(id); return this.userService.deleteUser(id).pipe( map( (usersResponse: Response<User>) => { if(usersResponse.code == 0){ return new userActions.DeleteUserSuccess(id) }else{ return new userActions.DeleteUserFailed(usersResponse.msg); } } ), catchError(err => of(new userActions.DeleteUserFailed(err))) ) }) ) }); } 修改src\app\users\state\user.reducer.ts - 不在处理LOAD_USERS，LOAD_USER, CREATE_USER, UPDATE_USER, DELETE_USER,这些都是网络api调用，现在交给user.effects.ts去处理 - reducer专注处理业务，处理各种网络操作的成功和失败。LOAD_USERS_SUCESS，LOAD_USERS_FAILED，LOAD_USER_SUCESS，LOAD_USER_FAILED，CREATE_USER_SUCESS，CREATE_USER_FAILED，UPDATE_USER_SUCESS，UPDATE_USER_FAILED，DELETE_USER_SUCESS，DELETE_USER_FAILED - import * as userAction from './user.actions'; import {createFeatureSelector, createSelector, Store} from '@ngrx/store'; import {EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity'; import * as fromRoot from '../../state/app-state'; import { User } from '../user.model'; export interface UserState extends EntityState{ selectedUserId: number | null; loading: boolean, loaded: boolean, error: string, } export interface AppState extends fromRoot.AppState{ users: UserState; } export const userAdapter: EntityAdapter = createEntityAdapter(); export const defaultUser: UserState = { ids: [], entities: {}, selectedUserId: null, loading: false, loaded: false, error: "" } export const initialState: UserState = userAdapter.getInitialState(defaultUser); export function userReducer(state = initialState, action: userAction.ACTION): UserState{ switch(action.type){ case userAction.UserActionTypes.LOAD_USERS_SUCESS:{ console.log("reducer: LOAD_USERS_SUCESS") return userAdapter.addAll(action.payload, { ...state, loading: false, loaded: true, }) } case userAction.UserActionTypes.LOAD_USERS_FAILED:{ console.log("reducer: LOAD_USERS_FAILED") return { ...state, entities: {}, loading: true, loaded: false, error: action.payload } } // load user case userAction.UserActionTypes.LOAD_USER_SUCESS:{ console.log("reducer: LOAD_USER_SUCESS") return userAdapter.addOne(action.payload, { ...state, selectedUserId: action.payload.id, }) } case userAction.UserActionTypes.LOAD_USER_FAILED:{ console.log("reducer: LOAD_USER_FAILED") return { ...state, error: action.payload, } } // create user case userAction.UserActionTypes.CREATE_USER_SUCESS:{ console.log("reducer: CREATE_USER_SUCESS") return userAdapter.addOne(action.payload, state) } case userAction.UserActionTypes.CREATE_USER_FAILED:{ console.log("reducer: CREATE_USER_FAILED") return { ...state, error: action.payload, } } // update user case userAction.UserActionTypes.UPDATE_USER_SUCESS:{ console.log("reducer: UPDATE_USER_SUCESS") return userAdapter.updateOne(action.payload, state) } case userAction.UserActionTypes.UPDATE_USER_FAILED:{ console.log("reducer: UPDATE_USER_FAILED") return { ...state, error: action.payload, } } // delete user case userAction.UserActionTypes.DELETE_USER_SUCESS:{ console.log("reducer: DELETE_USER_SUCESS") return userAdapter.removeOne(action.payload, state) } case userAction.UserActionTypes.DELETE_USER_FAILED:{ console.log("reducer: DELETE_USER_FAILED") return { ...state, error: action.payload, } } default: { return state; } } } const getUserFeatureState = createFeatureSelector( "users" ) export const getUsers = createSelector( getUserFeatureState, userAdapter.getSelectors().selectAll ) 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 ) // 增加获取当前选中user的id export const getCurrentUserId = createSelector( getUserFeatureState, (state: UserState) => state.selectedUserId ) // 增加获取当前选中user export const getCurrentUser = createSelector( getUserFeatureState, getCurrentUserId, state => state.entities[state.selectedUserId] ) 5. 修改src/app/users/user-add组件 修改模板src\app\users\user-add\user-add.component.html， - 使用reactiveForm，用户点击提交，调用component的createUser()方法, - createUser方法在user-add.component.ts中实现这个方法 添加用户 .......... 添加 修改src\app\users\user-add\user-add.component.ts - 原视频中form使用了下面这段代码创建form，这种做法已经不适用于angular 9。新的做法请参考我下面的代码 this.customerForm = this.fb.group({ name: ["", Validators.required], // angular 9这么做会报错 }); - 原视频中从form获取数据的方式，也已经不再适用于angular 9。新的做法请参考我下面的代码 const newCustomer: Customer = { name: this.customerForm.get("name").value, // angular 9这么做会报错 ....... } 完整代码 import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, FormControl, Validators } from '@angular/forms'; import { Store } from '@ngrx/store'; import as userActions from '../state/user.actions'; import as fromUser from '../state/user.reducer'; import { User } from '../user.model'; @Component({ selector: 'app-user-add', templateUrl: './user-add.component.html', styleUrls: ['./user-add.component.css'] }) export class UserAddComponent implements OnInit { userForm: FormGroup; constructor( private fb: FormBuilder, // inject FormBuilder private store: Store ) { } ngOnInit(): void { this.userForm = this.fb.group({ username: new FormControl("", {validators: [Validators.required,]}), password: new FormControl("", {validators: [Validators.minLength(6), Validators.maxLength(32)]}), role_id: new FormControl("", {validators: [Validators.required]}), cnname: new FormControl("", {validators: [Validators.minLength(2), Validators.maxLength(6)]}), enname: new FormControl("", {validators: [Validators.minLength(2), Validators.maxLength(30)]}), email: new FormControl("", {validators: [Validators.email]}), telephone: new FormControl("", {validators:[Validators.minLength(10), Validators.maxLength(11)]}), mobile: new FormControl("", {validators:[Validators.minLength(10), Validators.maxLength(11)]}), fax: new FormControl("", {validators:[Validators.minLength(10), Validators.maxLength(11)]}), address: new FormControl("", {validators:[Validators.minLength(3), Validators.maxLength(50)]}), post: new FormControl("", {validators:[Validators.minLength(6), Validators.maxLength(8)]}), expired: new FormControl("", {validators:[Validators.minLength(19), Validators.maxLength(19)]}), status: new FormControl("", {validators:[Validators.min(0), Validators.max(1)]}), }) } // 创建用户 createUser(){ console.log(this.userForm.value) console.log("创建用户") // 构造user对象 const newUser: User = { username: this.userForm.value.username, password: this.userForm.value.password, role_id: this.userForm.value.role_id, cnname: this.userForm.value.cnname, enname: this.userForm.value.enname, email: this.userForm.value.email, telephone: this.userForm.value.telephone, mobile: this.userForm.value.mobile, fax: this.userForm.value.fax, address: this.userForm.value.address, post: this.userForm.value.post, status: this.userForm.value.status, token: "", ip: "", expired: this.userForm.value.expired }; // 发起create user action this.store.dispatch( new userActions.CreateUser(newUser) ) // 清空表单 this.userForm.reset() } } 6. 修改src/app/users/user-edit组件 修改src\app\users\user-edit\user-edit.component.html 更新用户 ....... 修改 修改src\app\users\user-edit\user-edit.component.ts import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, FormControl, Validators } from '@angular/forms'; import { Store } from '@ngrx/store'; import as userActions from '../state/user.actions'; import as fromUser from '../state/user.reducer'; import { User } from '../user.model'; import { Observable } from 'rxjs'; @Component({ selector: 'app-user-edit', templateUrl: './user-edit.component.html', styleUrls: ['./user-edit.component.css'] }) export class UserEditComponent implements OnInit { userForm: FormGroup; constructor( private fb: FormBuilder, // inject FormBuilder private store: Store ) { } ngOnInit(): void { // 创建表单, 注意id是null,这个id不在表单输出 this.userForm = this.fb.group({ id: null, username: new FormControl("", {validators: [Validators.required,]}), password: new FormControl("", {validators: [Validators.minLength(6), Validators.maxLength(32)]}), role_id: new FormControl("", {validators: [Validators.required]}), cnname: new FormControl("", {validators: [Validators.minLength(2), Validators.maxLength(6)]}), enname: new FormControl("", {validators: [Validators.minLength(2), Validators.maxLength(30)]}), email: new FormControl("", {validators: [Validators.email]}), telephone: new FormControl("", {validators:[Validators.minLength(10), Validators.maxLength(11)]}), mobile: new FormControl("", {validators:[Validators.minLength(10), Validators.maxLength(11)]}), fax: new FormControl("", {validators:[Validators.minLength(10), Validators.maxLength(11)]}), address: new FormControl("", {validators:[Validators.minLength(3), Validators.maxLength(50)]}), post: new FormControl("", {validators:[Validators.minLength(6), Validators.maxLength(8)]}), expired: new FormControl("", {validators:[Validators.minLength(19), Validators.maxLength(19)]}), status: new FormControl("", {validators:[Validators.min(0), Validators.max(1)]}), }) // 订阅当前选中的user const user$: Observable<User> = this.store.select( fromUser.getCurrentUser ) // 如果当前选中的user有变化，更新表单 user$.subscribe(currentUser => { if(currentUser){ console.log("user id := " + currentUser.id) this.userForm.patchValue({ id: currentUser.id, // userForm存储user id username: currentUser.username, password: currentUser.password, role_id: currentUser.role_id, cnname: currentUser.cnname, enname: currentUser.enname, email: currentUser.email, telephone: currentUser.telephone, mobile: currentUser.mobile, fax: currentUser.fax, address: currentUser.address, post: currentUser.post, expired: currentUser.expired, status: currentUser.status }) } }) } updateUser(){ console.log(this.userForm.value) console.log("更新用户") console.log("user id := " + this.userForm.value.id) // 构造user const newUser: User = { id: this.userForm.value.id, username: this.userForm.value.username, password: this.userForm.value.password, role_id: this.userForm.value.role_id, cnname: this.userForm.value.cnname, enname: this.userForm.value.enname, email: this.userForm.value.email, telephone: this.userForm.value.telephone, mobile: this.userForm.value.mobile, fax: this.userForm.value.fax, address: this.userForm.value.address, post: this.userForm.value.post, status: this.userForm.value.status, token: "", ip: "", expired: this.userForm.value.expired }; // 发出user update action this.store.dispatch( new userActions.UpdateUser(newUser) ) // 清空表单 this.userForm.reset() } } 7. 修改src/app/users/user-list组件 修改src\app\users\user-list\user-list.component.html - 加入修改user响应，editUser(user) - 加入删除user响应，deleteUser(user) Users 用户名 Email 角色 名字 电话 手机 传真 地址 邮编 状态 操作 {{error}} {{user.username}} {{user.email}} {{user.role_id}} {{user.cnname}}{{user.enname}} {{user.telephone}} {{user.mobile}} {{user.fax}} {{user.address}} {{user.post}} {{user.status}} 修改 删除 修改src\app\users\user-list\user-list.component.ts import { Component, OnInit } from '@angular/core'; import { Store,select } from '@ngrx/store'; import { Observable } from 'rxjs'; import { THIS_EXPR } from '@angular/compiler/src/output/output_ast'; import as userActions from '../state/user.actions'; import as fromUser from '../state/user.reducer'; import {User} from './../user.model'; import { compileNgModuleFromRender2 } from '@angular/compiler/src/render3/r3_module_compiler'; @Component({ selector: 'app-user-list', templateUrl: './user-list.component.html', styleUrls: ['./user-list.component.css'] }) export class UserListComponent implements OnInit { users$: Observable<User[]>; errors$: Observable; constructor(private store: Store) { } ngOnInit(): void { this.store.dispatch(new userActions.LoadUsers()); // 调用reducer的getUsers方法 this.users$ = this.store.pipe(select(fromUser.getUsers)); //this.store.subscribe(state => (this.users = state.users.users)); } // 编辑user editUser(user: User){ this.store.dispatch(new userActions.LoadUser(user.id)) } // 删除user deleteUser(user:User){ if(confirm("确定删除用户?")){ this.store.dispatch(new userActions.DeleteUser(user.id)) } } } ### 效果 ![11.png][1] ![12.png][2] [1]: http://blog.shyclouds.net/usr/uploads/2020/05/3083320597.png [2]: http://blog.shyclouds.net/usr/uploads/2020/05/4008506764.png
本篇要做什么 本文将使用effect entity库来管理数据集，减少CRUD操作代码量，通过id来查找user github代码：https://github.com/shyandsy/angular-9-ngrx-user-mamagement 安装ngrx entity npm install @ngrx/entity 原教程视频连接 https://www.youtube.com/watch?v=igc5rBN50wk&list=PLaMbwDs23r4KXoMucJEyUAvamQ-kFNBvC&index=7 步骤 修改src\app\users\state\user.reducer.ts import * as userAction from './user.actions'; import {createFeatureSelector, createSelector, Store} from '@ngrx/store'; // 加入 import {EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity'; import * as fromRoot from '../../state/app-state'; import { User } from '../user.model'; // 改用EntityState export interface UserState extends EntityState<User>{ selectedUserId: number | null; loading: boolean, loaded: boolean, error: string, } export interface AppState extends fromRoot.AppState{ users: UserState; } export const userAdapter: EntityAdapter<User> = createEntityAdapter<User>(); export const defaultUser: UserState = { ids: [], entities: {}, selectedUserId: null, loading: false, loaded: false, error: "" } export const initialState: UserState = userAdapter.getInitialState(defaultUser); export function userReducer(state = initialState, action: userAction.ACTION): UserState{ switch(action.type){ case userAction.UserActionTypes.LOAD_USERS:{ console.log("reducer: LOAD_USERS") return { ...state, loading: true, } } case userAction.UserActionTypes.LOAD_USERS_SUCESS:{ console.log("reducer: LOAD_USERS_SUCESS") return userAdapter.addAll(action.payload, { ...state, loading: false, loaded: true, }) } case userAction.UserActionTypes.LOAD_USERS_FAILED:{ console.log("reducer: LOAD_USERS_FAILED") return { ...state, entities: {}, loading: true, loaded: false, error: action.payload } } default: { return state; } } } const getUserFeatureState = createFeatureSelector<UserState>( "users" ) export const getUsers = createSelector( getUserFeatureState, userAdapter.getSelectors().selectAll ) 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 ) 解释 引入ng/entity import {EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity'; UserState改为继承EntityState，加入selectedUserId记录当前选中的User Id // 改用EntityState export interface UserState extends EntityState<User>{ selectedUserId: number | null; loading: boolean, loaded: boolean, error: string, } 创建userAdapter对象 export const userAdapter: EntityAdapter<User> = createEntityAdapter<User>(); 创建default user对象 export const defaultUser: UserState = { ids: [], // 对象ids entities: {}, selectedUserId: null, loading: false, loaded: false, error: "" } 改用userAdapter创建initialState export const initialState: UserState = userAdapter.getInitialState(defaultUser); 修改userReducer export function userReducer(state = initialState, action: userAction.ACTION): UserState{ switch(action.type){ case userAction.UserActionTypes.LOAD_USERS:{ console.log("reducer: LOAD_USERS") return { ...state, loading: true, } } case userAction.UserActionTypes.LOAD_USERS_SUCESS:{ console.log("reducer: LOAD_USERS_SUCESS") // 改用userAdapter的addAll方法 return userAdapter.addAll(action.payload, { ...state, loading: false, loaded: true, }) } case userAction.UserActionTypes.LOAD_USERS_FAILED:{ console.log("reducer: LOAD_USERS_FAILED") return { ...state, entities: {}, loading: true, loaded: false, error: action.payload } } default: { return state; } } } 修改getUsers方法，改用userAdapter.getSelector export const getUsers = createSelector( getUserFeatureState, userAdapter.getSelectors().selectAll ) 效果
angular 9打造用户系统 - 4使用ngrx effect从http api加载数据
本篇要做什么 本文将使用effect从http api加载数据来显示，user-list.component改用action对象发布事件 github代码：https://github.com/shyandsy/angular-9-ngrx-user-mamagement 原教程视频连接 https://www.youtube.com/watch?v=1dQVJ1DaFWU&list=PLaMbwDs23r4KXoMucJEyUAvamQ-kFNBvC&index=6 步骤 新建src\app\app.models.ts 通用的抽象response接口 export interface Response<T> { code: number, msg: string, data: T } 实现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); } } 添加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))) ) ) ) }); } 修改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 ) 修改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 { } 修改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 { } 修改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)); } } 修改src\app\users\user-list\user-list.component.html {{user.username}} {{user.role_id}} {{user.cnname}} {{user.enname}} {{user.email}} {{user.telephone}} {{user.mobile}} {{user.fax}} {{user.address}} {{user.post}} {{user.status}} 修改 删除 效果 遗留问题 文件：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 ], ...... })
angular 9打造用户系统 - 3 使用ngrx action和devtools
本篇要做什么 本文将在上一篇基础上增加ngrx/store-devtools和actions github代码：https://github.com/shyandsy/angular-9-ngrx-user-mamagement 上一篇已经安装依赖 npm install @ngrx/store @ngrx/effects @ngrx/entity @ngrx/store-devtools @ngrx/router-store 原教程视频连接 https://www.youtube.com/watch?v=1dQVJ1DaFWU&list=PLaMbwDs23r4KXoMucJEyUAvamQ-kFNBvC&index=5 步骤 浏览器安装redux devtools插件，我使用chrome浏览器 修改src\app\app.module.ts import { BrowserModule } from '@angular/platform-browser'; import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { StoreModule } from '@ngrx/store'; //加入这行，引用StoreDevtoolsModule import { StoreDevtoolsModule } from '@ngrx/store-devtools'; 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({}), // 使用StoreDevtoolsModule StoreDevtoolsModule.instrument(), AppRoutingModule, ], providers: [], bootstrap: [AppComponent], }) export class AppModule { } 现在我们刷新浏览器，打开chrome console的redux面板，可以看到所有的状态变动，方便调试 创建actions，src\app\users\state\user.actions.ts import { Action } from '@ngrx/store'; import { User } from '../user.model'; export enum UserActionTypes { LOAD_USERS = "[User] Load Users", LOAD_USERS_SUCESS = "[User] Load Users Success", LOAD_USERS_FAILED = "[User] Load Users Failed", } export class LoadUsers implements Action{ readonly type = UserActionTypes.LOAD_USERS; } export class LoadUsersSuccess implements Action{ readonly type = UserActionTypes.LOAD_USERS_SUCESS; constructor(public payload: User[]) {} } export class LoadUsersFailed implements Action{ readonly type = UserActionTypes.LOAD_USERS_FAILED; constructor(public payload: string) {} } export type ACTION = LoadUsers | LoadUsersSuccess | LoadUsersFailed; 创建src\app\state\app-state.ts export interface AppState{ } 重写我们的reducer，不提供初始数据 import * as userAction from './user.actions'; 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; } } }
angular 9打造用户系统 - 2使用ngrx store和reducer来加载用户列表
angular 9打造用户系统-2 本片使用ngrx store和ngrx reducer加载用户列表 github代码：https://github.com/shyandsy/angular-9-ngrx-user-mamagement 原教程视频连接 https://www.youtube.com/watch?v=ChiAZJiX3v0&list=PLaMbwDs23r4KXoMucJEyUAvamQ-kFNBvC&index=4 步骤 安装ngrx依赖 npm install @ngrx/store @ngrx/effects @ngrx/entity @ngrx/store-devtools @ngrx/router-store 创建第一个action和reducer 创建src\app\users\user.model.ts import { Time } from '@angular/common' export interface User{ id?: number; username: string; cnname: string; enname: string; password?: string; role_id: number; email: string; telephone: string; mobile: string; fax: string; address: string; post: string; token: string; expired: Time; status: number; ip: string; } 创建src\app\users\user.service.ts, 稍后实现 创建src\app\users\state文件夹 创建src\app\users\state\user.actions.ts 创建src\app\users\state\user.reducer.ts 修改src\app.module.ts //加入 import { StoreModule } from '@ngrx/store'; imports: [ BrowserModule, // 加入这行 StoreModule.forRoot({}), // 在module中添加reducer AppRoutingModule, ], 修改src\app\users\users.module.ts //加入 import { StoreModule } from '@ngrx/store'; import { userReducer } from './state/user.reducer'; imports: [ CommonModule, RouterModule.forChild(userRoutes), // 加入，给user module添加UserReducer StoreModule.forFeature("users", userReducer), ], 编写userReducer, src\app\users\state\user.reducer.ts import { formatDate } from '@angular/common'; // 定义一个initialState let date = new Date(); const initialState = { users: [ { username: "shyclouds", cnname: "李连杰", enname: "Jack Li", password: "", role_id: 1, email: "shyclouds@shyclouds.com", telephone: "01022334441", mobile: "13122334455", fax: "01022334442", address: "北京市长安街128号", post: "10001", token: "xxxxx", expired: new Date(), status: 1, ip: "127.0.0.1", } ], loading: false, loaded: true, } // 定义userReducer export function userReducer(state = initialState, action){ switch(action.type){ case "LOAD_USERS":{ return { ...state, loading: true, loaded: false, } } default: { return state; } } } 修改src\app\users\user-list\user-list.component.ts,加载数据 import { Component, OnInit } from '@angular/core'; // 加入这行 import { Store } from '@ngrx/store'; // 引入User model import {User} from './../user.model'; import { THIS_EXPR } from '@angular/compiler/src/output/output_ast'; @Component({ selector: 'app-user-list', templateUrl: './user-list.component.html', styleUrls: ['./user-list.component.css'] }) export class UserListComponent implements OnInit { //存储当前users users: User[]; // 注入store constructor(private store: Store<any>) { } ngOnInit(): void { // 加载users this.store.dispatch({type: 'LOAD_USERS'}); // 订阅state.users this.store.subscribe(state => (this.users = state.users.users)); } } 修改模板src\app\users\user-list\user-list.component.html Users 用户名 角色 中文名 英文名 Email 电话 手机 传真 地址 邮编 状态 操作 {{user.username}} {{user.role_id}} {{user.cnname}} {{user.enname}} {{user.email}} {{user.telephone}} {{user.mobile}} {{user.fax}} {{user.address}} {{user.post}} {{user.status}} 修改 删除 最终效果
angular 9打造用户系统 - 1搭建一个用户管理界面
做什么 本文使用angular搭建一个客户管理页面，包含添加客户，修改客户信息，客户列表。本文内容只涵盖展示页面，没有业务逻辑。业务逻辑将在下一篇中实现，最终效果在页面底部 github代码：https://github.com/shyandsy/angular-9-ngrx-user-mamagement 感谢教程原作者 https://www.youtube.com/watch?v=QlzX_gKixa4&list=PLaMbwDs23r4KXoMucJEyUAvamQ-kFNBvC&index=3 https://github.com/angulardeveloper-io/ngrx-store-app 步骤 创建项目,项目名user ng new user 加入bootstrap4的css,文件： User <app-root></app-root> 安装ngrx npm install @ngrx/store 修改src\app\app.module.ts import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; // 加入这行 import { StoreModule } from '@ngrx/store'; 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, // 加入这行 StorwModule.forRoot({}), AppRoutingModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { } 用vscode打开项目目录，创建所需要的model和component // 创建home module ng g c home // 创建navbar module ng g c navbar ng g m users // 创建user module ng g c users/user -m users // 创建user module --> user component ng g c users/user-add -m users // 创建user module --> user-add component ng g c users/user-edit -m users // 创建user module --> user-edit component ng g c users/user-list -m users // 创建user module --> user-list component 修改src\app\app-routing.module.ts import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { CommonModule } from '@angular/common'; // 1.加入这行 import { HomeComponent } from './home/home.component'; // 2.加入这行 const routes: Routes = [ { path: "", component: HomeComponent }, // 3.加入这行 { path: "users", loadChildren: "../app/users/users.module#UserModule" }, // 4.加入这行 ]; @NgModule({ imports: [ CommonModule, // 5.加入这行 RouterModule.forRoot(routes) ], exports: [RouterModule] }) export class AppRoutingModule { } 修改src\app\users\users.module.ts import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; // 加入这行 import { RouterModule, Routes } from "@angular/router"; // 加入这行 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 }]; @NgModule({ declarations: [ UserComponent, UserAddComponent, UserEditComponent, UserListComponent ], imports: [ CommonModule, // 加入这行 RouterModule.forChild(userRoutes) // 加入这行 ] }) export class UsersModule { } 修改src\app\app.component.html <app-navbar></app-navbar> <router-outlet></router-outlet> 修改src\app\home\home.component.html 欢迎 请选择 Users 修改src\app\navbar\navbar.component.html NGRX Users 修改src\app\users\user\user.component.html <app-user-add></app-user-add> <app-user-list></app-user-list> <app-user-edit></app-user-edit> 修改src\app\users\user-add\user-add.component.html 添加用户 用户名 密码 Membership 超级管理员 一级代理商 二级代理商 中文名 英文名 Email 电话 手机 传真 地址 邮编 状态 启用 禁用 添加 修改src\app\users\user-edit\user-edit.component.html 添加用户 用户名 密码 Membership 超级管理员 一级代理商 二级代理商 中文名 英文名 Email 电话 手机 传真 地址 邮编 状态 启用 禁用 修改 修改src\app\users\user-list\user-list.component.html Users 用户名 角色 中文名 英文名 Email 电话 手机 传真 地址 邮编 状态 操作 - - - - - - - - - - - 修改 删除 教程就结束了，运行。主页正常，/users页面报错 : Compiled successfully. ERROR in src/app/users/user/user.component.html:2:5 - error NG8001: 'app-user-add' is not a known element: 1. If 'app-user-add' is an Angular component, then verify that it is part of this module. 2. If 'app-user-add' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message. 2 <app-user-add></app-user-add> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src/app/users/user/user.component.ts:5:16 5 templateUrl: './user.component.html', ~~~~~~~~~~~~~~~~~~~~~~~ Error occurs in the template of component UserComponent. src/app/users/user/user.component.html:3:5 - error NG8001: 'app-user-list' is not a known element: 1. If 'app-user-list' is an Angular component, then verify that it is part of this module. 2. If 'app-user-list' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message. 3 <app-user-list></app-user-list> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src/app/users/user/user.component.ts:5:16 5 templateUrl: './user.component.html', ~~~~~~~~~~~~~~~~~~~~~~~ Error occurs in the template of component UserComponent. src/app/users/user/user.component.html:4:5 - error NG8001: 'app-user-edit' is not a known element: 1. If 'app-user-edit' is an Angular component, then verify that it is part of this module. 2. If 'app-user-edit' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message. 4 <app-user-edit></app-user-edit> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src/app/users/user/user.component.ts:5:16 5 templateUrl: './user.component.html', ~~~~~~~~~~~~~~~~~~~~~~~ Error occurs in the template of component UserComponent. 解决方法 修改src\app\app-routing.module.ts import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { Routes, RouterModule } from '@angular/router'; import { HomeComponent } from './home/home.component'; // 只需加入这一行 import { UsersModule } from './users/users.module'; const routes: Routes = [ { path: "", component: HomeComponent }, { path: "users", loadChildren: "../app/users/users.module#UsersModule" }, ]; @NgModule({ imports: [ CommonModule, RouterModule.forRoot(routes) ], exports: [RouterModule] }) export class AppRoutingModule { } 原作者视频中并没有加入这一行import，我不知道是不是因为angular 6升级到angular 9之后，用法变了。 暂时先这样吧，后续再去理解