Mit Guards Ins Routing Des Angular-2-Routers Eingreifen

Mit Guards Ins Routing Des Angular-2-Routers Eingreifen

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.

Using Guards, Angular-Applications can get informed about changes to the current route by the new Angular-2-Router that is available since June 2016. These Guards are just services. Their methods that are defined by interfaces are called by the router when it activates or deactivates a component. The returned value defines whether the router is allowed to perform the requested route-change. Valid values are true, false and Observable<boolean>. The latter one allows to postpone the decision so that the application can delegate to a service or talk to the user before making up its decision.

For implementing guards, the router provides two interfaces: CanActivate defines the method canActivate that is executed by the router before activating a component. The interface CanDeactivate in turn comes with a method canDeactivate that is executed before deactivating a component. This article shows how an application can use canDeactivate to ask the user for permission before leaving a component. The sourcecode for this sample can be found here.

sample

Implementing a Guards

The here presented guard is a simple Angular-2-Service that implements the interface CanDeactivate. This interface has to be parametrised with the type of the component it is indented for:

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 method canDeactivate takes the instance of the component in question as well as parameters that inform about the current and the requested future state of the router. The here described implementation delegates to the component so that it can display a warning for the user.

The method canDeactivate within the component which is called by the guard sets the property exitWarning.show to true to show the warning. This warning asks the user whether they really want to leave the route. As canDeactivate has to wait for their decision it returns an Observable<boolean> and stores the equivalent Observer<boolean> into exitWarning.observer.

The method decide takes the decision from the user and hides the warning by setting exitWarning.show back to false. After that, it sends the received decision to the router by using the Overserver.

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 template for the warning can be found in the next listing. It shows the warning in lieu of exitWarning.show. The descion of the user is delegated to the method 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 guard within the router-configuration

To register the guard for a component, its entry within the router-configuration has to point to it via the array canDeactivate:

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]
            }
        ]
    }
];

Strictly speaking, canDeactivate only points to a token which has to be bound to a service with a provider. The following listing creates a such a provider using the usual abbreviated form:

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

[...]

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