OnInit 和 async-pipe 之间的时序问题
Timing problem between OnInit and async-pipe
我在 async
管道与 Observable
结合使用时遇到问题,它在 OnInit
中获得第一个值。一定是时间问题,关于 OnInit
发生的时间点和呈现模板并因此订阅 Observable
的时间点。
考虑这个组件:
export class AppComponent implements OnInit {
subjectA$: Subject<{name:string}>;
subjectB$: Subject<{name:string}>;
constructor(
protected http: HttpClient
) {
}
ngOnInit() {
this.subjectA$ = new Subject<{name: string}>();
this.subjectA$.next({name: "A"});
this.subjectB$ = new Subject<{name: string}>();
setTimeout(() => {
this.subjectB$.next({name: "B"});
}, 0);
}
}
和模板:
<p *ngIf='subjectA$ | async as subjectA; else: nosubjectA'>
subjectA: {{subjectA.name}}
</p>
<ng-template #nosubjectA>
<p>no subjectA</p>
</ng-template>
<p *ngIf='subjectB$ | async as subjectB; else: nosubjectB'>
subjectB: {{subjectB.name}}
</p>
<ng-template #nosubjectB>
<p>no subjectB</p>
</ng-template>
这导致
no subjectA
subjectB: B
这意味着:即使 subjectA$
在 onInit
中得到一个值,视图也不会更新。如果我在 setTimeout
中环绕创建第一个值,如您在 subjectB$
中看到的那样,它会起作用并且我会看到该值。虽然这是一个解决方案,但我想知道 为什么会这样,是否有更好的解决方案?
我已经找到的一个解决方案是使用 BehaviorSubject
而不是提供第一个值作为初始值:
this.subjectC$ = new BehaviorSubject<{name: string}>({name: "C"});
使用主题 C 的类似模板导致 subjectC: C
。
我真正的可观察值根本不是 Subject
,而是 combineLatest
调用不同内容的结果,其中只有一个是(不幸的是,因为它使用的值来自@Input()
-annotation) a Subject
,并在 OnInit
中使用 next
手动推送,如示例所示。其余来自 http
等人。很可能我可以将组合结果包装在 BehaviourSubject
中,但对我来说它看起来很丑陋和危险,所以它比 setTimeout
方法更糟糕。但我敢打赌有人可以帮助我找到真正有用的解决方案。
此外,我宁愿避免BehaviorSubject
,以防止开发人员被诱惑使用getValue
。
一个快速解决方法是使用 ReplaySubject
和缓冲区 1 而不是 BehaviorSubject
。您不必提供默认值,它既没有 getValue()
功能,也没有 value
getter。然而它缓冲(或保存)最后发出的值并在新订阅时立即发出它。
尝试以下方法
ngOnInit() {
this.subjectA$ = new ReplaySubject<{name: string}>(1);
this.subjectA$.next({name: "A"});
this.subjectB$ = new ReplaySubject<{name: string}>(1);
this.subjectB$.next({name: "B"});
this.subjectC$ = combineLatest([this.subjectA$, this.subjectB$]).pipe(
map((things: [{name:string}, {name:string}]): {name:string} => {
return {name: things.map(thing => thing.name).join('|')}
})
);
}
我修改了你的Stackblitz。
发表评论后,我真的忍不住想一定有更好的方法 - 终于想到了一些有效的方法!
我刚刚稍微修改了你的 stackblitz。
private valueA = "A";
private valueB = "B";
subjectA$ = of({ name: this.valueA });
subjectB$ = of({ name: this.valueB });
subjectC$ = combineLatest([this.subjectA$, this.subjectB$])
.pipe(
map((things: [{name:string}, {name:string}]): {name:string} => {return {name: things.map(x => x.name).join('|')}})
);
这样,我们甚至可以丢弃 ngOnInit
钩子,一切正常!
我在 async
管道与 Observable
结合使用时遇到问题,它在 OnInit
中获得第一个值。一定是时间问题,关于 OnInit
发生的时间点和呈现模板并因此订阅 Observable
的时间点。
考虑这个组件:
export class AppComponent implements OnInit {
subjectA$: Subject<{name:string}>;
subjectB$: Subject<{name:string}>;
constructor(
protected http: HttpClient
) {
}
ngOnInit() {
this.subjectA$ = new Subject<{name: string}>();
this.subjectA$.next({name: "A"});
this.subjectB$ = new Subject<{name: string}>();
setTimeout(() => {
this.subjectB$.next({name: "B"});
}, 0);
}
}
和模板:
<p *ngIf='subjectA$ | async as subjectA; else: nosubjectA'>
subjectA: {{subjectA.name}}
</p>
<ng-template #nosubjectA>
<p>no subjectA</p>
</ng-template>
<p *ngIf='subjectB$ | async as subjectB; else: nosubjectB'>
subjectB: {{subjectB.name}}
</p>
<ng-template #nosubjectB>
<p>no subjectB</p>
</ng-template>
这导致
no subjectA
subjectB: B
这意味着:即使 subjectA$
在 onInit
中得到一个值,视图也不会更新。如果我在 setTimeout
中环绕创建第一个值,如您在 subjectB$
中看到的那样,它会起作用并且我会看到该值。虽然这是一个解决方案,但我想知道 为什么会这样,是否有更好的解决方案?
我已经找到的一个解决方案是使用 BehaviorSubject
而不是提供第一个值作为初始值:
this.subjectC$ = new BehaviorSubject<{name: string}>({name: "C"});
使用主题 C 的类似模板导致 subjectC: C
。
我真正的可观察值根本不是 Subject
,而是 combineLatest
调用不同内容的结果,其中只有一个是(不幸的是,因为它使用的值来自@Input()
-annotation) a Subject
,并在 OnInit
中使用 next
手动推送,如示例所示。其余来自 http
等人。很可能我可以将组合结果包装在 BehaviourSubject
中,但对我来说它看起来很丑陋和危险,所以它比 setTimeout
方法更糟糕。但我敢打赌有人可以帮助我找到真正有用的解决方案。
此外,我宁愿避免BehaviorSubject
,以防止开发人员被诱惑使用getValue
。
一个快速解决方法是使用 ReplaySubject
和缓冲区 1 而不是 BehaviorSubject
。您不必提供默认值,它既没有 getValue()
功能,也没有 value
getter。然而它缓冲(或保存)最后发出的值并在新订阅时立即发出它。
尝试以下方法
ngOnInit() {
this.subjectA$ = new ReplaySubject<{name: string}>(1);
this.subjectA$.next({name: "A"});
this.subjectB$ = new ReplaySubject<{name: string}>(1);
this.subjectB$.next({name: "B"});
this.subjectC$ = combineLatest([this.subjectA$, this.subjectB$]).pipe(
map((things: [{name:string}, {name:string}]): {name:string} => {
return {name: things.map(thing => thing.name).join('|')}
})
);
}
我修改了你的Stackblitz。
发表评论后,我真的忍不住想一定有更好的方法 - 终于想到了一些有效的方法!
我刚刚稍微修改了你的 stackblitz。
private valueA = "A";
private valueB = "B";
subjectA$ = of({ name: this.valueA });
subjectB$ = of({ name: this.valueB });
subjectC$ = combineLatest([this.subjectA$, this.subjectB$])
.pipe(
map((things: [{name:string}, {name:string}]): {name:string} => {return {name: things.map(x => x.name).join('|')}})
);
这样,我们甚至可以丢弃 ngOnInit
钩子,一切正常!