1

I am trying to implement a secure route based on the right of the user. A user who doesn't have a specific right can't access that route. That's why I am passing the Right Name in a route (lazy module routing). But, I found that it's a bit complicated to get lazy route data. I had to subscribe to router events. But, after subscribing I am not finding a way to return false or true to Canactivate.

Here is my canActivate code:

 canActivate() {
    this.router.events
      .pipe(
        filter((event) => event instanceof NavigationEnd),
        map(() => this.activatedRoute),
        map((route) => {
          while (route.firstChild) {
            route = route.firstChild;
          }
          return route;
        }),
        mergeMap((route) => route.data))
      .subscribe((event) => {
        const right = event.right; // getting the right name from the route data. 
        const rights = this.localStorageService.getLocalStorageItem('rights');

        if (right) {
          if (rights.includes(right)) {
            // I need to return true from here 
          } else {
            // I need to return false from here 
          }
        }
      });
  }

And, this is my route code:

const routes: Routes = [{ path: ':id', component: ProfileComponent, 
data: { right: 'VIEW_PROFILE' }, canActivate: [RightRouteGuardService]}];
Shofol
  • 693
  • 1
  • 11
  • 26

1 Answers1

1

An Angular route guard can return Observable<boolean> | Promise<boolean> | boolean. I think you want to return a boolean but if I were you in this case, I would return Observable<boolean>.

Try this:

canActivate(): Observable<boolean> {
  return this.router.events
      .pipe(
        filter((event) => !!event), // !! add this filter !!
        filter((event) => event instanceof NavigationEnd),
        map(() => this.activatedRoute),
        map((route) => {
          while (route.firstChild) {
            route = route.firstChild;
          }
          return route;
        }),
        mergeMap((route) => route.data),
        // map will return true false for us
        map(event => {
          const right = event.right; // getting the right name from the route data. 
          const rights = this.localStorageService.getLocalStorageItem('rights');
          if (right) {
            if (rights.includes(right)) {
              return true;
            } else {
              return false;
            }
          }
        }),
      );
}

You don't need to subscribe anymore, just the first emission from this observable will return the true and false for you.

========================= Edit ========================== Don't inject the router and listen to the events because the router has not completed navigation yet and your filter of NavigationEnd will not allow it to proceed as well as there are no events at that moment in time.

I have solved the problem like so:

import { Injectable } from "@angular/core";
import {
  ActivatedRouteSnapshot,
  CanActivate,
  RouterStateSnapshot
} from "@angular/router";

@Injectable({
  providedIn: "root"
})
export class RightRouteGuardService implements CanActivate {
  constructor() {}

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean{
    let right = route.data.right;
    console.log({ right });
    const rights = ['VIEW_STUDENT'];
    if (rights.includes(right)) {
      return true;
    } else {
      return false;
    }
  }
}

The canActivate method takes two properties and you can use the properties to read the data property of the route in question.

I got inspired by this post.

AliF50
  • 16,947
  • 1
  • 21
  • 37
  • If I don't subscribe the event is showing undefined and the code is just stuck there. In this line the event is undefined. filter((event) => event instanceof NavigationEnd), – Shofol Apr 15 '21 at 16:14
  • Check out my edit with `!! add this filter !!` to filter out the undefined emission. – AliF50 Apr 15 '21 at 16:22
  • Still not working. I have created a stackblitz. Can you check please if you have time- https://stackblitz.com/edit/angular-route-right?file=src/app/features/student/student-routing.module.ts – Shofol Apr 15 '21 at 16:35
  • 1
    Yes, because it got stuck on right-route-guard.service.ts. That's what I was saying about getting undefined and stuck. You can debug from devtools – Shofol Apr 15 '21 at 17:04
  • Check out my edit, I think that's what you're looking for. – AliF50 Apr 16 '21 at 12:51
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/231217/discussion-between-shofol-and-alif50). – Shofol Apr 16 '21 at 13:35
  • I'm trying to use this answer (it's first part) and I wonder, why the ```map((route) =>``` part is never called, at least on my side (tried with breakpoint and ```console.log```). Could you try to help me with this? Interesting is that when I subscribe, the subscription is called without any problem. – Frimlik Apr 05 '22 at 12:09
  • @Frimlik If you subscribe, and it goes inside of the `map` but not when you don't subscribe, I am thinking the configuration of `canActivate: [ThisGuard]` is wrong or the routing configuration is wrong. I am not entirely sure. – AliF50 Apr 05 '22 at 13:01
  • My previous comment was a little misleading. I then found your note after edit - as you pointed out, "there are no events at that moment", so there is no event to catch, so neither pipe nor subscribe are called. I tried (just for fun) to return ```true``` from canActivate and was surprised that ```pipe``` and ```subscribe``` "started working"... – Frimlik Apr 05 '22 at 13:20
  • @Frimlik, ok, interesting. I think the `subscribe` happens at a later point in time after the `canActivate` returns `true`. – AliF50 Apr 05 '22 at 13:48