Search code examples
angularrxjs6angular-observableangular-guards

Subscribing to observables in route guards and its implications


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.


Solution

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