I stumbled upon this question quite a bit after the fact, but I hope to help someone else coming here.
The principal candidate for a solution is a route guard.
See here for an explanation:
https://angular.io/guide/router#candeactivate-handling-unsaved-changes
The relevant part (copied almost verbatim) is this implementation:
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { CanDeactivate,
ActivatedRouteSnapshot,
RouterStateSnapshot } from '@angular/router';
import { MyComponent} from './my-component/my-component.component';
@Injectable({ providedIn: 'root' })
export class CanDeactivateGuard implements CanDeactivate<MyComponent> {
canDeactivate(
component: MyComponent,
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean> | boolean {
// you can just return true or false synchronously
if (expression === true) {
return true;
}
// or, you can also handle the guard asynchronously, e.g.
// asking the user for confirmation.
return component.dialogService.confirm('Discard changes?');
}
}
Where MyComponent
is your custom component and CanDeactivateGuard
is going to be registered in your AppModule
in the providers section and, more importantly, in your routing config in the canDeactivate
array property:
{
path: 'somePath',
component: MyComponent,
canDeactivate: [CanDeactivateGuard]
},
Edit:
Given that this question has quite a bit of traction and that many people seems to be requesting a solution to a different but related problem (how to add guards to a set of routes), I'm adding here a small helper function to add a set of guards to all of them:
// the Pick<...> syntax could be extended or replaced with a Partial<Route>
function withGuards<T extends Route | Routes>(input: T,
guards: Pick<Route, 'canActivate' | 'canDeactivate'>): T {
const routes: Route[] = Array.isArray(input) ? input : [input];
for (let route of routes) {
// if an object already exists, we do not replace the configured guards. meant to allow overrides for specific entries.
Object.entries(guards).forEach(([prop, guard]) => {
if (route && !route[prop]) route[prop] = guard;
});
if (route?.children) {
withGuards(route.children, guards);
}
}
return input;
}
// usage:
const route = withGuards({
path: 'somePath',
component: MyComponent
}, {
canDeactivate: [CanDeactivateGuard]
});
This method will inject the guard in all the provided route(s) and in all their nested children, recursively.