1

We can check (restrict/allow) an access to a module in Angular RouterModule using Guards, implementing an canActivate() method. The method can use a business logic to make some checks, lets call it checkAccess() method. And return a boolean observable.

Imagine, a user visits a module, lets call it Private Page Module, which is protected by a guard (lets call it CheckAccessGuard) and in the canActivate() which returns an observable of a service variable (lets call it haveAccess. If canActivate() emits a true value - then the user gets access to the Private Page Module. If false is emitted - let's redirect user to some 403 Access Denied Page.

On the Private Page Module, lets add a button, which sets haveAccess variable to false. So the next emission by the canActivate() should return false and the user should be redirected. User click on the button - and nothing happens. Because the user is already on the Private Page Module. And the guard was already checked. But I would like to redirect the user to the 403 Access Denied Page, by executing the guard logic again. And without adding extra code on the Private Page Module. (So the business logic can be added only in the guard, because the G guard should check the access). But the guard checks the access only before accessing the page and does not check the access, when the user already is on the page.

I will post some code snippets as an example:

Routes configuration:

const routes: Routes = [
  {
    path: 'private-area',
    loadChildren: () =>
      import('./pages/private-page.module').then((m) => m.PrivatePageModule),
    canActivate: [CheckAccessGuard],
  }
]

CheckAccessGuard:

...
constructor(private checkAccessService: CheckAccessService) {}

canActivate(): Observable<boolean> {
    return of(this.checkAccess())
}

checkAccess(): boolean {
    return this.checkAccessService.haveAccess;
}
...

CheckAccessService:

export class CheckAccessService {
    public haveAccess = true;
}

PrivatePageModule default component:

(.html)
<button (click)="checkAccessService = false">

(.ts)
constructor(public checkAccessService: CheckAccessService) {}
Sergej
  • 2,030
  • 1
  • 18
  • 28
  • Is reloading the page not an option? Or change the path a little bit so the `routeGuard` checks again? – akop Feb 02 '23 at 12:32
  • No, Angular app should not be reloaded, thats for sure. And Yes, the Guard should check again, but how it can check again ? I know the condition, the parameters is changed, I can even get an event of the parameter change, but how can I execute the guard logic again? – Sergej Feb 02 '23 at 12:35
  • Does this answer your question? [How to reload current route guards?](https://stackoverflow.com/questions/46521638/how-to-reload-current-route-guards) – akop Feb 02 '23 at 12:42
  • setup the button to navigate the user to the restricted page? – Wen W Feb 02 '23 at 12:43
  • no, both comments does not fit my requirements. Because both comments (including the another question with an answer) - offers to add an extra business logic in the component. The component should not know anything about access/redirects and can have different guard at all later. I want to setup this on the guard or on the router level, but the guard seems to be a better way. since the logic is inside it. – Sergej Feb 02 '23 at 12:49
  • @Sergej why cant inject router constructor and redirect as per business logic. take a look here. https://stackoverflow.com/a/40046843/5011051 – Kesavan R Feb 02 '23 at 13:04
  • The problem is that the canActivate() method, which checks the access condition is called before you enter the page. But when you are already on the protected page the canActivate() is not called anymore, even if the access condition has changed. DI does nothing here. – Sergej Feb 02 '23 at 13:08
  • There is another guard for leaving a page. It's called [CanDeactivate](https://angular.io/api/router/CanDeactivate). – Octavian Mărculescu Feb 02 '23 at 15:51
  • *when you are already on the protected page the canActivate() is not called anymore*, sounds like you want canActivateChild? – Andrew Allen Feb 02 '23 at 15:53
  • No. Listen again. The `canAcrivate` method check SOME CONDITIONS, and then resolves true, you enter the page. And then, when you are on the page after a while, those CONDITIONS that was in the canActivate - changed. So I want canActivate to re-check them and redirect, if there is no access anymore. – Sergej Feb 06 '23 at 18:10

1 Answers1

0

If you want to redirect the user when his access changes but keep all the logic inside of the guard, You will probably need an obversable for the user's access status you can listen to. Then one way would be to make your guard subscribe to that observable, and redirect the users if he his currently in a guarded route.

Something like this.

...
constructor(
    private checkAccessService: CheckAccessService
    private router: Router) {
    
    initGuard()
}

canActivate(): Observable<boolean> {
    return of(this.checkAccess())
}

checkAccess(): boolean {
    return this.checkAccessService.haveAccess;
}

initGuard(){
    this.checkAccessService.haveAccessSubject.subscribe(haveAccess => {
      let currentRoute = this.router.routerState.snapshot.root.firstChild;
      if (haveAccess == false && currentRoute?.routeConfig?.canActivate?.includes(CheckAccessGuard)) {
        this.router.navigate(["/forbidden"])
      }
    })
}
...
Sleepwalker
  • 803
  • 3
  • 11
  • This is the most relevant answer to my problem. But isn't the guard service is destroyed after it is used? How can you offer to handle to unsubscribe from `haveAccessSubject`? How can you manage when several pages are using the same guard? – Sergej Feb 06 '23 at 18:43