4

I have a router guard called PermissionGuard which is initiated here

const routes: Routes = [
  {
    path: ':company',
    component: CompanyComponent,
    canActivate: [PermissionGuard],
    canActivateChild: [PermissionGuard],
    children: [
      {
        path: '',
        component: IndexComponent
      },
      {
        path: 'projects',
        loadChildren: '../projects/projects.module#ProjectsModule'
      },
    ]
  }
];

Inside my PermissionGuard I subscribe to a PermissionService like this:

export class PermissionGuard implements CanActivate, CanActivateChild {

  private permissions: Permission[];

  constructor(private permissionService: PermissionService, private router: Router) {
    this.permissionService.permissions$.subscribe(
      (permissions: Permission[]) => {
        this.permissions = permissions;
      }
    );
  }

  canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
    this.permissionService.getPermissions(next.paramMap.get('company'));
    return this.permissionService.permissionsFetchDone$
    .pipe(filter(x => x))
    .pipe(
      map(response => {
        if (this.permissions) {
          return true;
        } else {
          this.router.navigate(['/forbidden']);
        }
      })
    );
  }
}

and perform necessary canActivate or canActivateChild checks based on this data. From adding console.log() and emitting new data from permissions$ inside the children routes I can see that the observable is still active, even though the guard has been "used" and the route is activated. I was then expecting it to disappear when I go to a route outside of path: ':company', however the guard isn't destroyed.

This brings me to my question: Am I doing this correctly? I wish to use a guard to check if the user has any permissions, but at the same time I want to perform the HTTP request for permissions only once (when navigating to path: ':company' or any of its children). I'm afraid that if I use guards like this it will in time slow down the entire application due to massive amounts of observers.

Gjert
  • 1,069
  • 1
  • 18
  • 48

1 Answers1

3

emitting new data from permissions$ inside the children routes I can see that the observable is still active, even though the guard has been "used" and the route is activated.

First of all, permissions$ is still active because you never unsubscribed in your guard. And angular creates the guard as singleton.

Even if the guard were not a singleton, the guard's instance is still referenced in the subscription via this.permissions, and if your observable exists as variable in your auth service (which I assume is a singleton) this binding would also prevent garbage collection.

This brings me to my question: Am I doing this correctly? I wish to use a guard to check if the user has any permissions,

It is fine totally fine to issue a request in your guard e.g. for getting permissions.

but at the same time I want to perform the HTTP request for permissions only once (when navigating to path: ':company' or any of its children).

If you only want to issue the request once then you should think about using shareReplay in your auth service so all future instances that connect use the same previously emitted values.

Felix K.
  • 14,171
  • 9
  • 58
  • 72
  • I only want it to be issued once per `:company` id and its navigation in the children routes, aka. when the company changes, a new http request is required. However, I was mostly concerned about the amount of observables that would appear, however, as guards are singletons I dont see any other option to be honest. Or does angular guards have a version of ngAfterActivation where I can unsubscribe? – Gjert Oct 22 '18 at 19:51
  • the guard's `can` will be called everytime when you navigate in routes for which the guard is registered (e.g. `:company`), therefore you could subscribe in your `can` method and not store your result in `this.permissions` and then unsubscribe (you only need to unsubscribe though if your observable is held as variable in your auth service though). – Felix K. Oct 22 '18 at 20:05
  • "Or does angular guards have a version of ngAfterActivation where I can unsubscribe?" not really; you could use the `CanDeactivate` interface but this is actually for deciding if a route can be deactivated, not for unsubscribing. – Felix K. Oct 22 '18 at 20:08