TypeError: Cannot read property 'canDeactivate' of null
Asked Answered
P

2

6

I'm trying to use the canDeactivate router guard. The component I'm passing in (ClaimsViewComponent) is null the first time the code runs. But on subsequent runs, the code runs as expected. We're trying to figure out why the component is null on first run.

This is the canDeactivate guard code:

@Injectable()
export class ConfirmDeactivateGuard implements 
CanDeactivate<ClaimsViewComponent> {
  canDeactivate(target: ClaimsViewComponent): boolean {

      if (target.canDeactivate()) {
          return window.confirm('Do you really want to cancel?');
      }

      return true;
  }
}

This is the routing module code:

const routes: Routes = [
{ path: ':type', component: ClaimsViewComponent, canDeactivate: 
[ConfirmDeactivateGuard] }
];

@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})

Here is the full error stack:

TypeError: Cannot read property 'canDeactivate' of null
    at ConfirmDeactivateGuard.canDeactivate (confirm-deactivate-guard.ts:16)
    at MergeMapSubscriber.eval [as project] (router.js:3933)
    at MergeMapSubscriber._tryNext (mergeMap.js:128)
    at MergeMapSubscriber._next (mergeMap.js:118)
    at MergeMapSubscriber.Subscriber.next (Subscriber.js:95)
    at ArrayObservable._subscribe (ArrayObservable.js:116)
    at ArrayObservable.Observable._trySubscribe (Observable.js:172)
    at ArrayObservable.Observable.subscribe (Observable.js:160)
    at MergeMapOperator.call (mergeMap.js:92)
    at Observable.subscribe (Observable.js:157)
    at ConfirmDeactivateGuard.canDeactivate (confirm-deactivate-guard.ts:16)
    at MergeMapSubscriber.eval [as project] (router.js:3933)
    at MergeMapSubscriber._tryNext (mergeMap.js:128)
    at MergeMapSubscriber._next (mergeMap.js:118)
    at MergeMapSubscriber.Subscriber.next (Subscriber.js:95)
    at ArrayObservable._subscribe (ArrayObservable.js:116)
    at ArrayObservable.Observable._trySubscribe (Observable.js:172)
    at ArrayObservable.Observable.subscribe (Observable.js:160)
    at MergeMapOperator.call (mergeMap.js:92)
    at Observable.subscribe (Observable.js:157)
    at resolvePromise (zone.js:814)
    at resolvePromise (zone.js:771)
    at eval (zone.js:873)
    at ZoneDelegate.invokeTask (zone.js:421)
    at Object.onInvokeTask (core.js:4740)
    at ZoneDelegate.invokeTask (zone.js:420)
    at Zone.runTask (zone.js:188)
    at drainMicroTaskQueue (zone.js:595)

Thank you.

Punkah answered 20/7, 2018 at 21:1 Comment(0)
A
15

I fixed this issue by adding the canDeactivate inside my page.module.ts instead of app.module.ts

Aerograph answered 22/5, 2019 at 9:40 Comment(2)
Hi, Could you please share in which section in the page.module.ts did you add canDeactivate. I have tried adding the Guard to the provider. Still getting the error.Esparza
@TapasMukherjee I added , canDeactivate: [ConfirmDeactivateGuard] to lazyloadingpage-routing.module.ts.Farrica
P
5

It seems, canDeactivate not working on 'lazy loading'. Please check your route, and make sure, that you have set the component property.

Update with @rohan-sampat feedback.

It works. I too had issue with CanDeactivate in my project.

CanDeactivate-guard.service.ts

import { Observable } from 'rxjs';
import { CanDeactivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';

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

export class CanDeactivateGuard implements CanDeactivate<CanComponentDeactivate> {

  canDeactivate(component: CanComponentDeactivate,
    currentRoute: ActivatedRouteSnapshot,
    currentState: RouterStateSnapshot,
    nextState?: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
    return component.canComDeactivate();
  }

}

This is the component where I was using CanDeactivate to validate.

import { Component, EventEmitter, Output, OnInit } from "@angular/core";
import { DataService } from '../Shared/Service/DataService';
import { ActivatedRoute } from '@angular/router';
import { Subscription, Observable } from 'rxjs';
import { CanComponentDeactivate } from '../can-deactivate-guard.service';

@Component({
  selector: 'app-server',
  templateUrl: './server.component.html'
})

export class ServerComponent implements OnInit, CanComponentDeactivate {

  @Output() serverCreated = new EventEmitter<string>();

  queryParams: Subscription;
  data: string;

  canMove: false;

  userModel: any;
  constructor(private dataService: DataService, private route: ActivatedRoute) {

  }

  ngOnInit() {
    this.dataService.dataUpdate.subscribe(data => {
      this.data = data;
    })

    this.queryParams = this.route.queryParams.subscribe(data => {
      this.userModel = JSON.parse(data["model"]);
    })
  }


  canComDeactivate(): Observable<boolean> | Promise<boolean> | boolean {
    if (this.data == undefined || this.data == "") {
      var res = confirm("Text is blank");
      return res;
    }
    return this.canMove;
  }
}

The app.module.ts file in which I had my routes defined.

const appRoutes: Routes = [
 // { path: '', loadChildren: () => import('./Login/login.module').then(l => l.LoginModule) },
  { path: 'login', loadChildren: () => import('./Login/login.module').then(l => l.LoginModule) },
  { path: 'server', canActivate: [AuthGuard], canDeactivate: [CanDeactivateGuard], loadChildren: () => import('./severs/server.module').then(s => s.ServerModule) },
  { path: '**', component: ErrorComponent },
];

CanDeactivate on path Server was causing issue. If I was trying to go to another path outside the module, then id does not even hit the CanDeactivate guard.

But if I placed CanDeactivate from app.module to server.routes files, means in lazy loaded module itself to validate it, then it was working properly, as expected.

Server.route.ts file

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { ServerComponent } from './server.component';
import { ErrorComponent } from './404.component';
import { CanDeactivateGuard } from '../can-deactivate-guard.service';

const route: Routes = [
  { path: '', component: ServerComponent },
  { path: 'server:/model', component: ServerComponent, canDeactivate: [CanDeactivateGuard] },
  { path: 'server', component: ServerComponent, canDeactivate: [CanDeactivateGuard] },

  { path: 'not-found', component: ServerComponent },
  //{ path: '**', component: ErrorComponent },

]

@NgModule({
  imports: [RouterModule.forChild(route)],
  exports: [RouterModule]
})

export class ServerRoute { }

Now after this change in solution, my code works was expected. Hope this helps.

Posh answered 2/5, 2019 at 9:33 Comment(2)
No it works, we have to check it's implementation in our project. I too was facing problem. I was on lazy loaded module and was trying to move to another lazy load module and I had put canDeactivate check on app.routing itself where as I had to put it in my specific lazy loaded module routing file i.e in my case Server Module. So after that it's working fine. Do refer this link... github.com/angular/angular/issues/16868#issuecomment-341346994Veradi
@RohanSampat Thank you, Can you update my answer with your input? I'll accept the change.Posh

© 2022 - 2024 — McMap. All rights reserved.