与Chau Tran合作。
迁移波向信号是真实的,状态管理库已开始增加支持以支持可观察到和信号。
这是我与Chau一起拍摄的,将可观察物和信号组合成一个。
小历史
Angular自开始以来就具有可观察到的东西,并且开发人员已使用并在其应用程序的几乎每个部分中都使用了它。
Angular的反应性与 rxjs (除Zone.js自动化更改检测)!
然后,信号ð来了!他们改变了一切!一般而言,我们看到模板的方式,改变角度的检测和反应性!
谁会想到角度建议在模板中调用功能?!
@Component({
template: `<div>Count: {{ count() }}</div>`
})
export class MyCmp {
count = signal(0);
}
(我做了大声笑!在这里阅读更多:It’s ok to use function calls in Angular templates!)
信号很棒!但是我们习惯了Angular的RXJS模式,我们的服务与RXJS主题,可观察者,操作员和其他所有内容相关联!
!示例方案
我有一个 GalleryComponent 从API中检索一些数据,它取决于ID(从路由参数检索)和全局表单过滤器。
@Component({
template: `
<div *ngIf="data$ | async as data">
{{ data | json }}
</div>
`
})
export class GalleryComponent {
private route = inject(ActivatedRoute);
private galleryService = inject(GalleryService);
private filterService = inject(GlobalFilterService);
galleryId$ = this.route.paramMap.pipe(map(p => p.get('id')!));
data$ = combineLatest([
this.filterService.filters$,
this.galleryId$
]).pipe(
switchMap(([filters, id]) =>
this.galleryService.getGalleryItems(id, filters)
)
);
favoritesCount$ = this.data$.pipe(map(data => getFavoritesCount(data)));
}
我想在模板中使用信号以提高性能,并且因为它们是未来吗?但是,我不想摆脱RXJ,我认为它们彼此补充!
角辅助功能
我要做的第一件事是使用toSignal
来包装我的data$
可观察。
data = toSignal(this.data$, { initialValue: [] });
现在,我可以直接在模板中使用我的数据作为信号,而无需异步管!伟大的!
因为favoritesCount$
仅取决于data$
,所以我也可以将其转换为信号。计算是完美的选择!
- favoritesCount$ = this.data$.pipe(map(data => getFavoritesCount(data)));
+ favoritesCount = computed(() => getFavoritesCount(this.data()));
完美!
我从BehaviorSubjects
或RxAngular
/ ComponentStore
更新我的GlobalFilterService
以使用信号!< / p>
,现在我无法在combineLatest
中使用我的filters
(现在是一个信号)!让我们将其转换回可观察的可观察,以便再次在combineLatest
中使用它!
让我们从Angular中使用toObservable
。
@Component()
export class GalleryComponent {
...
private filterService = inject(GlobalFilterService);
+ filters$ = toObservable(this.filterService.filters);
data$ = combineLatest([
- this.filterService.filters$,
+ this.filters$,
this.galleryId$
]).pipe(...);
}
直到现在!问题是,我需要在组件中添加输入!此输入将在API调用中使用!因此,我使用一个设置器和BehaviorSubject
创建它。
showStars$ = new BehaviorSubject(false);
@Input({ required: true }) set showStars(x: boolean) {
this.showStars$.next(x);
}
我们可以轻松地在combineLatest
中使用它,因为它只是可观察到的!但是我还需要在模板中使用它,所以我再次使用toSignal
将其转换!
showStars = toSignal(this.showStars$);
再次很棒!事实是Angular RFC向我们展示了一种新的输入可能会出现RFC;
因此,我们可能想在它们来时做准备,以便于对它们进行重构,因此我们可能想到的是,让Setter设置值将值设置为signal
而不是BehaviorSubject
,然后从那里将其转换为observable
。
我们得到了类似的东西:
showStars = signal(false);
showStars$ = toObservable(this.showStars);
@Input({ required: true }) set showStars(x: boolean) {
this.showStars.set(x);
}
一切看起来都像这样:
@Component()
export class GalleryComponent {
private route = inject(ActivatedRoute);
private galleryService = inject(GalleryService);
private filterService = inject(GlobalFilterService);
galleryId$ = this.route.paramMap.pipe(map(p => p.get('id')!));
filters$ = toObservable(this.filterService.filters);
showStars = signal(false);
showStars$ = toObservable(this.showStars);
@Input({ required: true }) set showStars(x: boolean) {
this.showStars.set(x);
}
data$ = combineLatest([
this.filters$,
this.galleryId$,
this.showStars$
]).pipe(
switchMap(([filters, id, showStars]) =>
this.galleryService.getGalleryItems(id, filters, showStars)
)
);
data = toSignal(this.data$, { initialValue: [] });
favoritesCount = computed(() => getFavoritesCount(this.data()));
}
所有这些来回的和决策困境,因为我们只需要将价值放入combineLatest
中,并为明天的角度做好准备。
如果我们有更好的替代方案,那也可以考虑信号?
Chau的首次镜头
Chau是我与新信号交谈的第一批开发人员之一,他真的很夸张!他经常考虑这些模式!并向我展示了他为将信号与可观察物相结合的解决方案。
用法(与初始示例不同)看起来像这样:
githubUsers = computed$(
this.query,
pipe(
debounceTime(500),
switchMap((query) => api.getUsers(query)),
startWith([])
)
);
// or with explicit initial value
readonly githubUsers = computed$(
this.query,
[], // initial value
pipe(
debounceTime(500),
switchMap((query) => api.getUsers(query))
)
);
computed$
是一个原始的,第一个参数可以是信号,可观察到的,并且第二个arg可以是 inationvalue 或一个管道操作员,我们可以通过所有RXJS操作员。操作员每次都会更改信号或可观察到的时发射!
在这里找到整个实现:
https://gist.github.com/eneajaho/dd74aeecb877069129e269f912e6e472
computed$
解决了什么?让我们重新评估我们拥有的和想要实现的目标:
- 我们希望我们的
githubUsers
成为Signal
,以便我们可以在没有AsyncPipe
的模板上使用它 - 我们有一个
query
,它是对搜索输入反应的Signal
。query
值用于调用用户的github api - 我们想签出我们称之为github api的速率
>请不要去创建
debounceSignal()
。使用RXJS进行异步操作。
@angular/core/rxjs-interop
提供了另外两个原语:toSignal()
和toObservable()
为我们解决这个问题
githubUsers = toSignal(
toObservable(this.query).pipe(
debounceTime(500),
switchMap((query) => api.getUsers(query))
),
{ initialValue: [] }
);
这种方法效果很好,但是toSignal
和toObservable
之间的来回来回可能很快。这就是computed$
的来源。但是它仅适用于单个来源。
我的镜头
因为我有多个具有不同类型的来源(信号和可观察到),所以我将Chau的计算并转换为最初的参数为一系列来源(这可能是信号或可观察到)。作为第一次修复打字稿类型,我认为它进展顺利!在此处找到实现。
https://gist.github.com/eneajaho/53c0eca983c1800c4df9a5517bdb07a3
如果我以之前的示例为例,并应用了更新的计算$(现在称为Comput From)方法,则看起来像这样:
@Component()
export class GalleryComponent {
private route = inject(ActivatedRoute);
private galleryService = inject(GalleryService);
private filterService = inject(GlobalFilterService);
galleryId$ = this.route.paramMap.pipe(map(p => p.get('id')!));
showStars = signal(false);
@Input({ required: true }) set showStars(x: boolean) {
this.showStars.set(x);
}
data = computedFrom(
[this.filterService.filters, this.showStars, this.galleryId$],
[], // initial value
pipe(
switchMap(([filters, showStars, id]) =>
this.galleryService.getGalleryItems(id, filters, showStars)
)
)
);
favoritesCount = computed(() => getFavoritesCount(this.data()));
}
正如我们所看到的,无需来回转换事物即可将信号和可观察到的信号组合在一起!
正如我之前说的,这是完全键入的。而且有一个接收器,如果我们不传递Intial值,我们的信号将为Signal<T | undefined>
类型,如果我们传递初始值,则它将是Signal<T>
类型。
我认为,在我们必须在模板或其他计算中使用此信号的情况下,这很重要(传递初始值),因为我们首先必须检查信号是否具有值。
favoritesCount
的示例:
favoritesCount = computed(() => {
const data = this.data();
if (data === undefined) return 0;
return getFavoritesCount(data);
});
乔的火箭筒
我带着解决方案回到乔,他最初喜欢它!并改善它!回来说我解决方案的缺陷是initialValue
。
因此,如果我不提供操作员,则计算的的Intial值将为类型。但是我们不想要那个,因为我们有价值,至少我们应该在数组中取回源的价值。
让我们以一个例子来更好地理解它:
// Signal with default value
const first = signal(1);
// Observable that emits first value synchronously
const second$ = of(1);
// Observable that emits first value asynchronously
const third$ = timer(5000);
const combined = computedFrom(
[first, second$, third$],
);
我的实现将返回Signal<any>
,它的价值将不确定! 不好吧?是的,当然。
我们至少想得到这样的东西:
const combined = computedFrom(
[first, second$, third$],
);
// typeof combined - Signal<[number, number, number]>
虽然另一个示例将返回Signal<number>
,并且值为0。
const combined = computedFrom(
[first, second$, third$],
0, // initial value
);
但是,Chau认为明确的initialValue
是多余的,因为:
- 信号已经具有初始值
- 可观察物可以同步发射值
他相信 Observables
,它发出值异步应具有其初始值明确定义定义。在上面的示例中,只有third$
源需要一个初始值。
const combined = computedFrom(
[first, second$, third$.pipe(startWith(0))],
pipe(map(([f, s, t]) => f + s + t))
);
// if we have only one operator we can write it directly like:
const combined = computedFrom(
[first, second$, third$.pipe(startWith(0))],
map(([f, s, t]) => f + s + t)
);
最后但并非最不重要的一点是,Chau的computedFrom()
版本也像combineLatest()
一样接受Dictionary
。在某些情况下,这是一个很大的好处。
const combined = computedFrom(
{
first: first,
second: second$,
third: third$.pipe(startWith(0))
},
map(({first, second, third}) => first + second + third)
);
在此处找到Chau的计算实现:https://gist.github.com/eneajaho/33a30bcf217c28b89c95517c07b94266
总结
我现在也喜欢Chau的解决方案!并且一直在某些地方使用它,这很棒!
*去尝试一下,在评论中让我们知道或鸣叫! ð *
有关信号的其他讨论,可观察物和Libs也会查看rxangular github repo。
- RFC: @rx-angular/state/signals - extended signal and new eventEmitter
- RFC: funtional @rx-angular/state - the new rxState function
谢谢阅读!
我在推文上发表了很多有关Angular的信息(最新新闻,信号,视频,播客,更新,RFC,拉动请求等等)。 ð
如果您想了解更多有关现代角度功能,例如独立的,信号,功能守卫,拦截器,SSR,水合作用,新注射方法,指令组成API,ngoptimatimizedImage,请随时查看我们的Modern Angular Workshop from Push-Based.ioð16ð
如果这篇文章对您很有趣并且有用,并且您想了解有关Angular的更多信息,请在@Enea_Jahollari或dev.to上跟随我。 ð
如果您愿意在buying me a coffee ☕️上支持我,我将不胜感激。 预先感谢您ð