0

I'm currently trying to implement a CanDeactivate Guard to my angular application which already had HTTP Interceptor to resolve for authentication. But adding the CanDeactivate guard is throwing Error as mentioned below.

I have registered the Class which implements the CanDeactivate interface in providers array of the AppModule. I have registered the class which implements the CanDeactivate interface in the canDeactivate property of the route.

class implementing the CanDeactivate Interface :

export interface CanComponentDeactivate {
  canComponentDeactivate(): boolean;
}

@Injectable({providedIn: 'root'})
export class AppRegisterDeactivateGuard implements CanDeactivate<AppRegisterComponent> {
  canDeactivate(
    component: AppRegisterComponent,
    currentRoute: ActivatedRouteSnapshot,
    currentState: RouterStateSnapshot
  ): Observable<boolean>|Promise<boolean>|boolean {
    return component.isDeactivated;
  }
}

Component in which the Deactivate guard is applied :

@Component({
  selector: 'app-app-register',
  templateUrl: './app-register.component.html',
  styleUrls: ['./app-register.component.css']
})
export class AppRegisterComponent implements OnInit, CanComponentDeactivate {
  isDeactivated = false;
  @ViewChild('registrationForm') registForm: NgForm;
.
.
canComponentDeactivate() {
    if (this.registForm.dirty) {
      this.isDeactivated = confirm('You have modified changes in the form. \nDo you still want to navigate away ? ');
    }
    return this.isDeactivated;
  }
}

Route definition for that endpoint :

...
{ path: 'register', component: AppRegisterComponent, canDeactivate: ['AppRegisterDeactivateGuard'] },

Providers array in the Module :

 providers: [{
    provide: HTTP_INTERCEPTORS,
    useClass: AppAuthInterceptService,
    multi: true
  }, AppRegisterDeactivateGuard],
  bootstrap: [AppComponent]

ERROR

  StaticInjectorError(Platform: core)[AppRegisterDeactivateGuard]: 
    NullInjectorError: No provider for AppRegisterDeactivateGuard!
Error: StaticInjectorError(AppModule)[AppRegisterDeactivateGuard]: 
  StaticInjectorError(Platform: core)[AppRegisterDeactivateGuard]: 
    NullInjectorError: No provider for AppRegisterDeactivateGuard!
    at NullInjector.push../node_modules/@angular/core/fesm5/core.js.NullInjector.get (core.js:8896)
    at resolveToken (core.js:9141)
    at tryResolveToken (core.js:9085)
    at StaticInjector.push../node_modules/@angular/core/fesm5/core.js.StaticInjector.get (core.js:8982)
    at resolveToken (core.js:9141)
    at tryResolveToken (core.js:9085)
    at StaticInjector.push../node_modules/@angular/core/fesm5/core.js.StaticInjector.get (core.js:8982)
    at resolveNgModuleDep (core.js:21218)
    at NgModuleRef_.push../node_modules/@angular/core/fesm5/core.js.NgModuleRef_.get (core.js:21907)
    at getToken (router.js:2865)
    at resolvePromise (zone.js:831)
    at resolvePromise (zone.js:788)
    at zone.js:892
    at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:423)
    at Object.onInvokeTask (core.js:17290)
    at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:422)
    at Zone.push../node_modules/zone.js/dist/zone.js.Zone.runTask (zone.js:195)
    at drainMicroTaskQueue (zone.js:601)
    at ZoneTask.push../node_modules/zone.js/dist/zone.js.ZoneTask.invokeTask [as invoke] (zone.js:502)
    at invokeTask (zone.js:1744)
Debadatta Meher
  • 83
  • 2
  • 10

2 Answers2

1

TLDR; Change your route to this:

{ path: 'register', component: AppRegisterComponent, canDeactivate: [AppRegisterDeactivateGuard] }

Change your providers to this:

[{
    provide: HTTP_INTERCEPTORS,
    useClass: AppAuthInterceptService,
    multi: true
  }]

Longer answer:

AppRegisterDeactivateGuard having both {providedIn: 'root'} and declaring it as a provider in the module is unnecessary. See the Angular provider docs.

The issue here is a combination with the route and providers.

Right now your route is looking for a token via the string name, however it is available via the class name.

Using @Injectable({providedIn: 'root'}) how you have it uses the class name as the token, and it will be available as AppRegisterDeactivateGuard.

Having AppRegisterDeactivateGuard in your providers how you have it also will make it available as AppRegisterDeactivateGuard.

I don't recommend this, but if you want or need to reference it via string names, you can put it in the providers like so:

{ provide: 'AppRegisterDeactivateGuard', useClass: AppRegisterDeactivateGuard }
  • Thanks @OneLunchMan. It was silly mistake to have the class registered as a string in route definition. Changing it to the class name solved it. I know that registering the service both with the meta-data and providers array is unnecessary, but I did that while debugging just to make sure that it gets registered. Thanks a lot !! – Debadatta Meher Jun 15 '19 at 13:14
0

You need to pass the interface in generics :

export class AppRegisterDeactivateGuard implements CanDeactivate<CanComponentDeactivate > {

and then implement canDeactivate method

Ankii32
  • 106
  • 3