0
votes

I have an Angular application with in it a UserService and a guard. That guard is using the UserService to create a user and using that user based on its role to decide to return true or false. See the following code:

This is the guard:

  canActivate(): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {

  return new Observable<boolean>(obs => {
    this.userService.getUser().subscribe(user => {
      if (user.roles.includes(Roles.IT_PROFESSIONAL)) {
        obs.next(true);
      } else {
        this.router.navigate(['/']);
        obs.next(false);
      }
    });
  });
}

This is the UserService Class:

  private static user: User;

  public getUser(): Observable<User> {

    if (!UserService.user) {

        UserService.user = new User();
        this.getGroups()
        .subscribe((response: any) => {
          response.value.map((v: any) => {  
            this.setRol(v); // use v to push v to the role array of the user
          });

          return of(UserService.user);
        });
    }
    else{
      return of(UserService.user);
    }
  }

    private getGroups(): Observable<any> { // Get groups for user
      return this.http.get("https://graph.microsoft.com/v1.0/me/memberOf");
    }

When I run the app it says "Cannot read property 'subscribe' of undefined". The problem is that the guard subscribes on the getUser observable, but this that I gets the user observable immediately. Why doesn't it wait for the UserService to return the user with the statement "return of(UserService.user)"?

1
thanks for your answer! Now it says this "Cannot set property 'roles' of undefined" in the setRole method which basically does this: " if(v.mailNickname == "Employees") { UserService.user.roles = [Roles.IT_PROFESSIONAL]; }" - M Yil

1 Answers

1
votes

I don't see the need to subscribe to the observable in both instances. You could modify the notification using map or perform side-effects using tap operator and simply forward the observable. Try the following

Guard

canActivate(): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
  return this.userService.getUser().map(user => {
    if (user.roles.includes(Roles.IT_PROFESSIONAL)) {
      return true;
    }
    this.router.navigate(['/']);
    return false;
  });
}

UserClass service

public getUser(): Observable<User> {
  return (!!UserService.user)
    ? of(UserService.user)
    : this.getGroups().pipe(
        map((response: any) => // <-- not sure if `response` is modified here, if not use `tap`
          response.value.map((v: any) => this.setRol(v))
        )
      );
}

Update: Initializing UserService.user

private static user: User;    // <-- don't initialize here
public getUser(): Observable<User> {
  return (!!UserService.user)
    ? of(UserService.user)
    : this.getGroups().pipe(
        tap((response: any) =>
          response.value.forEach((v: any) => this.setRol(v))
        )
      );
}

setRol(v: any) {
  if (!UserService.user) UserService.user = { /* valid user object */ } // <-- initialize here
  if (v.mailNickname == "Employees")  { 
    UserService.user.roles = [Roles.IT_PROFESSIONAL]; 
  }
}