宣布 NgRx v17:介绍 NgRx 信号、操作符、性能改进、研讨会等!

2025-06-07

宣布 NgRx v17:介绍 NgRx 信号、操作符、性能改进、研讨会等!

我们很高兴地宣布 NgRx 框架的最新主要版本,其中包含一些令人兴奋的新功能、错误修复和其他更新。


介绍 NgRx 信号

此前,我们已为一款新的状态管理解决方案提交了一份RFC,该方案将为 Angular Signals 提供一流的响应式支持。我们很高兴能隆重推出这个@ngrx/signals库。

NgRx Signals 库完全基于 Angular Signals 构建,支持 RxJS 互操作,并包含开箱即用的实体管理功能。非常感谢Marko Stanimirović提出这个新库,并结合 NgRx 团队其他成员和社区的反馈不断构建和改进。Signals 库开启了使用 NgRx 和 Angular Signals 构建的新一代 Angular 应用。

入门

要安装该@ngrx/signals软件包,请使用您选择的软件包管理器:

npm install @ngrx/signals
Enter fullscreen mode Exit fullscreen mode

您还可以使用以下ng add命令:

ng add @ngrx/signals@latest
Enter fullscreen mode Exit fullscreen mode

定义状态

并非每个状态都需要单独的存储。针对此用例,@ngrx/signals我们提供了一个signalState实用函数,可以快速创建和操作小部分状态。它可以直接在组件类、服务或独立函数中使用。

import { Component } from '@angular/core';
import { signalState, patchState } from '@ngrx/signals';

@Component({
  selector: 'app-counter',
  standalone: true,
  template: `
    Count: {{ state.count() }} 

    <button (click)="increment()">Increment</button>
    <button (click)="decrement()">Decrement</button>
    <button (click)="reset()">Reset</button>
  `,
})
export class CounterComponent {
  state = signalState({ count: 0 });

  increment() {
    patchState(this.state, (state) => ({ count: state.count + 1 }));
  }

  decrement() {
    patchState(this.state, (state) => ({ count: state.count - 1 }));
  }

  reset() {
    patchState(this.state, { count: 0 });
  }
}
Enter fullscreen mode Exit fullscreen mode

实用patchState函数提供了一种类型安全的方式来对状态片段执行不可变的更新。

创建商店

为了管理具有更复杂状态的大型商店,您可以使用signalStore实用函数以及patchState和其他函数来管理状态。

import { computed } from '@angular/core';
import { signalStore, withState } from '@ngrx/signals';

export const CounterStore = signalStore(
  withState({ count: 0 })
);
Enter fullscreen mode Exit fullscreen mode

withState函数采用存储的初始状态并定义状态的形状。

导出计算值

计算属性也可以使用该函数从存储中现有的状态中派生出来withComputed

import { computed } from '@angular/core';
import { signalStore, patchState, withComputed } from '@ngrx/signals';

export const CounterStore = signalStore(
  withState({ count: 0 }),
  withComputed(({ count }) => ({
    doubleCount: computed(() => count() * 2),
  })),
);
Enter fullscreen mode Exit fullscreen mode

doubleCount属性作为存储的属性公开,可对 的更改做出反应count

定义存储方法

您还可以定义公开的方法,以使用明确定义的 API 对商店进行操作。

import { computed } from '@angular/core';
import { signalStore, patchState, withComputed, withMethods } from '@ngrx/signals';

export const CounterStore = signalStore(
  withState({ count: 0 }),
  withComputed(({ count }) => ({
    doubleCount: computed(() => count() * 2),
  })),
  withMethods(({ count, ...store }) => ({
    increment() {
      patchState(store, { count: count() + 1 });
    },
    decrement() {
      patchState(store, { count: count() - 1 });
    },
  }))
);
Enter fullscreen mode Exit fullscreen mode

定义生命周期钩子

您还可以创建在创建和销毁存储时调用的生命周期挂钩,以初始​​化获取数据、更新状态等。

import { computed } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { interval } from 'rxjs';
import {
  signalStore,
  withState,
  patchState,
  withComputed,
  withHooks,
  withMethods,
} from '@ngrx/signals';

export const CounterStore = signalStore(
  withState({ count: 0 }),
  withComputed(({ count }) => ({
    doubleCount: computed(() => count() * 2),
  })),
  withMethods(({ count, ...store }) => ({
    increment() {
      patchState(store, { count: count() + 1 });
    },
    decrement() {
      patchState(store, { count: count() - 1 });
    },
  })),
  withHooks({
    onInit({ increment }) {
      interval(2_000)
        .pipe(takeUntilDestroyed())
        .subscribe(() => increment());
    },
    onDestroy({ count }) {
      console.log('count on destroy', count());
    },
  }),
);
Enter fullscreen mode Exit fullscreen mode

在上面的例子中,onInit钩子订阅了一个间隔可观察对象,并调用incrementstore 上的方法每 2 秒增加一次计数。生命周期方法还可以访问注入上下文,以便使用 进行自动清理takeUntilDestroyed()

提供并注入 Store

要使用CounterStore,请将其添加到providers组件的数组中,然后使用依赖注入进行注入。

import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { CounterStore } from './counter.store';

@Component({
  selector: 'app-counter',
  standalone: true,
  template: `
    <h1>Counter (signalStore)</h1>

    <p>Count: {{ store.count() }}</p>
    <p>Double Count: {{ store.doubleCount() }}</p>

    <button (click)="store.increment()">Increment</button>
    <button (click)="store.decrement()">Decrement</button>
  `,
  providers: [CounterStore],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export default class CounterComponent {
  readonly store = inject(CounterStore);
}
Enter fullscreen mode Exit fullscreen mode

选择与 RxJS 互操作

RxJS 仍然是 NgRx 和 Angular 生态系统的重要组成部分,并且 NgRx Signals 包提供了可选用法,可以使用该rxMethod函数与 RxJS 可观察对象进行交互。

rxMethod函数允许您在 signalStore 上定义一种方法,该方法可以接收信号或可观察对象、读取其最新值并使用可观察对象执行其他操作。

import { inject } from '@angular/core';
import { debounceTime, distinctUntilChanged, pipe, switchMap, tap } from 'rxjs';
import {
  signalStore,
  patchState,
  withHooks,
  withMethods,
  withState,
} from '@ngrx/signals';
import { rxMethod } from '@ngrx/signals/rxjs-interop';
import { tapResponse } from '@ngrx/operators';
import { User } from './user.model';
import { UsersService } from './users.service';

type State = { users: User[]; isLoading: boolean; query: string };

const initialState: State = {
  users: [],
  isLoading: false,
  query: '',
};

export const UsersStore = signalStore(
  { providedIn: 'root' },
  withState(initialState),
  withMethods((store, usersService = inject(UsersService)) => ({
    updateQuery(query: string) {
      patchState(store, { query });
    },
    async loadAll() {
      patchState(store, { isLoading: true });
      const users = await usersService.getAll();
      patchState(store, { users, isLoading: false });
    },
    loadByQuery: rxMethod<string>(
      pipe(
        debounceTime(300),
        distinctUntilChanged(),
        tap(() => patchState(store, { isLoading: true })),
        switchMap((query) =>
          usersService.getByQuery(query).pipe(
            tapResponse({
              next: (users) => patchState(store, { users }),
              error: console.error,
              finalize: () => patchState(store, { isLoading: false }),
            }),
          ),
        ),
      ),
    ),
  })),
  withHooks({
    onInit({ loadByQuery, query }) {
      loadByQuery(query);
    },
  }),
);
Enter fullscreen mode Exit fullscreen mode

上面的例子UserStore使用rxMethod运算符创建了一个方法,该方法根据查询字符串在商店初始化时加载用户。

然后可以UsersStore在组件中使用它及其附加方法,提供一种干净、结构化的方式来使用信号管理状态,并结合 RxJS 可观察流的强大功能来实现异步行为。

import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { SearchBoxComponent } from './ui/search-box.component';
import { UserListComponent } from './ui/user-list.component';
import { UsersStore } from './users.store';

@Component({
  selector: 'app-users',
  standalone: true,
  imports: [SearchBoxComponent, UserListComponent],
  template: `
    <h1>Users (RxJS Integration)</h1>

    <app-search-box
      [query]="store.query()"
      (queryChange)="store.updateQuery($event)"
    />

    <app-user-list [users]="store.users()" [isLoading]="store.isLoading()" />
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export default class UsersComponent {
  readonly store = inject(UsersStore);
}
Enter fullscreen mode Exit fullscreen mode

@ngrx/signals软件包还包括管理实体、组成共享功能、共享全局存储的功能,并且可以扩展到许多不同的用例。

查看NgRx 文档以获取有关新@ngrx/signals包的更多示例和用法。

NgRx Signals 软件包目前处于开发者预览阶段,我们正在收集用户反馈并改进其 API。我们也在为 NgRx Signals 软件包征集一些 logo 设计灵感!欢迎查看未解决的问题并/或提供建议!

非常感谢Angular Architects 团队的Manfred SteyerRainer Hahnekamp对 NgRx Signals 库提供的宝贵反馈和演示。

NgRx 库的未来

随着 NgRx Signals 库的推出,您可能想知道其他 NgRx 库会发生什么。

NgRx Store 周围的生态系统继续很好地扩展到大型企业应用程序,并且我们将继续改进与 Signals 的集成,而不会破坏其自然工作流程。

NgRx ComponentStore 的构建是为了在 Angular Signals 推出之前,在本地层面填补 RxJS 在状态管理方面响应性和结构化的空白。它也被应用于企业应用,并持续提供价值。由于它存在时间更长,并且如今已久经考验,有些人会继续使用它。目前没有弃用它的计划。

NgRx Signals 是一种全新的响应式状态管理方法,其根源在于 ComponentStore API,并且支持 RxJS 的使用。它还提供了其他一些实用程序,可以结构化地使用 Angular Signals,帮助开发者进行扩展。因此,它并非 ComponentStore 的替代品。我们会看到越来越多的人开始使用 NgRx Signals,并提升其使用体验。它继承了我们多年来维护这些库的经验。

每个包在 NgRx 生态系统中都有其用途,并且将继续如此。

NgRx 研讨会🎓

随着 NgRx 的使用率与 Angular 的持续增长,许多开发者和团队仍然需要关于如何架构和构建企业级 Angular 应用的指导。我们非常高兴地宣布即将推出由 NgRx 团队直接提供的研讨会!

从一月份开始,我们将提供一到三场全天研讨会,涵盖从 NgRx 的基础知识到最高级的主题。无论您的团队是刚开始使用 NgRx,还是已经使用一段时间,都能在这些研讨会中学习到新的概念。

研讨会涵盖了使用 NgRx Store 和库的全局状态,以及使用 NgRx ComponentStore 和 NgRx Signals 管理本地状态。

请访问我们的研讨会页面,从即将举行的研讨会列表中进行报名。

新的 NgRx 操作符包

随着 NgRx 框架包的扩展,一些 RxJS 操作符逐渐出现在许多不同的领域。这些操作符位于tapResponseNgRx ComponentStore 包和concatLatestFromNgRx Effects 包中。

tapResponse操作符提供了一种简单且安全的方式处理 Observable 的响应。它强制处理错误情况,并确保即使发生错误,effect 仍会继续运行。

concatLatestFrom运算符的功能与 类似,但withLatestFrom有一个重要的区别 - 它惰性地评估所提供的 Observable 工厂。

我们引入这个包是为了向任何@ngrx/operatorsAngular 应用程序提供这些通用操作符。NgRx ComponentStore 和 NgRx Effects 包仍然提供这些操作符,因为它们是从该包重新导出的。@ngrx/operators

使用 NgRx StoreDevtools 获得更好的性能

NgRx StoreDevtools 提供了一种在使用 NgRx Store 时轻松调试、跟踪和检查整个应用程序操作流程的方法。以前,将 StoreDevtools 连接到 Redux Devtools Extension 时,此连接是在 zone.js 的 Angular 变更检测上下文中完成的。这可能会导致与 Redux Devtools 交互时出现额外的变更检测周期。

我们通过连接到 Angular Zone 上下文之外的 Redux Devtools 对此进行了改进,当一起使用 NgRx Store 和 NgRx StoreDevtools 时,为新应用程序和现有应用程序提供更好的性能。

作为 v17 升级的一部分,迁移将作为现有应用程序的选择运行:

对于基于模块的应用程序:

import { StoreDevtoolsModule } from '@ngrx/store-devtools';

@NgModule({
  imports: [
    StoreDevtoolsModule.instrument({
      connectInZone: true,
    }),
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

对于使用独立 API 的应用程序:

import { provideStoreDevtools } from '@ngrx/store-devtools';

bootstrapApplication(AppComponent, {
  providers: [
    provideStoreDevtools({
      maxAge: 25,
      logOnly: !isDevMode(),
      connectInZone: true
    }),
  ],
});
Enter fullscreen mode Exit fullscreen mode

connectInZone您可以通过从 StoreDevtools 选项中移除该属性,选择退出在 Angular 变更检测上下文中使用 zone.js 进行连接。对于新应用程序,这是默认行为。

感谢Artur Androsovych的贡献!

文档的黑暗模式😎

我们的文档中添加的另一个期待已久的功能是黑暗模式!

NgRx 文档暗模式

非常感谢Mateusz Stefańczyk的贡献!

维护模式下的 NgRx 数据

Ward Bell 和 John Papa 最初创建了 NgRx Data 包,目的是使用 NgRx Store、Effects 和 Entity 以更结构化的方式处理数据集合。虽然这个包多年来为开发者提供了一些价值,但它并没有像其他 NgRx 库那样持续发展,从而变得更具可扩展性。

随着新库和新模式的出现,我们决定将 NgRx Data 移至维护模式。

这意味着:

  • NgRx Data 不会在 v17 中被弃用。
  • 此软件包的新功能请求将不被接受。
  • NgRx Data 不适用于新项目和现有项目。

对于那些希望迁移到 NgRx Data 的用户,我们将推荐一些未来迁移的策略。


弃用和重大变更💥

此版本包含错误修复、弃用功能和重大变更。对于大多数弃用功能或重大变更,我们提供了迁移功能,该功能会在您将应用升级到最新版本时自动运行。

请参阅版本 17 迁移指南,获取有关迁移到最新版本的完整信息。完整的变更日志可在我们的 GitHub 代码库中找到。


升级到 NgRx 17

要开始使用 NgRx 17,请确保安装以下最低版本:

  • Angular 版本 17.x
  • Angular CLI 版本 17.x
  • TypeScript 版本 5.2.x
  • RxJS 版本 ^6.5.x 或 ^7.5.x

NgRx 支持使用 Angular CLIng update命令来更新你的 NgRx 软件包。要将软件包更新到最新版本,请运行以下命令:

ng update @ngrx/store
Enter fullscreen mode Exit fullscreen mode

如果您的项目使用@ngrx/component-store,但没有@ngrx/store,请运行以下命令:

ng update @ngrx/component-store
Enter fullscreen mode Exit fullscreen mode

Swag 商店和 Discord 服务器🦺

您可以通过我们的商店购买 NgRx 官方礼品!印有 NgRx 标志的 T 恤有多种尺码、材质和颜色可供选择。我们计划未来在商店中增加贴纸、磁铁等新品。立即访问我们的商店,获取您的 NgRx 礼品吧!

想要与 NgRx 社区新老成员互动的朋友们,欢迎加入我们的Discord 服务器。


为 NgRx 做贡献

我们始终致力于改进文档,并使其始终与 NgRx 框架用户保持同步。为了帮助我们,您可以开始为 NgRx 做出贡献。如果您不确定从哪里开始,请查看我们的贡献指南,并观看Jan-Niklas WortmannBrandon Roberts制作的介绍视频 ,它们可以帮助您入门。


感谢我们所有的贡献者和赞助商!

NgRx 始终是一个社区驱动的项目。设计、开发、文档和测试均在社区的帮助下完成。访问我们的社区贡献者页面,查看所有为该框架做出贡献的人员。

如果您有兴趣贡献代码,请访问我们的GitHub页面,浏览我们的未解决问题,其中一些问题已标记为专门针对新贡献者。我们还在 GitHub 上积极讨论新功能和增强功能。

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

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

在TwitterLinkedIn上关注我们,了解有关 NgRx 平台的最新更新。

文章来源:https://dev.to/ngrx/announcing-ngrx-v17-introducing-ngrx-signals-operators-performance-improvements-workshops-and-more-55e4
PREV
使用 CopilotKit 将 Copilot 功能集成到您的 React 应用程序中
NEXT
如何通过 8 个步骤将 VueJs 项目迁移到 NuxtJs。