Asked By: Anonymous
I am pretty much a noob at rxjs and may be just using the wrong function completely. Essentially I am doing a stack of .NET Core 3.1 backend and mocking a two second delay. I am using that same service call twice to mock something similar to what I was doing on production code. So my code even though it is wiring up is pretty similar in the concept. I want a spinner to stop when BOTH service calls finish. I want them both to kick off simultaneously but I don’t care if they are an observable or an actual item and having the calls made simultaneously. No matter what I have tried I keep see the spinner stays on and the context of the setting of the ‘false’ keeps happening.
component:
import { Component, OnInit } from '@angular/core';
import { combineLatest, Observable, of } from 'rxjs';
import { Entry } from '../Models/entry.model';
import { TestService } from '../test.service';
@Component({
selector: 'app-Entry',
templateUrl: './Entry.component.html',
styleUrls: ['./Entry.component.scss']
})
export class EntryComponent implements OnInit {
entry$: Observable<Entry> = this.service.getItem();
entry2$: Observable<Entry> = this.service.getItem();
loading$: Observable<boolean> = of(true);
constructor(private readonly service: TestService) { }
ngOnInit() {
//my guess is this part is completely wrong for what I am trying to accomplish.
combineLatest([this.entry$, this.entry2$]).pipe(x => this.loading$ = of(false));
}
}
view:
<mat-spinner [hidden]="loading$ | async"></mat-spinner>
<p>
<em>{{entry$ | async | json}}</em>
</p>
<p>
<em>{{entry2$ | async | json}}</em>
</p>
Entire Project is on GitHub and public: https://github.com/djangojazz/AngularMaterial. You just run the API in Visual Studio and then boot up the Angular client with ‘npm run start’.
Solution
Answered By: Anonymous
You aren’t far off with the combineLatest
but you aren’t subscribing to it.
If your entry$
and entry$2
observables are 1-off observables then this approach might be simpler:
entry$: Observable<any> = of('entry$').pipe(delay(500));
entry2$: Observable<any> = of('entry2$').pipe(delay(5000));
combined$;
constructor() {}
ngOnInit() {
this.combined$ = combineLatest([this.entry$, this.entry2$]);
}
<div *ngIf="combined$ | async as combined; else loading">
Data loaded
{{combined[0]}}
{{combined[1]}}
</div>
<ng-template #loading>
<mat-spinner></mat-spinner>
</ng-template>
But if they constantly emit a value then you might want to subscribe to it because you’d want to show the lolading button everytime a new value emits
Also, showing the loading indicator is usually a side-effect and when side-effects are mentioned you should be using the tap
operator.
ngOnInit() {
combineLatest([this.entry$, this.entry2$])
.pipe(
tap(() => this.loading$.next(true)),
takeUntil(this.destroy$)
)
.subscribe(res => {
console.log(res);
this.data1 = res[0];
this.data2 = res[1];
this.loading$.next(false);
});
}