Reputation: 1677
I'm doing a web application in Angular 8 and I want to show a loading spinner while doing a HTTP request.
The loading spinner is not showing with my implementation and I could not find the reason.
Service
@Injectable()
export class UserService {
// Caching
private bsResource: BehaviorSubject<string[]> = new BehaviorSubject([]);
private readonly resources$: Observable<string[]> = this.bsResource.asObservable();
constructor(
private http: HttpClient
) {
}
// Calling this method the loading spinner is not showing.
getResources(): Observable<string[]> {
if (this.bsResource.getValue().length === 0) {
this.fetchResourceList().toPromise().then(res => this.bsResource.next(res));
}
return this.resources$;
}
// The loading spinner appears if I use this method directly and make this method public.
private fetchResourceList(): Observable<string[]> {
return this.http.get<string[]>('MY_URL');
}
}
Component:
@Component({
templateUrl: './create-new.component.html',
styleUrls: ['./create-new.component.scss']
})
export class CreateNewComponent {
resourceList$: Observable<string[]> = this.service.getResources();
constructor(
private service: UserService
) { }
}
Template
<div class="form-group">
<label for="resourceSelect">* Resources</label>
<div *ngIf="resourceList$ | async as resourceList; else loading">
<ng-container *ngIf="resourceList.length; else noResults" >
<div *ngFor="let r of resourceList; index as i">
<!-- Show the result using checkboxes -->
</div>
</ng-container>
</div>
</div>
<ng-template #loading>
<br/>
<div class="spinner-border spinner-border-sm text-muted" role="status">
<span class="sr-only">Loading...</span>
</div>
</ng-template>
<ng-template #noResults>
<div class="text-muted">No results.</div>
</ng-template>
I don't understand why by using this.service.getResources()
the loading spinner is not showing and if I use this.service.fetchResourceList()
the loading spinner appears correctly.
My goal is to show the loading spinner correctly using the example that I have provided and keeping the method that I'm calling in my component.
Upvotes: 1
Views: 933
Reputation: 3236
I spent time to reproduce it in a sandbox, but finally found that the problem comes from the initialization of your BehaviorSubject
. The first value of bsResource
is []
. So in your *ngIf
, when the pipe async
subscribes to it, it receives the value []
, which is truthy, so the *ngIf
is instantly true, and never triggers the else (loading) block.
All you have to do is to initialize your BehaviorSubject
with a null value (and then fix the code using it, to be null-safe):
private bsResource: BehaviorSubject<string[]> = new BehaviorSubject(null);
And when you use it:
const cachedValue = this.bsResource.getValue();
if (!cachedValue || cachedValue.length === 0) {
this.fetchResourceList().subscribe(res => this.bsResource.next(res));
}
I'm saying it again, you should not use Promises
, use .subscribe
, which works the same as toPromise().then
. You should NEVER use Promise. .toPromise
is only usefull when you are upgrading Angular, where most of the code uses Promises
, and don't want to upgrade the whole code at once.
Upvotes: 1