My Angular 9 app has a CustomErrorHandler, and an ErrorPageComponent. When any exception is thrown throughout the app, the CustomErrorHandler will tell the router to navigate to ErrorPageComponent. However, there is a button inside ErrorPageComponent which could throw its own exception, in which case I want CustomErrorHandler to still tell the router to navigate to ErrorPageComponent, as is normal. However, when ErrorPageComponent is routed to itself in this way, it needs to call its initialization method a second time.
Normally, if you want a component to call an initialization method after routing to itself, you can simply subscribe to the routing event. Then, as long as you have correctly set onSameUrlNavigation to reload, when the component navigates to itself, the router will trigger a routing event will call whichever callback method your component has used to subscribe to it.
However, when my CustomErrorHandler tells the router to navigate to ErrorPageComponent, no routing event is triggered.
This will make more sense if you look at the code:
Here's my route config inside app-routing.module.ts:
const routes: Routes = [
{ path: 'normal', component: NormalComponent},
{ path: '', redirectTo: '/normal', pathMatch: 'full'}
];
const volitileRoutes: Routes = [
{ path: 'error', component: ErrorPageComponent}
];
const fallbackRoute: Routes = [
{ path: '**', component: Error404PageComponent }
];
@NgModule({
imports: [
RouterModule.forRoot(routes),
RouterModule.forRoot(volitileRoutes, {onSameUrlNavigation: 'reload'}), // I've correctly set onSameUrlNavigation
RouterModule.forRoot(fallbackRoute)
],
exports: [RouterModule]
})
export class AppRoutingModule { }
Here is the CustomErrorHandler which will redirect to the ErrorPageComponent:
@Injectable({
providedIn: 'root'
})
export class CustomErrorHandler extends ErrorHandler {
constructor(private ngZone: NgZone, private router: Router){
super();
}
handleError(error: any){
// error handling code here
console.log('error caught'); // I've confirmed that my CustomErrorHandler is in fact handling errors.
/* This is not the normal way to do navigation. But for reasons I don't understand, this
is the only way to get navigation to work from within an ErrorHandler; you cannot simply
call router.navigate like normal from within an ErrorHandler. */
this.ngZone.run(() => this.router.navigate(['/error']));
}
}
Finally, here is ErrorPageComponent:
@Component({
selector: 'app-error-page',
templateUrl: './error-page.component.html',
styleUrls: ['./error-page.component.css']
})
export class ErrorPageComponent implements OnInit, OnDestroy {
private navSubscription = new Subscription();
constructor(private router: Router) { }
ngOnInit(): void {
this.navSubscription = this.router.events.subscribe((e: any) => {
console.log('route event triggered'); // this line is never reached
if (e instanceof NavigationEnd) {
// do initialization code
}
});
}
ngOnDestroy(): void {
this.navSubscription?.unsubscribe(); // this prevents a memory leak
}
}
As I mentioned in my code comments, I've confirmed that CustomErrorHandler is correctly handling the error, and is calling the router navigation method. However, the subscription callback method passed to the router navigation event is never called. What am I doing wrong? How do I subscribe to routing events in ErrorPageComponent?
Have you tried to first redirect to dummy location, and then redirect to your actual component,
this.router.navigateByUrl('/', {skipLocationChange: true}).then(()=>
this.router.navigate(['/error'])
);
I suggest you to set the first location as a location with ngOnInit and ngOnDestroy that are not doing a lot of actions because this will wait the init and destroy before routing to the new location.
Doing this you don't need to use onSameUrlNavigation
, I think it's way better because you don't need to use a subscription, and so destroy it, you don't have to set onSameUrlNavigation
and you can simply reload when you want with a simple function.
But if you want to use your subscription, move it in your constructor instead of in your ngOnInit
function.