I am using Angular 5.2.1. I have a main page which has two components:
<proposal-expansion [proposallist]="proposalList" ... ></proposal-expansion>
<proposal-dashboard ... ></proposal-dashboard>
All the data coming from the server is loaded by the time the main page is rendered. Initially the expansion is empty (proposalList is []). The dashboard shows an abbreviated version of 5 proposals. Note that even though the display shows minimal data on the proposals, all the data is still there. I then have a button on the dashboard component that when clicked hides the dashboard and emits an event containing all the proposal data.
expandRecent(): void {
console.log("expand clicked...");
this.spinnerService.spinner(true);
this.showRecent.emit(this.propList);
}
In the main page component, here is the function that is called when that event is emitted:
showRecent(event) {
this.proposalList = event;
this.showDashboard = false;
this.spinnerService.spinner(false);
}
So at that point, the expansion component takes the data from proposalList and tries to render. There are a number of child components which are rendered within the expansion component, and it takes 4-5 seconds before the expansion component is actually displayed. Keep in mind that there is no interaction with the server for that time--the 4-5 seconds is all Angular.
Given that it takes so long for the expansion component to be displayed, I would like to have a spinner appear so that the user knows something is happening. I have a spinner service which works by passing in true
to show the spinner or false
to hide it. I pass true
to the service as soon as the button on the dashboard is clicked (in expandRecent()
). Here is where I run into problems.
Even though turning the spinner on is the first thing to happen (other than the console.log()
, it doesn't actually happen first. Initially, I thought that the spinner was being turned on and off immediately. However, I found that if I never turned off the spinner, it would still wait until the expansion component was loaded before the spinner came on. Also, I found that if I manually turned on the spinner prior to clicking the button, then the spinner would not turn off until the expansion was loaded as desired. So it seems that I need to figure out when and where to turn the spinner on.
Based on the Angular documentation, ngOnChanges
Responds when Angular (re)sets data-bound input properties.
Based on that, I added this in the expansion component, thinking that as soon as the proposalList
was updated, this would be called:
ngOnChanges() {
this.spinnerService.spinner(true);
}
I also removed the line that sets the spinner to true in the expandRecent()
method. However, when I did all that, there was still no spinner until after the expansion component was loaded, and worse--it turned it off before the ngOnChanges turned it on. So it showed no spinner for 4-5 seconds, then it came on and ran indefinitely.
What are the appropriate lifecycle hooks I should use, and in which components should I use them, in order to get the spinner to behave as expected?
Thanks!
UPDATE WITH LATEST ATTEMPTS:
I have now moved to using the router and a service to accomplish this, but still have the same problem.
expandRecent(): void {
console.log("expand recent");
this.spinnerService.spinner(true);
this.changeDetectorRef.detectChanges();
// essentially this just passes the data from one list to another in the service
this.proposalService.proposalShortList$.take(1).subscribe(
list => this.proposalService.setProposalList(list)
);
// this navigates to the ProposalExpansion component
this.router.navigate(['../display'], { relativeTo: this.route });
}
I then tried adding this to the ProposalExpansion component:
ngAfterViewChecked() {
this.spinnerService.spinner(false);
}
Now when expandRecent() runs, this is what I see in the console (The numbers are the time stamps for when each component's ngOnInit is run.):
expand recent
Should change spinner to true
Set up proposal list in expansion component
1520359500144
Initiate proposal for 2924
1520359500342
Inititiating revision for -
1520359500417
Initiating item 1
1520359500537
Initiate proposal for 2923
1520359500718
...
Initiating item 1
1520359502082
Should change spinner to false (8 x)
Should change spinner to false
Should change spinner to false (8 x)
Based on the console, one would think it is working. However, the spinner never as actually visible. But, if I put the router.navigate
inside a setTimeout
function, then the spinner works exactly as expected. (The setTimeout worked when the time was set to 100 ms or more. It did not work with 10 ms or less.) While this is a work-around that I could use, I would really like to understand what is happening, and it doesn't seem like I should have to use that kind of work-around.
I would love to have this in a Plunker, but I was having trouble even getting it to work with Angular Material before adding all my code, so that would be a significant time investment that I would prefer to avoid.
Here is the spinner code:
spinner.component.html
<div *ngIf="(spinner$ | async)">
<i class="fas fa-spinner fa-pulse fa-2x fa-fw"></i>
</div>
spinner.component.ts
import { Component, OnInit } from '@angular/core';
import {Observable} from 'rxjs/Observable';
import { SpinnerService } from './spinner.service';
@Component({
selector: 'spinner',
templateUrl: './spinner.component.html',
styleUrls: ['./spinner.component.scss']
})
export class SpinnerComponent implements OnInit {
spinner$: Observable<boolean>;
constructor(private spinnerService: SpinnerService) { }
ngOnInit() {
this.spinner$ = this.spinnerService.spinner$;
}
// to show the spinner
showSpinner() {
this.spinnerService.spinner(true);
}
// to hide the spinner
hideSpinner() {
this.spinnerService.spinner(false);
}
}
spinner.service.ts
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
@Injectable()
export class SpinnerService {
private spinnerSubject = new BehaviorSubject<boolean>(false);
spinner$: Observable<boolean> = this.spinnerSubject.asObservable();
constructor() {}
spinner(spin:boolean) {
console.log("Should change spinner to "+spin);
this.spinnerSubject.next(spin);
}
}