5

In the code below (an example from the book "Angular 2 Development with TypeScript"):

import {CanDeactivate, Router} from "@angular/router";
import {Injectable} from "@angular/core";

@Injectable()
export class UnsavedChangesGuard implements CanDeactivate{

    constructor(private _router:Router){}

    canDeactivate(){
        return window.confirm("You have unsaved changes. Still want to leave?");

    }
}

I see a warning when I hover over CanDeactivate in WebStorm:

enter image description here

Referring to the answers to this question - Generic type 'Observable<T>' requires 1 type argument - the following change removes the warning:

export class UnsavedChangesGuard implements CanDeactivate<any>{

However, I would like to know how I can find out what is the actual argument that CanDeactivate requires.

Edit: Looking at @angular/router/src/interfaces.d.ts we can see the following:

/**
 * @whatItDoes Indicates that a class can implement to be a guard deciding if a route can be
 * deactivated.
 *
 * @howToUse
 *
 * ```
 * class UserToken {}
 * class Permissions {
 *   canDeactivate(user: UserToken, id: string): boolean {
 *     return true;
 *   }
 * }
 *
 * @Injectable()
 * class CanDeactivateTeam implements CanDeactivate<TeamComponent> {
 *   constructor(private permissions: Permissions, private currentUser: UserToken) {}
 *
 *   canDeactivate(
 *     component: TeamComponent,
 *     route: ActivatedRouteSnapshot,
 *     state: RouterStateSnapshot
 *   ): Observable<boolean>|Promise<boolean>|boolean {
 *     return this.permissions.canDeactivate(this.currentUser, route.params.id);
 *   }
 * }
 *
 * @NgModule({
 *   imports: [
 *     RouterModule.forRoot([
 *       {
 *         path: 'team/:id',
 *         component: TeamCmp,
 *         canDeactivate: [CanDeactivateTeam]
 *       }
 *     ])
 *   ],
 *   providers: [CanDeactivateTeam, UserToken, Permissions]
 * })
 * class AppModule {}
 * ```
 *
 * You can also provide a function with the same signature instead of the class:
 *
 * ```
 * @NgModule({
 *   imports: [
 *     RouterModule.forRoot([
 *       {
 *         path: 'team/:id',
 *         component: TeamCmp,
 *         canActivate: ['canDeactivateTeam']
 *       }
 *     ])
 *   ],
 *   providers: [
 *     {
 *       provide: 'canDeactivateTeam',
 *       useValue: (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => true
 *     }
 *   ]
 * })
 * class AppModule {}
 * ```
 *
 * @stable
 */
export interface CanDeactivate<T> {
    canDeactivate(component: T, route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean;
}

But it's not clear what "TeamComponent" is.

camden_kid
  • 12,591
  • 11
  • 52
  • 88

2 Answers2

9

API documentation provides links to source code at the bottom of the page.

CanDeactivate interface uses generics to type the first canDeactivate argument (deactivated component):

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

As shown in this guide, canDeactivate is called with deactivated component instance as an argument:

export interface CanComponentDeactivate {
 canDeactivate: () => Observable<boolean> | Promise<boolean> | boolean;
}

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

Where component.canDeactivate is just a method of the same name in component class, it doesn't necessarily have to be called canDeactivate or even exist. And CanComponentDeactivate is just user-defined interface that provides a convention for canDeactivate component method.

This allows to interact with component during deactivation,

The canDeactivate() method provides you with the current instance of the component, the current ActivatedRoute, and RouterStateSnapshot in case you needed to access some external information. This would be useful if you only wanted to use this guard for this component and needed to get the component's properties or confirm whether the router should allow navigation away from it.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
0

I hope to be not too late...

Based on an answer of marco-barbero to a question Is there any way to prevent destroying a component in angular?

import { CanDeactivate } from '@angular/router';
import { CanDeactivateComponent } from './app/can-deactivate';

export class ConfirmDeactivateGuard implements CanDeactivate<any> 
{
  canDeactivate(target: any) 
  {
    if(target.hasChanges()){
      return window.confirm('Do you really want to cancel?');
    }
    return true;
  }
}

And in the router entry of the component:

{ 
  path: '',
  component: SomeComponent,
  canDeactivate: [ConfirmDeactivateGuard]
}

Finally, of course, include the guard in a module as a provider:

@NgModule({
  ...
  providers: [
    ...
    ConfirmDeactivateGuard
  ]
})
export class AppModule {

}

Note the use of any as a parameter of CanDeactivate() method in a guard class. Invocation in the route add the correct parameter, so you can use in any Component. The only requiremet is to implement the method hasChanges():boolean (with the appropiate condition in each one) in every Component in which You want to use the guard.

Stalky
  • 11
  • 1