NgRx Toolkit v21
Source: Dev.to
Toolkit 提供的功能
NgRx Toolkit 是一套在 Angular 应用中常用的丰富扩展。以下是其核心功能的简要历史:
| 功能 | 描述 |
|---|---|
withDevtools() | 允许任何 SignalStore(无论是否基于 Redux)使用 Redux DevTools。添加 withDevtools('storeName') 即可可视化 store 状态。 |
@ngrx/signals/event & withFeature | 现已成为 @ngrx/signals 核心的一部分;最初在 Toolkit 中孵化。 |
withStorageSync() | 将状态同步到 Web Storage(localStorage / sessionStorage)和 IndexedDB(通过异步策略)。使用 withStorageSync('storeName')。IndexedDB 支持是去年由社区贡献者(GitHub 用户 mzkmnk)通过 PR #134 添加的。 |
| 其他扩展 | 请参阅文档中的完整列表。 |
v20 次要特性:withResource、withEntityResources 与 Mutations
withResource() / withEntityResources()
withResource将 Angular 的 Resource API 与 store 关联,使 store 能够管理异步数据(例如从 API 加载)。它同时支持未命名和已命名两种变体。withEntityResources为使用@ngrx/signals/entities构建的 store 提供相同的功能。
Mutations API
Mutations API 为典型的 REST 交互添加了“写”操作。它提供:
- 独立函数:
httpMutation、rxMutation - 功能包装器:
withMutations
该设计受 Angular Query 与 Marko Stanimirović 提出的 Mutations API 启发,内部讨论中还涉及 Alex Rickabaugh。
import {
httpMutation,
rxMutation,
withMutations,
withResource,
withEntityResources,
} from '@angular-architects/ngrx-toolkit';
export const UserStore = signalStore(
withState({ userId: undefined as number | undefined }),
// Async data handling via Angular Resource API
withResource(({ userId }) => ({
detail: httpResource(() =>
userId === undefined ? undefined : `/user/${userId}`
),
})),
// Mutations (save, post, etc.)
withMutations((store, userService = inject(UserService)) => ({
saveUserDetail: rxMutation({
operation: (params: Params) =>
userService.saveUserDetail(store.counter(), params.value),
onSuccess: (result) => {
// …
},
onError: (error) => {
// …
},
}),
saveToServer: httpMutation({
request: () => ({
url: `https://httpbin.org/post`,
method: 'POST',
body: { counter: store.counter() },
}),
parse: (response) => response as UserResponse,
onSuccess: (result) => {
// …
},
onError: (error) => {
// …
},
}),
})),
// Entity‑level resources
withEntityResources(() =>
resource({
loader: () => Promise.resolve([] as User[]),
defaultValue: [],
})
)
);
v21 – 新功能
主要新增
- 改进的错误处理,适用于
withResource()和withEntityResources() - 事件集成 到 Redux DevTools
- 引入
clearUndoRedo(取代store.clearStack)
升级的 withResource() / withEntityResources() 错误处理
Angular 资源可能会进入死锁状态:一旦资源处于错误状态,更新 params 中的信号会触发 patchState,而 patchState 再次访问状态值,可能导致再次出错。
工具包现在提供了多种处理 withResource() 中错误的策略:
type ErrorHandlingStrategy =
| 'throw' // 重新抛出原始错误(默认)
| 'ignore' // 静默忽略错误并保留之前的状态
| 'fallback' // 通过用户提供的函数提供回退值
| 'custom' // 执行自定义错误处理回调
在配置资源时选择所需的策略:
withResource(
({ userId }) => ({
detail: httpResource(() =>
userId === undefined ? undefined : `/user/${userId}`
),
}),
{
errorHandling: 'fallback',
fallbackValue: () => ({ name: 'Anonymous', id: 0 })
}
);
事件集成到 DevTools
withDevtools() 现在会捕获从 store 发出的自定义事件,使你能够在 Redux DevTools UI 中与状态变化一起查看这些事件。
clearUndoRedo
之前的 store.clearStack() 方法已被 clearUndoRedo() 取代,后者更直观地表达了清除撤销和重做历史的目的。
参考文献
- 文档: (link to official docs)
- IndexedDB 贡献: (by mzkmnk)
- Angular Query(变更灵感): (link)
- Marko Stanimirović 的变更提案: (link)
错误处理策略
errorHandling = 'native' | 'undefined value' | 'previous value';
withResource(
(store) => {
const resolver = inject(AddressResolver);
return {
address: resource({
params: store.id,
loader: ({ params: id }) => resolver.resolve(id),
}),
};
},
// Other values: 'native' and 'previous value'
{ errorHandling: 'undefined value' } // default if not specified
);
选项
| 值 | 描述 |
|---|---|
'undefined value'(默认) | 当出现错误时,资源的值变为 undefined。 |
'previous value' | 如果资源之前已有值,则返回该值;否则抛出错误。 |
'native' | 不进行特殊处理——使用默认的错误行为。 |
对于 withEntityResources(),策略为 'undefined value'。
在内部实现中,'previous value' 和 'undefined value' 会对值进行代理。有关详细说明,请参阅 错误处理策略的 JSDoc。
将事件集成到 DevTools
这里有点讽刺意味:NgRx Toolkit 在官方插件出现之前就已经在 Signal Store 中引入了 events,并且它也提供了 Redux DevTools 集成(无论是否使用 Redux)。然而,现已正式的 NgRx events 功能并没有直接映射到 Toolkit 的 withDevtools。
在 NgRx Toolkit v21 中,我们通过 withTrackedReducer() 解决了这个问题,这是一种在 Redux DevTools 中跟踪基于 reducer 的状态变化的替代方式。
使用方法
-
将
withReducer的使用替换为withTrackedReducer。
(原生withReducer支持计划中,但需要在@ngrx/signals上游进行更改。) -
在
withDevtools内部指定withGlitchTracking。
如果在没有 DevTools 和 glitch tracking 的情况下使用withTrackedReducer,运行时将抛出错误。
import {
withTrackedReducer,
withGlitchTracking,
withDevtools,
} from '@angular-architects/ngrx-toolkit';
export const bookEvents = eventGroup({
source: 'Book Store',
events: {
loadBooks: type(),
},
});
const Store = signalStore(
{ providedIn: 'root' },
withDevtools('book-store-events', withGlitchTracking()),
withState({
books: [] as Book[],
}),
withTrackedReducer(
// `[Book Store] loadBooks` 将出现在 DevTools 中
on(bookEvents.loadBooks, () => ({
books: mockBooks,
}))
),
withHooks({
onInit() {
injectDispatch(bookEvents).loadBooks();
},
})
);
clearUndoRedo – 替代 store.clearStack
clearStack已 弃用。- 请改用全新的独立函数
clearUndoRedo。
clearUndoRedo 默认执行 软重置(不会将状态设为 null)。可以通过选项请求 硬重置:
clearUndoRedo(store, { lastRecord: null });
由 Gregor Woiwode 实现。已回移植至 NgRx Toolkit v19.5.0 和 v20.7.0。
ngrx-toolkit-openapi-gen
我们收到了来自 Murat Sari 的一份绝佳圣诞礼物:一个能够生成以下内容的 OpenAPI 生成器:
- ✅ NgRx Signal Store
- ✅ 资源(Resources)
- ✅ 变更(Mutations)
- ✅ 基于 Zod 架构的代码
生成的代码真的非常漂亮——在代码生成器中很少见到如此优雅的实现。
- npm: (link)
- Documentation: (link)
Future Release Strategy for Compatible Angular Versions
Toolkit 的第 21 版因新功能和修复而耗时更长。NgRx v21 与后续的 Toolkit v20 发行版兼容,但仅在某些覆盖的情况下。为提升体验,我们发布了 NgRx Toolkit v20.6.0,支持 NgRx v20 和 v21。同样适用于 v20.7.0,它向后移植了若干 v21 功能。
今后:
如果在任何即将到来的主要 Toolkit 发行版中出现障碍,我们将在下一个稳定的主要 NgRx 发行版准备好集成时,发布一个兼容的 次要 Toolkit 版本。
感谢!
我们感谢所有为此贡献时间、专业知识、讨论和代码的人。特别感谢本文中突出显示的贡献者:
你们的努力让 NgRx Toolkit 社区更加强大。 🙏