You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 07:35:38