In Angular (v12) I've got the component you can see below:
component.html
<h3 *ngIf="userInfo$ | async as userInfo; else loading">{{ userInfo.user.name }}</h3>
<ng-template #loading>
Loading...
</ng-template>
component.ts
token: string;
userInfo$: Observable<any>
getUserInfo: Subject<string> = new Subject<string>()
constructor(
private service: Service,
private route: ActivatedRoute
) { }
ngOnInit(): void {
this.userInfo$ = this.getUserInfo.pipe(
mergeMap(token => {
return this.service.getKey(token).pipe(
map(res => {
return {
user: res.client,
info: res.info
}
})
);
}),
tap((res) => console.log(res))
)
this.route.paramMap.subscribe(params => {
this.token = params.get("token");
this.getUserInfo.next(this.token);
});
}
Using the async
pipe the user'll get a perpetual loading, while if I this.getUserInfo.pipe(..).subscribe()
the right response is logged.
I know that the async
pipe subscribes and unsubscribes to the observables, so I expected the ngIf
to be truthful.
CodePudding user response:
You have an issue with timing. The problem is that this.getUserInfo.next(this.token)
emits before the async
pipe subscribes, so you don't receive the emission.
You can simplify your observable chain a bit which would side-step this timing issue.
@Component(...)
class MyComponent() {
private token$ = this.route.paramMap.pipe(
map(params => params.get('token')),
distinctUntilChanged()
);
userInfo$ = this.token$.pipe(
switchMap(token => this.service.getKey(token)),
map(res => ({
user: res.client,
info: res.info
}))
);
constructor(
private service: Service,
private route: ActivatedRoute
) { }
}
Notice the token
is defined as an observable and we defined userInfo$
as an observable that depends on the token$
emissions. There's no need for a separate Subject and a subscription. We also don't need to use ngOnInit
.