Reputation: 125
I have a simple subject in MyComponent
:
private readonly selectedID = new Subject<number | null>();
that gets triggered through HTML input from another component:
@Input()
public set myID(value: number | null)
{
this.selectedID.next(value);
}
Every time a new value is given in it, I want to invoke an HTTP call defined in MyService
and display that object. To achieve this, I created a second observable that should map the ID
to a new observable of an HTTP call:
public readonly myModel$: Observable<IMyObject>;
public isLoading = false;
constructor(private myHttpService: MyHttpService, private myAlertService: MyAlertService)
{
this.myModel$ = this.selectedID.pipe(
tap(() => this.isLoading = true),
switchMap(id => id == null ? of(null) : this.myService.getMyModel(id)),
catchError(e =>
{
this.myAlertService.handleError(e);
return of(null);
}),
tap(() => this.isLoading = false));
}
With this myModel$
observable defined, I pass it through the HTML template in an async
-pipe that does the subscribing.
<ng-container *ngIf="myModel$ | async as m;">
{{ m | json }}
</ng-container>
<ng-container *ngIf="isLoading">
<i class="fas fa-spinner fa-spin" aria-hidden="true"></i>
</ng-container>
But the problem now is, the first time when my set myID
is called, it simply works (server throws an error and alertService
handles it, but that's not part of my problem). But when a new ID is given, myService.getMyModel
is not being called again which it should.
I tried different RxJS operators, also using the decision tree but to no avail. I think something else is wrong than a mere higher order operator but I cannot figure out exactly what.
I also logged the execution of the first tap
:
tap((value) => { this.isLoading = true; console.log('hit', value); ),
This also gets called only once, i.e. for every new ID
given, whether it be a real number or null
, it simply does not get logged any more after the first time.
Upvotes: 1
Views: 409
Reputation: 3399
(server throws an error and alertService handles it, but that's not part of my problem)
That's exactly your problem :)
catchError
doesn't magically restore the observable. The parent observable has thrown an error, it dies, and catchError
lets you stop the propagation of that error, handle it how you want and return another observable to be consumed by the ones below.
Your catchError
returns of(null)
, which means observers will receive null
followed by [complete]
. The subscription is closed and nothing else will happen.
You have an easy solution though: Move catchError
inside the switchMap
. This way, the observable that completes will be the one inside switchMap
, and the outer subscription won't close:
constructor(private myHttpService: MyHttpService, private myAlertService: MyAlertService)
{
this.myModel$ = this.selectedID.pipe(
tap(() => this.isLoading = true),
switchMap(id => id == null ? of(null) : this.myService.getMyModel(id).pipe(
catchError(e => {
this.myAlertService.handleError(e);
return of(null);
})
)),
tap(() => this.isLoading = false));
}
Upvotes: 1