Understanding the canDeactivate Router Guard

Angular provides built-in support for route guards, and one of the key guards available is canDeactivate. This guard gives you control over whether a user can leave a particular route. It is especially useful when there is ongoing work on the current view, such as unsaved edits on a profile page. If a user attempts to navigate away without saving, the guard can prompt for confirmation before allowing the navigation.

A typical canDeactivate implementation looks like this:

canDeactivate(
    component: Component,
    currentRoute: ActivatedRouteSnapshot,
    currentState: RouterStateSnapshot,
    nextState?: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {

    return true;
  }

The important aspect here is understanding what the function should return. The guard accepts a boolean value, or it can return an observable or promise that resolves to a boolean or UrlTree. It can also directly return a UrlTree. When implementing the guard, ensure the return type matches one of these options.

Practical Implementation Example

Let us walk through a practical implementation of this guard step by step. First, generate a guard service using the Angular CLI with the following command:

ng g guard custom-guard

After running the command, you will be prompted to select which router guards you want to implement.

image

For this discussion, choose canDeactivate and proceed.

The use case involves two components, comp1 and comp2. The guard is applied to comp1, so when the user attempts to navigate from comp1 to comp2, the confirmation logic kicks in.

A full working example is available on Stackblitz. While the code for the components and the confirmation modal can be referenced there, the focus here is on the route guard itself.

export class ConfirmguardGuard implements CanDeactivate<Comp1Component> {
  canDeactivate(
    component: Comp1Component,
    currentRoute: ActivatedRouteSnapshot,
    currentState: RouterStateSnapshot,
    nextState?: RouterStateSnapshot
  ):
    | Observable<boolean | UrlTree>
    | Promise<boolean | UrlTree>
    | boolean
    | UrlTree {
    console.log('deactivate');
    let subject = new Subject<boolean>();
    component.openDialog();
    subject = component.subject;
    return subject.asObservable();
  }
}

In the routes configuration, the guard is registered as follows:

const routes: Routes = [
  {
    path: 'comp1',
    canDeactivate: [ConfirmguardGuard],
    component: Comp1Component,
  },
  { path: 'comp2', component: Comp2Component },

  { path: '', redirectTo: 'comp1', pathMatch: 'full' },
];

This straightforward implementation gives you a working route guard. The subject that handles the confirmation modal's user interaction is converted into an observable, which is what the guard expects as its return value. No additional business logic is added here, but you can easily incorporate component state or variables if needed.

When you click on Comp2, a confirmation prompt appears. Choosing OK proceeds with navigation, while selecting Cancel keeps you on the current view. Note that the observable returned by canDeactivate only takes the first emitted value.

This example demonstrates the purpose of canDeactivate and shows how to integrate it with a custom confirmation modal in an Angular application. If you have any questions or suggestions, feel free to reach out on Twitter or leave a comment below.

Happy learning!