Reputation: 300
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
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 subscribe
ing, 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