6
votes

Is it possible to get the intended route in the CanDeactivate guard in Angular 2 RC5 router? I see an answer to a similar question about CanActivate (CanActivate), but that does not seem to work in the CanDeactivate guard.

The use case I have is as follows: User clicks on a "Home" link while in the middle of filling out a form serviced by a "FormComponent". The FormComponent has a CanDeactivate attached to it. I need to know that the user was attempting to go specifically to the "Home" route to decide on what I want to do in the CanDeactivate. Inspection of the ActivatedRouteSnapshot and RouterStateSnapshot objects in the CanDeactivate guard only show information about the route that the user is currently on, related to the FormComponent. Although I understand this couples the two components together, this is just an example here to understand whether this is possible to do.

2
The official angular docs for ActivatedRouteSnapshot, which you are probably using inside your canActivate function contains root, parent and pathFromRoot which you could use to work out the url. angular.io/docs/ts/latest/api/router/index/…peppermcknight
I can only get information from ActivatedRouteSnapshot on the currently active route, not the one that is about to be activated.ruk

2 Answers

6
votes

You might not need the destination URL for the next route since you can return an Observable<boolean> to the CanDeactivate() in the guard.

When our guard is checked we trigger our check for changes in the component and return a filtered Observable back to the guard. (Filter it for just the "true" value - otherwise we won't be able to continue the navigation to our desired route afterwards!)

When our check passes, we may navigate - if not we can't.

So we maybe want to show our user two buttons. One to skip saving and navigate (doNotSaveAndNavigate()) and another one to save and continue his navigation (doSaveAndNavigate()).. or any other help to guide him or her..

some.component.ts

import { Component } from '@angular/core';

// Utils
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable }      from 'rxjs/Observable';

@Component({
  selector: 'selector',
  templateUrl: 'some.component.html'
})
export class SomeComponent {
  // Utils
  youMayNavigateSource = new BehaviorSubject<boolean>(false);
  youMayNavigate$: Observable<boolean> = this.youMayNavigateSource.asObservable();

  checkForUnsavedChanges(): void {
    // Check your form/data for changes...
    let hasSaved: boolean = false;
    // Set the next value for our observable
    this.youMayNavigateSource.next(hasSaved);
  }

  canDeactivate(): Observable<boolean> {
    // Do some checking if we "can deactivate" the current route 
    this.checkForUnsavedChanges();
    // Return an observable so we can change it later on and continue our navigation attempt
    return this.youMayNavigate$.filter(el => el); // (!)
  }

  allowNavigation(): void {
    // Set the next value for our observable to true
    this.youMayNavigateSource.next(true);
  }

  doNotSaveAndNavigate(): void {
    this.allowNavigation();
  }

  doSaveAndNavigate(): void {
    // Save our data
    this.doSave();
    this.allowNavigation();
  }

  doSave(): void {
    // do some fancy saving...
  }
}

can-deactivate-guard.service.ts

import { Injectable }    from '@angular/core';
import { CanDeactivate } from '@angular/router';

import { SomeComponent } from './some.component';

@Injectable()
export class CanDeactivateGuard implements CanDeactivate<SomeComponent > {
  canDeactivate(component: SomeComponent ) {
    return component.canDeactivate ? component.canDeactivate() : true;
  }
}

(I guess you know how to setup the CanDeactivate for the Router but for the sake of completeness you'll find a usage example in the official docs)


FYI: If you rly just want to get the URL on CanDeactivate() failure, subscribe to the router.events and filter for the NavigationCancel event like this:

this.router.events
           .filter(event => event instanceof NavigationCancel)
           .subscribe(el => console.log('Requested URL:', el.url));

you may need to import:

import { Router, NavigationCancel } from '@angular/router'; 
import 'rxjs/add/operator/filter';

and add the router to your constructor:

  constructor(private router: Router) { }
4
votes

Angular 4 introduced an extra optional parameter to the CanDeactivate interface.

You can see it here.

canDeactivate(
  component: T, 
  currentRoute: ActivatedRouteSnapshot,
  currentState: RouterStateSnapshot, 
  nextState?: RouterStateSnapshot
  ): Observable<boolean>|Promise<boolean>|boolean;

You can use it with either Observable, Promise or immediately return a boolean.

Example:

  canDeactivate(
    component: RetailerComponent,
    route: ActivatedRouteSnapshot,
    currentState: RouterStateSnapshot,
    nextState: RouterStateSnapshot
  ): Observable<boolean> | boolean {
    if (nextState.url.includes('company/manage'))
      return false;
    else
      return true;

Discussed here.