服务中的 SignalStore
在基于信号的服务中保存应用状态
服务中的 SignalStore 是 CoddyKit 上的免费 Angular Academy 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Angular Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Angular Academy 课程共包含 4 节课。
作为状态的信号
Angular 的信号是具有响应性的值,在发生变化时会通知使用者。信号保存一个值,并会重新运行读取它的计算或模板。
将信号存储在 @Injectable 服务中,可以让多个组件共享同一份响应式状态。
最小信号存储
创建一个服务,并将状态保存在 signal() 中。将可写信号保持为私有,并公开一个只读视图。
import { Injectable, signal } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class CounterStore {
private _count = signal(0);
readonly count = this._count.asReadonly();
increment() { this._count.update(c => c + 1); }
}为什么使用 providedIn root
providedIn: 'root' 会让服务成为整个应用中的单例。每个注入该服务的组件都会共享完全相同的信号实例,因此状态始终保持同步。
读取信号
像调用函数一样调用信号,即可读取其值。在模板中,Angular 会跟踪读取操作,并在值发生变化时自动更新 DOM。
import { Component, inject } from '@angular/core';
import { CounterStore } from './counter.store';
@Component({
selector: 'app-counter',
template: '<button (click)="store.increment()">{{ store.count() }}</button>'
})
export class CounterComponent {
store = inject(CounterStore);
}asReadonly 保护状态
asReadonly() 会返回一个可读取但不可写入的信号。组件无法意外地对其调用 set() 或 update(),因此所有状态变更都会通过存储方法进行。
set 与 update
如果您已经直接得到下一个值,请使用 set(value)。如果下一个值取决于当前值,请使用 update(fn)。
private _user = signal<User | null>(null);
setUser(u: User) { this._user.set(u); }
clearUser() { this._user.set(null); }
touch() { this._user.update(u => u ? { ...u, seen: true } : u); }每个存储包含多个信号
一个存储可以包含多个信号,分别表示状态的不同部分。将每个可写信号保持为私有,并公开相应的只读信号。
private _items = signal<Item[]>([]);
private _loading = signal(false);
readonly items = this._items.asReadonly();
readonly loading = this._loading.asReadonly();方法封装逻辑
业务规则应位于存储方法中,而不是组件中。组件调用能够表达意图的方法,例如 addToCart(item),由存储决定状态如何变化。
addToCart(item: Item) {
this._items.update(list => [...list, item]);
}
removeFromCart(id: string) {
this._items.update(list => list.filter(i => i.id !== id));
}无需订阅
与基于 RxJS 的服务不同,信号存储不需要 subscribe(),也不需要手动取消订阅。在模板中读取信号即可建立响应性连接,而组件销毁时 Angular 会自动清理这些连接。
使用 inject() 注入
inject() 函数可以在构造函数、字段初始化器和工厂函数中使用。它是获取存储实例的现代方式。
export class CartComponent {
private store = inject(CartStore);
items = this.store.items; // read-only signal
}测试信号存储
信号存储就是普通类,因此测试很简单:创建实例,调用方法,然后同步读取信号值。无需异步处理。
it('increments', () => {
const store = new CounterStore();
store.increment();
expect(store.count()).toBe(1);
});快速检查
测试您对信号存储的理解。
回顾
您学会了构建基于信号的存储:将可写信号保持为私有,公开 asReadonly() 视图,在方法中通过 set/update 修改状态,并使用 inject() 注入单例。无需订阅,易于测试,还能共享响应式状态。
常见问题解答
「服务中的 SignalStore」课时是免费的吗?
是的 — 「服务中的 SignalStore」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Angular Academy 课程的其余内容,请升级到 CoddyKit PRO。 Angular Academy 课程共包含 4 节课。
「服务中的 SignalStore」这节课中我会学到什么?
在基于信号的服务中保存应用状态 你通过在浏览器中直接运行的动手代码来练习 Angular Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Angular Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Angular Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。
「服务中的 SignalStore」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Angular Academy 课中编写并运行代码吗?
能。每节 Angular Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 服务中的 SignalStore
- 使用 computed 派生状态
- 以不可变方式更新状态
- 连接信号与 RxJS