如何在已集成NgRx与Facade模式的Angular项目中使用Component Store
在NgRx+Facade模式的Angular应用中结合Component Store的示例
下面是一个完整的示例,展示如何在已有NgRx全局状态和Facade的Angular应用里,用Component Store管理组件专属的局部状态,同时保持Facade作为全局状态入口的职责。
核心思路
- Facade:继续负责全局状态的读取、全局操作的分发(比如加载用户列表、更新全局用户数据),作为全局状态的统一入口。
- Component Store:接管组件专属的临时状态(比如表单编辑状态、弹窗显示/隐藏、局部筛选条件等),这些状态不需要共享到其他组件,也没必要放到全局Store里。
- 两者可以互相协作:Component Store可以监听Facade提供的全局状态,也可以调用Facade的方法完成全局操作。
示例代码
1. 全局用户Facade(已存在的NgRx部分)
假设你已经有一个管理用户全局状态的Facade:
// src/app/user/user.facade.ts import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; import { selectCurrentUser, loadUser, saveUser } from './user.state'; import { User } from '../models/user.model'; import { Observable } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class UserFacade { // 全局用户状态流 currentUser$: Observable<User | null> = this.store.select(selectCurrentUser); constructor(private store: Store) {} // 触发全局加载用户的action loadUser(userId: string): void { this.store.dispatch(loadUser({ userId })); } // 触发全局保存用户的action saveUser(userId: string, userData: Partial<User>): Observable<User> { // 这里假设你的NgRx effect已经处理了API请求,返回保存后的用户数据 this.store.dispatch(saveUser({ userId, userData })); return this.currentUser$; } }
2. 组件专属的Component Store
创建一个UserDetailStore,负责管理用户详情组件的局部状态:
// src/app/user/user-detail/user-detail.store.ts import { ComponentStore, tapResponse } from '@ngrx/component-store'; import { inject } from '@angular/core'; import { UserFacade } from '../user.facade'; import { User } from '../../models/user.model'; import { switchMap, catchError, tap } from 'rxjs/operators'; import { of, Observable } from 'rxjs'; // 组件局部状态的类型定义 interface UserDetailState { editForm: Partial<User>; // 编辑表单的临时数据 isEditing: boolean; // 是否处于编辑模式 saveError: string | null; // 保存失败的错误信息 } // 初始状态 const initialState: UserDetailState = { editForm: {}, isEditing: false, saveError: null, }; @Injectable() export class UserDetailStore extends ComponentStore<UserDetailState> { // 注入全局Facade private userFacade = inject(UserFacade); // 状态选择器:供组件订阅局部状态 readonly editForm$ = this.select(state => state.editForm); readonly isEditing$ = this.select(state => state.isEditing); readonly saveError$ = this.select(state => state.saveError); // Updater:切换编辑模式 readonly setEditing = this.updater((state, isEditing: boolean) => ({ ...state, isEditing, // 进入编辑模式时,同步当前用户数据到表单(如果还没初始化) ...(isEditing && !Object.keys(state.editForm).length ? { editForm: state.editForm } : {}), })); // Updater:更新表单单个字段 readonly updateFormField = this.updater((state, payload: { key: keyof User; value: any }) => ({ ...state, editForm: { ...state.editForm, [payload.key]: payload.value }, })); // Effect:初始化组件,从全局Store拉取用户数据同步到局部表单 readonly initUserDetail = this.effect((userId$: Observable<string>) => { return userId$.pipe( switchMap(userId => { // 先调用全局Facade加载用户 this.userFacade.loadUser(userId); // 订阅全局用户状态,同步到局部表单 return this.userFacade.currentUser$.pipe( tap(user => { if (user) { this.patchState({ editForm: { ...user } }); } }) ); }) ); }); // Effect:保存表单数据,调用全局Facade完成全局更新 readonly saveUser = this.effect((userId$: Observable<string>) => { return userId$.pipe( switchMap(userId => { this.patchState({ saveError: null }); const formData = this.get(state => state.editForm); // 调用全局Facade的保存方法 return this.userFacade.saveUser(userId, formData).pipe( tapResponse( () => this.setEditing(false), // 保存成功后退出编辑模式 (error: Error) => this.patchState({ saveError: error.message }) // 保存失败时记录错误 ) ); }), catchError(err => { this.patchState({ saveError: err.message }); return of(null); }) ); }); constructor() { super(initialState); } }
3. 用户详情组件
组件中同时注入Facade和Component Store,分别处理全局和局部逻辑:
// src/app/user/user-detail/user-detail.component.ts import { Component, OnInit, Input } from '@angular/core'; import { UserDetailStore } from './user-detail.store'; import { UserFacade } from '../user.facade'; import { FormBuilder, FormGroup } from '@angular/forms'; import { Observable } from 'rxjs'; import { User } from '../../models/user.model'; @Component({ selector: 'app-user-detail', templateUrl: './user-detail.component.html', providers: [UserDetailStore], // 组件级提供Store,确保每个组件实例有独立的局部状态 }) export class UserDetailComponent implements OnInit { @Input() userId!: string; // 订阅局部状态 editForm$: Observable<Partial<User>> = this.userDetailStore.editForm$; isEditing$: Observable<boolean> = this.userDetailStore.isEditing$; saveError$: Observable<string | null> = this.userDetailStore.saveError$; // 订阅全局用户状态 currentUser$: Observable<User | null> = this.userFacade.currentUser$; editForm: FormGroup; constructor( private userDetailStore: UserDetailStore, private userFacade: UserFacade, private fb: FormBuilder ) { // 初始化Angular表单 this.editForm = this.fb.group({ name: [''], email: [''], phone: [''], }); } ngOnInit(): void { // 初始化组件局部状态 this.userDetailStore.initUserDetail(this.userId); // 同步局部Store的表单状态到Angular表单 this.editForm$.subscribe(formData => { this.editForm.patchValue(formData); }); } startEditing(): void { this.userDetailStore.setEditing(true); } cancelEditing(): void { this.userDetailStore.setEditing(false); // 重置表单到全局用户数据 this.userDetailStore.initUserDetail(this.userId); } onFormInput(key: keyof User): void { const value = this.editForm.get(key)?.value; if (value !== undefined) { this.userDetailStore.updateFormField({ key, value }); } } saveChanges(): void { this.userDetailStore.saveUser(this.userId); } }
4. 组件模板
<!-- src/app/user/user-detail/user-detail.component.html --> <div *ngIf="currentUser$ | async as user" class="user-detail"> <h2>用户详情:{{ user.name }}</h2> <!-- 编辑模式 --> <div *ngIf="isEditing$ | async; else viewMode" class="edit-mode"> <form [formGroup]="editForm"> <div class="form-group"> <label>姓名</label> <input formControlName="name" (input)="onFormInput('name')"> </div> <div class="form-group"> <label>邮箱</label> <input formControlName="email" (input)="onFormInput('email')"> </div> <div class="form-group"> <label>电话</label> <input formControlName="phone" (input)="onFormInput('phone')"> </div> <button (click)="saveChanges()" class="btn btn-primary">保存</button> <button (click)="cancelEditing()" class="btn btn-secondary">取消</button> <div *ngIf="saveError$ | async as error" class="error-message"> 保存失败:{{ error }} </div> </form> </div> <!-- 查看模式 --> <ng-template #viewMode> <div class="view-mode"> <p><strong>邮箱:</strong>{{ user.email }}</p> <p><strong>电话:</strong>{{ user.phone }}</p> <button (click)="startEditing()" class="btn btn-primary">编辑</button> </div> </ng-template> </div>
关键注意点
- Component Store的作用域:通过在组件的
providers数组中声明UserDetailStore,确保每个组件实例拥有独立的局部状态,不会互相干扰。 - Facade与Component Store的协作:Component Store可以通过
inject获取Facade,监听全局状态或调用全局操作;Facade不需要知道Component Store的存在,保持单一职责。 - 状态分离:全局状态(比如当前登录用户、用户列表)放在NgRx全局Store,通过Facade暴露;组件临时状态(编辑模式、表单草稿)放在Component Store,避免污染全局状态。
内容的提问来源于stack exchange,提问作者user1938143
相关产品推荐
相关产品推荐

