Zackorrigan
Zackorrigan

Reputation: 300

Cannot read property 'subscribe' of undefined when calling subcribe on function

I'm working with a jwt token and I need to know the role of the user to decide which routing is allowed on the client side and which menu I show in the navbar.

All of this is saved in a service which should tell the app-routing module if the user is allowed to access this route and the navbar if it should show the menu.

Here is the existing solution which works:

security.service.ts

import { ApplicationRef, EventEmitter, Injectable, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from "rxjs";
import { ActivatedRouteSnapshot, RouterStateSnapshot } from "@angular/router";
import { LoggingUtil } from "../utils/logging.util";
import { ServerUtil } from "../utils/server.util";
import { UserRoleModel } from "../model/models-generated";
import { Log } from "@angular/core/testing/src/logger";

@Injectable({
  providedIn: 'root'
})
export class SecurityService {

  constructor(private client: HttpClient) {}

  canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
    if (next.data.permission.includes(SecurityService.AVA_PERMISSION)) {
      this.getUserRoles().subscribe((result) => {
        return of(result.hasAvaRole);
      });
    } else if (next.data.permission.includes(SecurityService.ADMIN_PERMISSION)) {
      this.getUserRoles().subscribe((result) => {
        return of(result.hasAdminRole);
      });
    } else if (next.data.permission.includes(SecurityService.USER_PERMISSION)) {
      this.getUserRoles().subscribe((result) => {
        return of(result.hasUserRole);
      });
    }
    return of(false);
  }



  public getUserRoles(): Observable<UserRoleModel> {
    let serverUrl = ServerUtil.GetRestApiURL() + '/role';
    return this.client.get<UserRoleModel>(serverUrl);
  }


navbar.component.ts

import {Component, OnInit} from '@angular/core';
import {SecurityService} from "../../services/security.service";
import {Observable} from "rxjs";


@Component({
  selector: 'app-navbar',
  templateUrl: "./navbar.component.html",
  styleUrls: ['./navbar.component.css']
})
export class NavbarComponent implements OnInit {
  isAdmin: boolean;

  constructor(private securityService: SecurityService) {}


  ngOnInit() { 
    this.securityService.getUserRoles().subscribe(value => {
      this.isAdmin = value.hasAdminRole;
    })
  }
}


The problem with this version is that canActivate() will called at every route changes and therefore getUserRoles() too.

My goal was to do that you could either do it the old way or store the value. The switch is done with the helps of a spring profile, therefore I changed the code as following:

security.service.ts

export class SecurityService {
  hasAvaRole = false;
  hasAdminRole = false;
  hasUserRole = false;
  profiles: string[] = [];
  profileLoaded = false;

  // previously getUserRoles
  loadRoles(): Observable<UserRoleModel> {
    let serverUrl = ServerUtil.GetRestApiURL() + '/' + this.ACTION_PATH;
    LoggingUtil.debug("posting to remote server : " + serverUrl);
    return this.client.get<UserRoleModel>(serverUrl);
  }

  loadProfiles(): Observable<string[]> {
    let serverUrl = ServerUtil.GetRestApiURL() + '/' + this.PROFILE_PATH;
    LoggingUtil.debug("calling remote server : " + serverUrl);
    LoggingUtil.debug('client info :');
    console.log(this.client);
    return this.client.post<string[]>(serverUrl, null);
  }

  private getUserRolesSync(): UserRoleModel {
    return {'hasAvaRole': this.hasAvaRole, 'hasAdminRole': this.hasAdminRole, 'hasUserRole': this.hasUserRole}
  }


  getUserRoles(): Observable<UserRoleModel> {
     // if roles aren't in memory we load them
    if (!this.profileLoaded) {
      this.loadProfiles().subscribe((profiles: string[]) => {
        this.profiles = profiles;
        this.profileLoaded = true;
        if (this.profiles.includes('saving-role')) {
          this.loadRoles().subscribe(result => {
            this.hasAvaRole = result.hasAvaRole;
            this.hasAdminRole = result.hasAdminRole;
            this.hasUserRole = result.hasUserRole;
            return of(this.getUserRolesSync());
          });
        } else {
          return this.loadRoles();
        }
      });
    } else {
      if (this.profiles.includes('saving-role')) {
        return of(this.getUserRolesSync());
      } else {
        return this.loadRoles();
      }
    }

At first I thought that the httpClient wasn't injected but by printing it I saw that it wasn't the case. The spring profiles is properly loaded as well.

I now get the error "Cannot read property 'subscribe' of undefined" in navbar.component.ts at the line where I subscribe to the function

Upvotes: 1

Views: 669

Answers (1)

SiddAjmera
SiddAjmera

Reputation: 39432

The issue is here in the getUserRoles. If the user goes to the first if condition, then the control will go inside a subscribe block. But you haven't returned anything from there. So you'll have to return from there. And instead of subscribeing, you'll have to pipe and switchMap to switch the context to the inner Observable:

getUserRoles(): Observable < UserRoleModel > {
  // if roles aren't in memory we load them
  if (!this.profileLoaded) {
    return this.loadProfiles().pipe(
      switchMap(profiles: string[]) => {
        this.profiles = profiles;
        this.profileLoaded = true;
        if (this.profiles.includes('saving-role')) {
          this.loadRoles().pipe(
            switchMap(result => {
              this.hasAvaRole = result.hasAvaRole;
              this.hasAdminRole = result.hasAdminRole;
              this.hasUserRole = result.hasUserRole;
              return of(this.getUserRolesSync());
            })
          );
        } else {
          return this.loadRoles();
        }
      })
    );
  }
  else {
    if (this.profiles.includes('saving-role')) {
      return of(this.getUserRolesSync());
    } else {
      return this.loadRoles();
    }
  }
}

Upvotes: 1

Related Questions