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.
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
.