如何在NgRx中以组合方式使用Component Store?
可以用组合方式使用NgRx Component Store吗?当然可以
完全不需要通过extends继承Component Store,你可以通过依赖注入+组合的方式使用它,这也契合"组合优于继承"的设计原则。下面是两种常见的实现方式:
1. 在组件内直接组合使用
这种方式适合组件专属的状态管理,不需要复用状态逻辑的场景:
import { Component, OnInit } from '@angular/core'; import { ComponentStore } from '@ngrx/component-store'; import { Movie } from './movie.model'; import { of, delay, EMPTY } from 'rxjs'; import { tap, switchMap } from 'rxjs/operators'; // 定义状态类型 interface MoviesState { movies: Movie[]; loading: boolean; } @Component({ selector: 'app-movies-page', template: ` <div *ngIf="loading$ | async">加载中...</div> <ul> <li *ngFor="let movie of (movies$ | async)"> {{ movie.name }} <button (click)="removeMovie(movie.id)">删除</button> </li> </ul> <button (click)="addNewMovie()">添加测试电影</button> `, // 提供ComponentStore实例,作用域限定在当前组件 providers: [ComponentStore], }) export class MoviesPageComponent implements OnInit { // 注入ComponentStore实例,并指定状态类型 private readonly store = this.componentStore as ComponentStore<MoviesState>; // 选择器:获取状态片段 readonly movies$ = this.store.select(state => state.movies); readonly loading$ = this.store.select(state => state.loading); constructor(private readonly componentStore: ComponentStore<unknown>) {} ngOnInit() { // 初始化状态 this.store.setState({ movies: [], loading: false }); // 模拟加载数据 this.loadMovies(); } // 定义更新器:修改状态 private readonly addMovie = this.store.updater((state, movie: Movie) => ({ ...state, movies: [...state.movies, movie], })); private readonly removeMovie = this.store.updater((state, movieId: number) => ({ ...state, movies: state.movies.filter(m => m.id !== movieId), })); private readonly setLoading = this.store.updater((state, loading: boolean) => ({ ...state, loading, })); // 定义副作用:处理异步逻辑 private readonly loadMovies = this.store.effect<void>(() => { return this.store.select(() => true).pipe( tap(() => this.setLoading(true)), switchMap(() => { // 模拟API请求 return of([{ id: 1, name: '星际穿越' }, { id: 2, name: '盗梦空间' }]).pipe( delay(1000), tap(movies => { this.store.setState({ ...this.store.getState(), movies }); this.setLoading(false); }), catchError(() => { this.setLoading(false); return EMPTY; }) ); }) ); }); // 组件公开的方法,供模板调用 addNewMovie() { const newMovie: Movie = { id: Date.now(), name: `新电影${Date.now()}` }; this.addMovie(newMovie); } removeMovie(movieId: number) { this.removeMovie(movieId); } }
2. 封装成独立的服务(组合方式)
如果状态逻辑需要在多个组件间复用,可以把Component Store的逻辑封装成独立服务,无需继承,通过注入组合实现:
import { Injectable } from '@angular/core'; import { ComponentStore } from '@ngrx/component-store'; import { Movie } from './movie.model'; import { of, delay, EMPTY } from 'rxjs'; import { tap, switchMap } from 'rxjs/operators'; interface MoviesState { movies: Movie[]; loading: boolean; } @Injectable({ providedIn: 'root' }) export class MoviesStoreService { // 注入ComponentStore实例 private readonly store = this.componentStore as ComponentStore<MoviesState>; // 对外暴露的选择器 readonly movies$ = this.store.select(state => state.movies); readonly loading$ = this.store.select(state => state.loading); constructor(private readonly componentStore: ComponentStore<unknown>) { // 初始化状态 this.store.setState({ movies: [], loading: false }); } // 更新器 private readonly addMovie = this.store.updater((state, movie: Movie) => ({ ...state, movies: [...state.movies, movie], })); private readonly removeMovie = this.store.updater((state, movieId: number) => ({ ...state, movies: state.movies.filter(m => m.id !== movieId), })); private readonly setLoading = this.store.updater((state, loading: boolean) => ({ ...state, loading, })); // 副作用:加载数据 loadMovies() { this.store.effect<void>(() => { return of(true).pipe( tap(() => this.setLoading(true)), switchMap(() => { return of([{ id: 1, name: '星际穿越' }, { id: 2, name: '盗梦空间' }]).pipe( delay(1000), tap(movies => { this.store.setState({ ...this.store.getState(), movies }); this.setLoading(false); }), catchError(() => { this.setLoading(false); return EMPTY; }) ); }) ); })(); } // 对外公开的操作方法 addNewMovie(movie: Movie) { this.addMovie(movie); } deleteMovie(movieId: number) { this.removeMovie(movieId); } }
在组件中使用该服务:
import { Component, OnInit } from '@angular/core'; import { MoviesStoreService } from './movies-store.service'; import { Movie } from './movie.model'; @Component({ selector: 'app-movies-page', template: ` <div *ngIf="loading$ | async">加载中...</div> <ul> <li *ngFor="let movie of (movies$ | async)"> {{ movie.name }} <button (click)="deleteMovie(movie.id)">删除</button> </li> </ul> <button (click)="addTestMovie()">添加测试电影</button> `, }) export class MoviesPageComponent implements OnInit { readonly movies$ = this.moviesStore.movies$; readonly loading$ = this.moviesStore.loading$; constructor(private readonly moviesStore: MoviesStoreService) {} ngOnInit() { this.moviesStore.loadMovies(); } addTestMovie() { const newMovie: Movie = { id: Date.now(), name: `新电影${Date.now()}` }; this.moviesStore.addNewMovie(newMovie); } deleteMovie(movieId: number) { this.moviesStore.deleteMovie(movieId); } }
组合方式的优势
- 避免继承带来的层级耦合,代码更灵活
- 方便拆分状态逻辑,或在多个store间组合使用
- 契合Angular依赖注入理念,更易测试和维护
内容的提问来源于stack exchange,提问作者Jon Sud
相关产品推荐
相关产品推荐

