0

I have a PolicyGuard with a method canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot).

In canActivate I create an observable, and inside of this observable I subscribe to an Observable that I retrieve from my AbilityService.

The observable I retrieve from AbilityService listens for changes to a users "policies"/"abilities".

This gets pretty messy when I am making changes to a user's "policies" and pushing those changes to the client using Socket.io as page re-direction to the home page can happen when I don't want it to (One subscription runs obs.next(true) while another subscription routes to home and runs obs.next(false))

Is there a way I can make the subscription unsubscribe when leaving the page that the subscription was meant for?

@Injectable()
export class PolicyGuard implements CanActivate {

    constructor(protected router: Router, private abilityService: AbilityService) {}

    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
        console.log("PolicyGuard() - canActivate()");

        return new Observable<boolean>(obs => {
            const observable = this.abilityService.getAbility();
            observable.subscribe(ability => {
                const can = ability.can(route.data.action, route.data.subject);

                if (can) {
                    obs.next(true);
                }
                else {
                    this.router.navigate(['/home']);
                    obs.next(false);
                }
            });
        });
    }
}
dgero95
  • 64
  • 10
  • Try rxjs take(1) with pipe before subscribe. It will take one emission and unsubscribe – Mehyar Sawas Dec 16 '21 at 18:49
  • I tried that. The problem with that is, if you're on a page in the application that you no longer have access to (due to the users policies being updated) then the page doesn't kick you back to home. I was thinking about using something like takeUntil() or takeWhile() somehow, but not sure what to use as a condition. – dgero95 Dec 16 '21 at 18:52

1 Answers1

0

You have currently created an additional Observable which is in my opinion not required. You could simply return this.abilityService.getAbility() and transform the return value with the RxJS map operator. This way Angular would take over and handle the subscription from your Observable.

This would look like this:

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
  console.log("PolicyGuard() - canActivate()");

  return this.abilityService.getAbility().pipe(
    map(ability => {
      const can = ability.can(route.data.action, route.data.subject);
     
      if (!can) {
        this.router.navigate(['/home']);
      }
      return can;
    });
  );
}

The example for canActivate on angular.io does something simliar, just without the map operator.

Batajus
  • 5,831
  • 3
  • 25
  • 38
  • With this solution I experience the same issue as using `take(1)` where if a user is on a page they no longer have access to (due to user policy/ability being updated) the user is not routed back to the home page. So I guess, somehow I could just make the `canActivate()` method fire again after user rules get updated without refreshing the page. – dgero95 Dec 16 '21 at 19:42
  • Do you mean with not kicked home, that the policy changes but the user is not directly navigated to home? – Batajus Dec 16 '21 at 19:46
  • Yes because the subscription has ended after it runs once, and the route guard doesn't get called again when a user's rules update via Socket.io. Is there a way to trigger the `canActivate()` method to run again without refreshing the page? – dgero95 Dec 16 '21 at 20:14