angularangular-guardsangular-httpclient-interceptors

Error while implementing CanDeactivate Guard


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)

Solution

  • 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 }