I'm implementing a dialog with a mat-spinner while page data is loading. I'm working with Angular v14. I'm using a dialog, instead of a plain spinner, because I'll need to pass some information in it. The problem I'm having is, the dialog doesn't display unless I put a break point immediately after it. My understanding of Angular is its synchronous so the code to show the dialog should complete before loading the data, yes? However, I also tried adding a setTimeout to my LoadData method to give it extra time. But the dialog never appears.
The dialog is its own component
<h3 mat-dialog-title>Please Wait</h3>
<mat-dialog-content>
<div style="display:flex; flex-direction:row; justify-content:space-around ">
We're getting your data...
<mat-spinner [diameter]="20"></mat-spinner>
</div>
</mat-dialog-content>
<mat-dialog-actions></mat-dialog-actions>
The typescript file is empty
import { Component, OnInit } from '@angular/core';
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
@Component({
selector: 'app-spinner',
templateUrl: './spinner.component.html',
styleUrls: ['./spinner.component.css']
})
export class SpinnerComponent implements OnInit {
constructor(
public spinnerRef: MatDialogRef<SpinnerComponent>
) { }
ngOnInit(): void {
}
}
I'm loading the spinner and data in a component ngOnInit:
ngOnInit(): void {
this.showSpinner();
setTimeout(() => this.loadData(), 3000);
this.hideSpinner();
}
Here are the showSpinner and hideSpinner functions:
private showSpinner(): void {
const spinnerConfig = new MatDialogConfig();
spinnerConfig.disableClose = true;
spinnerConfig.autoFocus = true;
spinnerConfig.width = '400px';
spinnerConfig.height = '150px';
spinnerConfig.closeOnNavigation = false;
spinnerConfig.disableClose = true;
spinnerConfig.hasBackdrop = true;
this.spinner.open(SpinnerComponent, spinnerConfig)
}
private hideSpinner(): void {
this.spinner.closeAll()
}
The load data methods makes several service calls to get data for drop down fields. This is an example, and there are 4 function calls in total
private loadData(): void {
this.projectService.getAllProjects().subscribe((data) => {
this.projects = data;
}, (error) => {
console.log("an error occured in the project service");
})
CodePudding user response:
The issue is that you are not awaiting the async process of loading the data. This means that this code is executed synchronously, but the callback inside of the setTimeout
method is executed asynchronously:
ngOnInit(): void {
this.showSpinner();
setTimeout(() => this.loadData(), 3000);
this.hideSpinner();
}
As a result, the loading spinner opens, but immediately closes again.
You can fix it by awaiting the result of the loadData
method. With this code the execution is paused until the loadData
method returns a result:
async ngOnInit() {
this.showSpinner();
await this.loadData();
this.hideSpinner();
}
But to achieve this you have to make sure that the loadData
method can be awaited. This can be done like this:
private async loadData() {
const data = await lastValueFrom(this.projectService.getAllProjects());
this.projects = data;
}
Now, we're awaiting the response of our projectService with the RxJS operator lastValueFrom
. This means that it waits until it got the last value from the HTTP request which is in our case the list of projects. By using the async
keyword the loadData
method is automatically returning a Promise that can be awaited by our ngOnInit method.
StackBlitz Example
https://stackblitz.com/edit/angular-59cr8m?file=src/app/dialog-overview-example.ts