2

I am loading my routes lazily. I am trying to use CanDeactivate as per the documentation but it seems that the guard can not read the component's properties or methods.

This is my guard,

@Injectable()
export class CanDeactivateGuard implements CanDeactivate<PatientRegistrationFormComponent> {
  constructor() {}
  canDeactivate(component: PatientRegistrationFormComponent) {
    return component.isChangesSaved();
  }
}

isChangesSaved() is a method inside the component.

public isChangesSaved(): Observable<boolean> {
    if (this.isDirtyForm()) {
      return this.confirmService.confirmUnsavedChanges();
    }
    return of(false);
  }

This is the error,

ERROR Error: Uncaught (in promise): TypeError: Cannot read property 'isChangesSaved' of null TypeError: Cannot read property 'isChangesSaved' of null at CanDeactivateGuard.push../src/app/core/database/database-cores/registry-core/guards/patient-registration.guard.ts.CanDeactivateGuard.canDeactivate

Here is my module (lazy loaded)

@NgModule({
  imports: [
    CommonModule,
    RegistryRoutingModule,
    ReactiveFormsModule,
    FormsModule,
  ],
  declarations: [
    RegistryContainerComponent,
    RegistryHomeComponent,
    PatientRegistrationFormComponent, // component of interest ….
  ],
  exports: [PatientRegistrationFormComponent],
  schemas: [NO_ERRORS_SCHEMA],
  providers: [
    …fromGaurds.guards,  // all my guards are loaded here….
    { provide: ErrorStateMatcher, useClass: ShowOnDirtyErrorStateMatcher },
    ToastService,
  ],
})
export class RegistryModule {}

and this is my routing module

const routes: Routes = [
  {
    path: '',
    component: RegistryContainerComponent,
    children: [
      {
        path: '',
        component: RegistryHomeComponent,
      },
      {
        path: 'patientregistration',
        component: PatientRegistrationFormComponent,
        canDeactivate: [fromGaurds.CanDeactivateGuard],

        children: [
          {
            path: '',
            component: PatientRegistrationFormComponent,
             canActivate: [fromGaurds.PatientRegistryGuard],
            canDeactivate: [fromGaurds.CanDeactivateGuard],
          },
          {
            path: ':id',
            component: PatientRegistrationFormComponent,
            canActivate: [fromGaurds.PatientRegistryRecordGuard],
            canDeactivate: [fromGaurds.CanDeactivateGuard],
          },
        ],
      },
    ],
  },
];
@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule],
})
export class RegistryRoutingModule {}

is this a bug or wrong implementation of the interface?

Update

I tired adding the guard to the lazy loading module. This is the parent module for the child module RegistryModule mentioned above,

const dbRoutes: Routes = [
  {
    path: '',
    component: DbHomeComponent,
    data: { preload: true },
    canDeactivate: [fromGaurds.CanDeactivateGuard],
    children: [
      { path: '', component: UsersDashboardComponent },
      {
        path: 'registry',
        canActivate: [RoleGuardService],
        data: { expectedRole: { roles: ['reg'] } },
        loadChildren: '../database-cores/registry-core/modules/registry.module#RegistryModule',
      },
      {
        path: 'cloudstorage',
        canActivate: [RoleGuardService],
        loadChildren:
          '../database-cores/cloud-storage-core/modules/cloud-storage.module#CloudStorageModule',
      },
    ],
  },
];
@NgModule({
  imports: [RouterModule.forChild(dbRoutes)],
  exports: [RouterModule],
})
export class DbRoutingModule {}

@NgModule({
  declarations: [
    DbHeaderComponent,
    DbFooterComponent,
    DbHomeComponent,
    UsersDashboardComponent,
  ],
  imports: [

    }),
  ],
  exports: [],
  providers: [
    ...fromGaurds.guards,
    MDBSpinningPreloader,
    ToastService,
    DirectoryService,
  ],
  schemas: [NO_ERRORS_SCHEMA],
})
export class DbModule {}
JSON
  • 1,583
  • 4
  • 31
  • 63
  • The lazy loaded component is null in your guard. Try the following solution(s) https://github.com/angular/angular/issues/16868#issuecomment-331037915 – Jags Aug 20 '18 at 03:47
  • @Jags I tried to load my guard in the parent module (where I am defining my lazy loaded children modules) and it did not work. – JSON Aug 20 '18 at 04:07
  • When you say parent module, which module do you mean? You should be loading the guard in the lazy loaded module imports via the RouterModule.forChild() – Jags Aug 20 '18 at 04:31
  • @jags, I updated my post, is this the proper way you are referring to? – JSON Aug 20 '18 at 04:37
  • According to [Victor Savkin](https://github.com/vsavkin), and I quote >>only routes that have components set should receive them. It's expected behavior. Makes sense no? – JSON Aug 20 '18 at 04:41
  • Apologies I must have misread the first time around, it looks like you already were loading the guard within the lazy loaded module. Did you track down the issue? Do you have a plunkr? – Jags Aug 20 '18 at 23:02
  • no worries, but this is becoming rather confusing to me, here is a [repo](https://stackblitz.com/edit/angular-v9djs1). things just work fine there!, I even loaded the guard in the lazily loaded module. – JSON Aug 21 '18 at 04:57
  • That is strange. The only major difference I can see is how you are providing your guards with the spread operator in the question code snippet. I've had issues with the spread separator and Intellij Intellisense before, but not with the component tree so I can't imagine that would be the cause of the issue. Might be worth a try though. – Jags Aug 21 '18 at 05:20
  • I love it, we are on the same page, I just did. Lol not working. This thing is driving me crazy now – JSON Aug 21 '18 at 05:37
  • I even updated my packages, fresh npm and yet nothing is changing – JSON Aug 21 '18 at 05:39
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/178412/discussion-between-json-and-jags). – JSON Aug 21 '18 at 05:49
  • @Jags, ok sorted out. I don't know if it makes sense but if you noticed that I am using the same component for new/edit operations `PatientRegistrationFormComponent`. removing `canDeactivate` from the children tree does the trick. any idea why? – JSON Aug 21 '18 at 06:04
  • 1
    I mentioned this in the chat, but due to how your routes are set up I think your deactivate guard is firing twice. You probably don't need the child route for edit, just use a route resolver instead if you want to load data. – Jags Aug 21 '18 at 06:11
  • 1
    Thank you, would you please post it as an answer. you earned the points for it. – JSON Aug 21 '18 at 06:14

2 Answers2

1

I modified your stackblitz to reproduce the issue. The core of the issue is that the same deactivate guard for the same component is firing twice. The second time it fires, the component has already been deactivated and no longer exists, and hence the error.

The offending code is in the RegistryRoutingModule. The parent route 'patientregistration' and child route '' are both active at the same time, and use the same guard. If you remove one of the guards it should work. Depending on your use case you could remove the guards from the parent or child, however given the guard is going to cascade, you could remove from all the children.

const routes: Routes = [
  {
    path: '',
    component: RegistryContainerComponent,
    children: [
      {
        path: '',
        component: RegistryHomeComponent,
      },
      {
        path: 'patientregistration',
        component: PatientRegistrationFormComponent,
        canDeactivate: [fromGaurds.CanDeactivateGuard],

        children: [
          {
            path: '',
            canActivate: [fromGaurds.PatientRegistryGuard]
          },
          {
            path: ':id',
            canActivate: [fromGaurds.PatientRegistryRecordGuard]
          },
        ],
      },
    ],
  },
];
Jags
  • 1,639
  • 1
  • 16
  • 30
1

Sorted out. Credit goes to sliceofbytes

The problem also is in duplicating my component in the parent and children paths as well

The following config also works as intended. canDeactivate serves both children paths. I believe the core issue was that I had only one instance of PatientRegistrationFormComponent that served both children routes.

Here is a repo.

const routes: Routes = [
  {
    path: '',
    component: RegistryContainerComponent,
    children: [
      {
        path: '',
        component: RegistryHomeComponent,
      },
      {
        path: 'patientregistration',
        //component: PatientRegistrationFormComponent, <---- here was the core issue
        children: [
          {
            path: '',
            component: PatientRegistrationFormComponent,
             canActivate: [fromGaurds.PatientRegistryGuard],
             canDeactivate: [fromGaurds.CanDeactivateGuard],
          },
          {
            path: ':id',
            component: PatientRegistrationFormComponent,
            canActivate: [fromGaurds.PatientRegistryRecordGuard],
            canDeactivate: [fromGaurds.CanDeactivateGuard],
          },
        ],
      },
    ],
  },
];
@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule],
})
export class RegistryRoutingModule {}
JSON
  • 1,583
  • 4
  • 31
  • 63