Update in January 2017: This post has been updated to use the final API of Angular 2.x.
The German version of this article can be found here.
Since June 2016, the new Angular router has offered Guards as a way for applications to stay informed about route changes. Guards are implemented as services whose interface-defined methods the router invokes when components are activated or deactivated. The value returned determines whether the router proceeds with the requested navigation. That value can be true, false, or Observable<boolean>. Using the last option, an application can defer its decision, allowing time to consult a service or interact with the user before committing.
Two interfaces support guard implementation: CanActivate, which declares the canActivate method invoked before a component is activated, and CanDeactivate, which declares the canDeactivate method invoked before a component is deactivated. This article demonstrates how canDeactivate can prompt the user for confirmation before navigating away from a component. The source code for this sample is available here.
Building a Guard
The guard described here is a straightforward Angular service that implements CanDeactivate. This interface requires a generic parameter specifying the type of the component it targets:
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();
}
}
Implementing the component
The canDeactivate method receives the component instance along with parameters describing the current router state and the requested future state. In this implementation, the guard hands off to the component so it can surface a warning to the user.
Inside the component, the canDeactivate method—called by the guard—sets exitWarning.show to true to display the warning. This prompt asks the user whether they truly intend to leave the route. Because canDeactivate must pause for the user's response, it returns an Observable<boolean> and stores the corresponding Observer<boolean> in exitWarning.observer.
The decide method accepts the user's choice, hides the warning by resetting exitWarning.show to false, and then pushes the decision back to the router via the 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 template appears in the following listing. The warning is rendered conditionally based on exitWarning.show, and the user's choice is forwarded to the decide method.
<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, its entry in the router configuration must reference the guard through 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 refers to a token that must be mapped to a service via a provider. The listing below creates such a provider using the standard shorthand syntax:
import { OAuthModule } from 'angular-oauth2-oidc';
[...]
@NgModule({
imports: [
BrowserModule,
FormsModule,
HttpModule,
[...]
],
providers: [
FlightEditGuard
]
[...]
})
export class AppModule {
}

