I'm currently trying to display a loading indicator on navigation in Angular. The issue I am experiencing is that when the user is tries to get on a protected route, the guard will redirect them but that means my loading service turns on/off/on/off, because the navigation gets cancelled and started again. How would you approach this issue?
Here's my flow exactly with examples: LoadingService
export class LoadingService {
private readonly loadingSubject = new BehaviorSubject<boolean>(false);
public readonly loading$ = this.loadingSubject.asObservable();
private timeoutId: ReturnType<typeof setTimeout> | null = null;
setLoading(isLoading: boolean, delay = 200): void {
if (isLoading) {
// Introduce a delay before showing the spinner
this.timeoutId = setTimeout(() => {
this.loadingSubject.next(true);
}, delay);
} else {
// Clear the timeout if navigation completes quickly
if (this.timeoutId !== null) {
clearTimeout(this.timeoutId);
this.timeoutId = null;
}
this.loadingSubject.next(false);
}
}
}
After authentication the loading service sets loading on true and routes to the correct url
this.authService
.login(email, password)
.pipe(take(1))
.subscribe({
next: () => {
const returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/devices';
this.loadingService.setLoading(true, 0);
this.router.navigate([returnUrl]);
},
in my app component:
this.router.events
.pipe(takeUntilDestroyed(this._destroyRef))
.subscribe((event) => {
if (event instanceof NavigationStart) {
this._loadingService.setLoading(true);
} else if (
event instanceof NavigationEnd ||
event instanceof NavigationCancel ||
event instanceof NavigationError
) {
this._loadingService.setLoading(false);
}
});
Sounds like you have a specific edge case you want to handle.
I would suggest using the rxjs operator pairwise
to get the previous and current navigation state.
Using this, you can filter out your specific scenario and prevent the flickering from happening.
this.router.events
.pipe(takeUntilDestroyed(this._destroyRef), pairwise())
.subscribe(([prevEvent, currEvent]: any) => {
// below if condition specifically for handling edgecases:
if(prevEvent instanceof NavigationCancelled &&
currEvent instanceof NavigationStart) {
// set loading to false, or event better do not change the state.
this._loadingService.setLoading(false);
// do not execute the bottom code, just exit.
return;
}
if (currEvent instanceof NavigationStart) {
this._loadingService.setLoading(true);
} else if (
currEvent instanceof NavigationEnd ||
currEvent instanceof NavigationCancel ||
currEvent instanceof NavigationError
) {
this._loadingService.setLoading(false);
}
});