Reputation: 455
In Angular, after a routerLink
triggers a route change, the old component is only destroyed basically at the same time as the new component's initialization:
This is being a problem for me because I want to show a spinner after the old component is destroyed and before the new component is built -- this is relevant when a component/module is lazy-loaded. Since the old component is only destroyed as late as the new component is being built, my current spinner is just laying over the old component while the new component is loaded.
So is there a way to config the router so it destroys the old component as soon as routerLink
is triggered? Like this:
I should also mention that I don't want to hide the router-outlet
in any way because I'm also applying animations to the entering/leaving components, including the spinner.
Here's what that code is looking like, if it matters (it's app.component
and I'm using Tailwind, btw):
@Component({
styles: `
:host ::ng-deep router-outlet + * { // target the routed components and the spinner
&, & + * {
position: absolute;
top: 0;
left: 0;
width: 100%;
}
}
.router-outlet-container {
position: relative;
height: 100%;
width: 100%;
}
.spinner-outer-container {
position: relative;
height: 100%;
width: 100%;
}
.spinner-inner-container {
position: absolute;
top: 50%;
left: 50%;
transform: translateX(-50%) translateY(-50%);
}
`,
animations: [
trigger('routeAnimations', [
transition('* <=> *', [
query(':enter, :leave', [ style({
position: 'absolute', left: 0, width: '100%', overflow: 'hidden',
}) ], { optional: true }),
group([
query(':enter', [
style({ opacity: '0' }),
animate(animation, style({ opacity: '1' })),
], { optional: true }),
query(':leave', [
style({ opacity: '1' }),
animate(animation, style({ opacity: '0' })),
], { optional: true }),
]),
]),
]),
],
})
export class MyComponent {
#router = inject(Router);
protected route = new Subject<string>();
#lazyLoadingStarted$ = this.#router.events.pipe(
filter(v => v instanceof RouteConfigLoadStart),
map(() => '__lazy_loading__'),
);
protected routingState = toSignal(this.#lazyLoadingStarted$.pipe(mergeWith(this.route)));
}
<div [@routeAnimations]=routingState() class="router-outlet-container">
<router-outlet #outlet="outlet" (activate)="route.next(outlet.activatedRoute.snapshot.url[0]?.path || '')"/>
@if (routingState() === '__lazy_loading__') {
<div class="spinner-outer-container">
<div class="spinner-inner-container">
<mat-spinner/>
</div>
</div>
}
</div>
my current spinner is just laying over the old component
I found out a little way to circumvent this. Basically turned the spinner's container into an overlay. Only had to change styles and the spinner's container:
:host {
height: 100vh;
width: 100vw;
display: flex;
flex-direction: column:
.spinner-overlay, ::ng-deep router-outlet + * { // target the spinner and the routed components
position: absolute;
top: 0;
left: 0;
width: 100%;
}
.spinner-overlay {
width: 100%;
z-index: 10;
background: radial-gradient(hsla(0, 0%, 100%, 0.79), hsla(0, 0%, 100%, 0.17));
}
}
<div class="spinner-overlay">
<div class="spinner-inner-container">
<mat-spinner/>
</div>
</div>
Meaning this question no longer causes problems for my use case, so now I'm only looking for an answer out of curiosity because there are probably other use cases where fine-grained customization of Router behaviour may be important.
Upvotes: 1
Views: 223
Reputation: 982
For me it sounds better to subscribe to Angular Router Events and build your spinner around that instead of the Lifecycle hooks.
For example, while NavigationStart
you set somewhere a flag like showSpinner = true
and at the NavigationEnd
you set somewhere a flag like showSpinner = false
. You could also build a service around that, that has some more logic about the current destructuring component and then the new one to maintain a robust solution.
Another alternative would be CanActivate
und canDeactivate
Route Guard to trigger a Subject, which sets the Spinner to true/false.
And last but not least another variant could be to write your own CustomRouteReuseStrategy and provide it globally in your app. With that you have full control over the router components lifecycle, but use it carefully, because it needs some core knowledge to do it properly.
Upvotes: 0
Reputation: 15279
Router may never destroy previous component, it can cache it for reuse. Previous component is not removed from DOM until router loads lazy module. You can wrap router outlet in custom component, subscribe to router events and show/hide outlet when needed e.g. apply visibility: hidden
.
Upvotes: 1