Updated January 2017: this post now reflects the final Angular 2.x API.

A German translation of this piece is available as well.

The new Angular 2 router, shipped since June 2016, can notify applications about navigation changes through Guards. A Guard is simply a service whose interface-defined methods are invoked by the router whenever a component is activated or deactivated. The result returned by these methods—true, false, or Observable<boolean> —determines whether the router may proceed with the navigation. The observable option is particularly useful when the decision must be deferred, for instance while consulting a backend service or prompting the user.

To build guards, the router exposes two interfaces: CanActivate, which includes canActivate and runs before a component is activated, and CanDeactivate, which provides canDeactivate and runs before a component is left. This example demonstrates how canDeactivate can be used to request user confirmation before navigating away from a screen. The complete sample code is hosted on GitHub.

sample

Building a Guard

The guard shown here is a standard Angular 2 service that implements the parametrised CanDeactivate interface, where the type parameter designates the component it protects:

import { CanDeactivate, ActivatedRouteSnapshot, RouterStateSnapshot} from '@angular/router';
import { FlightEditComponent} from './flight-edit.component';

export class FlightEditGuard implements CanDeactivate<FlightEditComponent> {

    canDeactivate(
            component: FlightEditComponent, 
            route: ActivatedRouteSnapshot, 
            state: RouterStateSnapshot) {

                return component.canDeactivate();
    }

}

Component implementation

The canDeactivate method receives the component instance along with details about both the current and the upcoming route state. In this implementation, the guard simply forwards the call to the component, letting it display a warning dialog.

Inside the component, the canDeactivate method invoked by the guard flips exitWarning.show to true so the warning becomes visible. The warning asks the user to confirm leaving the route. Because canDeactivate must wait for that confirmation, it returns an Observable<boolean> and keeps the matching Observer<boolean> in exitWarning.observer.

Once the user responds, the decide method absorbs the choice, resets exitWarning.show to false, and forwards the decision to the router through the stored Observer.

import { Component} from '@angular/core';
import { ActivatedRoute} from '@angular/router';
import { Observable, Observer} from 'rxjs';

@Component({
    template: require('./flight-edit.component.html')
})
export class FlightEditComponent {

    [...]    

    exitWarning = {
        observer: null,
        show: false
    }

    decide(d: boolean) {
        this.exitWarning.show = false;
        this.exitWarning.observer.next(d);
        this.exitWarning.observer.complete();
    }

    canDeactivate() {
        this.exitWarning.show = true;
        return new Observable<boolean>((sender: Observer<boolean>) => {
            this.exitWarning.observer = sender;

        });
    }

}

The warning's template appears below. It renders only when exitWarning.show is truthy, and routes the user's selection to decide.

<div *ngIf="exitWarning.show" class="alert alert-warning">
        <div>
        Daten wurden nicht gespeichert! Trotzdem Maske verlassen?
        </div>
        <div>
            <a href="javascript:void(0)" (click)="decide(true)" class="btn btn-danger">Ja</a>
            <a href="javascript:void(0)" (click)="decide(false)" class="btn btn-default">Nein</a>
        </div>
</div>

Registering the guard in the router configuration

To associate the guard with a component, the component's route entry must list it inside the canDeactivate array:

const APP_ROUTES: RouterConfig = [
    {
        path: '/home',
        component: HomeComponent,
        index: true
    },
    {
        path: '/flight-booking',
        component: FlightBookingComponent,
        children: [
            {
                path: '/flight-search',
                component: FlightSearchComponent
            },
            {
                path: '/passenger-search',
                component: PassengerSearchComponent
            },
            {
                path: '/flight-edit/:id',
                component: FlightEditComponent,
                canDeactivate: [FlightEditGuard]
            }
        ]
    }
];

Technically, canDeactivate references a token that needs a provider bound to a service. The next snippet sets up that provider with the usual shorthand syntax:

import { OAuthModule } from 'angular-oauth2-oidc';

[...]

@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    [...]
  ],
  providers: [
    FlightEditGuard
  ]
  [...]
})
export class AppModule {
}