发布于 2026-01-06 1 阅读
0

NgRx SignalStore 的 Events 插件发布:Flux 架构的现代演绎;DEV 的全球展示挑战赛,由 Mux 呈现:展示你的项目!

NgRx SignalStore 的事件公告插件:Flux 架构的现代诠释

由 Mux 赞助的 DEV 全球展示挑战赛:展示你的项目!

我们很高兴地宣布推出 NgRx SignalStore 的 Events 插件,该插件已作为 NgRx v19.2 的一部分发布!这一新增功能为 SignalStore 带来了基于事件的状态管理功能,使开发人员能够更有效地创建可扩展的应用程序。


实验阶段🚧

Events 插件目前处于实验阶段。这意味着其 API 可能会发生变化,并且在版本稳定之前,未来的版本可能会进行一些修改,而不会发布标准的重大变更通知。我们鼓励您试用该插件,提供反馈,并帮助我们塑造它的未来!


Flux架构🔄

该插件从最初的 Flux 架构中汲取灵感,并融合了 NgRx Store、NgRx Effects 和 RxJS 的最佳实践和模式。

带有事件插件的应用程序架构

带有 Events 插件的应用程序架构由以下构建模块组成:

  1. 事件:描述系统内发生的事件。事件被分发以触发状态更改和/或副作用。
  2. 调度器:一个事件总线,它将事件转发给商店中相应的处理程序。
  3. Store:包含用于管理状态和处理副作用的 reducer 和 effects,从而保持清晰可预测的应用程序流程。
  4. 视图:反映状态变化并分发新事件,从而实现用户界面与底层系统之间的持续交互。

通过分发事件并对事件做出反应,事件的发生(事件本身)与事件的发生方式(状态变化或由此产生的副作用)解耦,从而实现可预测的数据流和更易于维护的代码。


攻略🛠️

Events 插件提供了一组 API,用于以响应式和声明式的方式定义和处理事件。以下示例演示了如何使用这些 API 通过事件实现状态和行为编排。

定义事件

事件是使用该eventGroup函数创建的,该函数将相关事件组织到同一来源下。此函数接受一个包含两个属性的对象:

  • source:标识事件组的来源(例如'Users Page',,'Users API')。
  • events:一个命名事件创建者的字典,其中每个键定义事件名称,每个值使用type核心@ngrx/signals包中的辅助函数定义有效负载类型。
import { type } from '@ngrx/signals';
import { eventGroup } from '@ngrx/signals/events';

export const usersPageEvents = eventGroup({
  source: 'Users Page',
  events: {
    opened: type<void>(),
    refreshed: type<void>(),
  },
});

export const usersApiEvents = eventGroup({
  source: 'Users API',
  events: {
    loadedSuccess: type<User[]>(),
    loadedFailure: type<string>(),
  },
});
Enter fullscreen mode Exit fullscreen mode

执行状态更改

为了处理事件引起的状态变化,SignalStore 提供了相应的withReducer功能。Case reducer 通过函数定义on,该函数将一个或多个事件映射到一个 case reducer 函数。每个 case reducer 接收事件并返回状态更新。它支持三种返回值形式:部分状态对象、部分状态更新器以及部分状态对象和/或更新器的数组。

import { signalStore } from '@ngrx/signals';
import { setAllEntities, withEntities } from '@ngrx/signals/entities';
import { on, withReducer } from '@ngrx/signals/events';

export const UsersStore = signalStore(
  { providedIn: 'root' },
  withEntities<User>(),
  withRequestStatus(),
  withReducer(
    on(usersPageEvents.opened, usersPageEvents.refreshed, setPending),
    on(usersApiEvents.loadedSuccess, ({ payload }) => [
      setAllEntities(payload),
      setFulfilled(),
    ]),
    on(usersApiEvents.loadedFailure, ({ payload }) => setError(payload)),
  ),
);
Enter fullscreen mode Exit fullscreen mode

💡 该功能的实现withRequestStatus及其状态更新器可在SignalStore 官方文档中找到。


副作用

副作用通过该withEffects特性进行处理。该特性接受一个函数,该函数接收 store 实例作为参数,并返回一个副作用字典。每个副作用都被定义为一个可观察对象,它会对Events服务中的特定事件做出反应。如果某个副作用发出新事件,该事件将被自动分发。

import { Events, withEffects } from '@ngrx/signals/events';
import { mapResponse } from '@ngrx/operators';

export const UsersStore = signalStore(
  /* ... */
  withEffects(
    (
      store,
      events = inject(Events),
      usersService = inject(UsersService),
    ) => ({
      loadUsers$: events
        .on(usersPageEvents.opened, usersPageEvents.refreshed)
        .pipe(
          exhaustMap(() =>
            usersService.getAll().pipe(
              mapResponse({
                next: (users) => usersApiEvents.loadedSuccess(users),
                error: (error: { message: string }) =>
                  usersApiEvents.loadedFailure(error.message),
              }),
            ),
          ),
        ),
      logError$: events
        .on(usersApiEvents.loadedFailure)
        .pipe(tap(({ payload }) => console.log(payload))),
    }),
  ),
);
Enter fullscreen mode Exit fullscreen mode

雷丁州

该插件不会改变状态的暴露或使用方式。因此,组件可以通过使用 store 实例来访问状态和计算信号。

@Component({
  selector: 'app-users',
  template: `
    <h1>Users</h1>

    @if (usersStore.isPending()) {
      <p>Loading...</p>
    }

    <ul>
      @for (user of usersStore.entities(); track user.id) {
        <li>{{ user.name }}</li>
      }
    </ul>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UsersComponent {
  readonly usersStore = inject(UsersStore);
}
Enter fullscreen mode Exit fullscreen mode

调度事件

要触发状态变更或副作用,可以使用该Dispatcher服务分发事件。它提供了一个dispatch以事件作为输入的方法。

import { Dispatcher } from '@ngrx/signals/events';

@Component({
  /* ... */
  template: `
    <h1>Users</h1>

    <button (click)="refresh()">Refresh</button>
  `,
})
export class UsersComponent {
  readonly usersStore = inject(UsersStore);
  readonly dispatcher = inject(Dispatcher);

  constructor() {
    this.dispatcher.dispatch(usersPageEvents.opened());
  }

  refresh(): void {
    this.dispatcher.dispatch(usersPageEvents.refreshed());
  }
}
Enter fullscreen mode Exit fullscreen mode

为了方便起见,可以使用该injectDispatch函数生成自分发事件,该函数返回一个对象,该对象具有与提供的事件创建器匹配的方法。

import { injectDispatch } from '@ngrx/signals/events';

@Component({
  /* ... */
  template: `
    <h1>Users</h1>

    <button (click)="dispatch.refreshed()">Refresh</button>
  `,
})
export class UsersComponent {
  readonly usersStore = inject(UsersStore);
  readonly dispatch = injectDispatch(usersPageEvents);

  constructor() {
    this.dispatch.opened();
  }
}
Enter fullscreen mode Exit fullscreen mode

扩大规模

随着应用程序复杂性的增加,可以将 reducer 和 effect 提取到独立的功能中,以提高可维护性。

提取还原剂

export function withUsersReducer() {
  return signalStoreFeature(
    { state: type<EntityState<User> & RequestStatusState>() },
    withReducer(
      on(usersPageEvents.opened, usersPageEvents.refreshed, setPending),
      on(usersApiEvents.loadedSuccess, ({ payload }) => [
        setAllEntities(payload),
        setFulfilled(),
      ]),
      on(usersApiEvents.loadedFailure, ({ payload }) => setError(payload)),
    ),
  );
}
Enter fullscreen mode Exit fullscreen mode

提取效应

export function withUsersEffects() {
  return signalStoreFeature(
    withEffects(
      (
        store,
        events = inject(Events),
        usersService = inject(UsersService),
      ) => ({
        loadUsers$: events
          .on(usersPageEvents.opened, usersPageEvents.refreshed)
          .pipe(
            exhaustMap(() =>
              usersService.getAll().pipe(
                mapResponse({
                  next: (users) => usersApiEvents.loadedSuccess(users),
                  error: (error: { message: string }) =>
                    usersApiEvents.loadedFailure(error.message),
                }),
              ),
            ),
          ),
        logError$: events
          .on(usersApiEvents.loadedFailure)
          .pipe(tap(({ payload }) => console.log(payload))),
      }),
    ),
  );
}
Enter fullscreen mode Exit fullscreen mode

最终商店组成

export const UsersStore = signalStore(
  { providedIn: 'root' },
  withEntities<User>(),
  withRequestStatus(),
  withUsersReducer(),
  withUsersEffects(),
);
Enter fullscreen mode Exit fullscreen mode

要点总结🔑

事件插件:

  • 结合了 Flux、NgRx Store、NgRx Effects 和 RxJS 中经过验证的模式。
  • 与现有 SignalStore 功能无缝集成。
  • 通过强大的自定义选项扩展 Flux 架构。
  • 通过单一方法统一管理本地和全局状态。

即将举办的 NgRx 工作坊🎓

随着 NgRx 在 Angular 中的应用日益广泛,许多开发者和团队仍然需要关于如何架构和构建企业级 Angular 应用的指导。我们很高兴地宣布,NgRx 团队即将推出一系列研讨会!

我们提供一到三个全天的 NgRx 工作坊,涵盖从基础知识到高级主题的所有内容。无论您的团队是刚刚开始使用 NgRx 还是已经使用了一段时间,我们都保证他们能从这些工作坊中学习到新的概念。

请访问我们的工作坊页面了解更多详情。下一场工作坊安排如下:

  • 6月11日至13日(欧盟时间)|注册
  • 9月24日至26日(美国时间)|注册
  • 10月8日至10日(欧洲时间)|注册

感谢所有贡献者和赞助商!🏆

NgRx 仍然是一个社区驱动的项目。设计、开发、文档编写和测试——所有这些都离不开社区的帮助。请访问我们的社区贡献者页面,查看所有为该框架做出贡献的人。

如果您有兴趣贡献代码,请访问我们的GitHub 页面,查看我们待解决的问题,其中一些问题专门针对新贡献者。我们还在 GitHub 上设有活跃的讨论区,用于讨论新功能和改进建议。

我们要衷心感谢我们的黄金赞助商Nx!Nx 长期以来一直推广 NgRx 作为构建 Angular 应用程序的工具,并致力于支持他们所依赖的开源项目。

我们要感谢我们的铜牌赞助商House of Angular

最后,我们还要感谢那些一次性或每月捐款的个人赞助商。


赞助 NgRx 🤝

如果您有兴趣赞助 NgRx 的持续开发,请访问我们的GitHub Sponsors 页面了解不同的赞助选项,或直接联系我们讨论其他赞助机会。

请在TwitterLinkedIn上关注我们,获取有关 NgRx 平台的最新动态。

文章来源:https://dev.to/ngrx/announcing-events-plugin-for-ngrx-signalstore-a-modern-take-on-flux-architecture-4dhn