In my AuthGuard, I have implemented CanActivate which returns either true or false based on custom logic:
import { Injectable } from '@angular/core';
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { AuthService } from '../services/auth.service';
import { UserTypes } from '../model/user-type';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(
private router: Router,
private authService: AuthService
) {
}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const allowedRoles = route.data['allowedRoles'] as Array<UserTypes>;
if (!allowedRoles) {
// All users allowed
return true;
}
// Check user's role against the allowed roles defined
const canActivate = (allowedRoles.indexOf(this.authService.userData.UserTypeId) !== -1);
if (!canActivate) {
this.router.navigate(['/portal']);
}
return canActivate;
}
}
I require both canActivate
parameters (
route: ActivatedRouteSnapshot, state: RouterStateSnapshot
) for processing (not all code is shown).
My routes are set up with specific data indicating which user types can access a particular route. This works perfectly when attempting to access routes:
const routes: Routes = [
{
path: '',
component: PortalLayoutComponent,
children: [
{
path: '',
canActivate: [AuthGuard],
component: PortalDashboardComponent
},
{
path: 'customers',
canActivate: [AuthGuard],
data: { allowedRoles: [UserTypes.Admin] },
children: [
{ path: '', component: PortalCustomerListComponent }
]
}
]
}
];
If canActivate
returns false for any given link, I want to hide that link in the template. I am unsure of how to achieve this. Here's an example of a typical link in a template:
<a [routerLink]="['customers']">List Customers</a>
How can I disable this without duplicating the user type logic present in the AuthGuard? I have tried injecting AuthGuard into my components but struggle with providing the necessary params for canActivate
. Below is a sample component with some test code I am working on:
import { Component } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Constants } from '../../app.constants';
import { AuthGuard } from '../../guards/auth.guard';
@Component({
templateUrl: './portal-dashboard.component.html',
styleUrls: ['./portal-dashboard.component.scss']
})
export class PortalDashboardComponent {
constructor(
private authGuard: AuthGuard,
private activatedRoute: ActivatedRoute,
private router: Router
) { }
public get canActivateLink(targetUrl: string): boolean {
**// TODO: Use targetLink here to get user role rules from the route data?**
return this.authGuard.canActivate(this.activatedRoute.snapshot, this.router.routerState.snapshot);
}
}
If I am able to make the above code work, I could simply do the following in the template (although not completely DRY, it is better than repeating the role logic in every link controller):
<a [routerLink]="['customers']" *ngIf="canActivateLink('customers')">List Customers</a>
UPDATE
Thanks to Yordan, I have managed to turn this into a directive. However, I still face the same challenge of easily obtaining Route information (specifically the route data) for a specific URL. Here's where I stand currently:
import { Input, OnInit, Directive, ViewContainerRef, TemplateRef } from '@angular/core';
import { LocationStrategy } from '@angular/common';
import { Router, ActivatedRoute, UrlTree } from '@angular/router';
import { AuthService, AuthState } from '../../services/auth.service';
import { UserTypes } from '../../model/user-type';
@Directive({
selector: '[hiddenIfUnauthorised]'
})
export class HiddenIfUnauthorisedDirective implements OnInit {
private commands: any[] = [];
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef,
private locationStrategy: LocationStrategy,
private router: Router,
private route: ActivatedRoute,
private auth: AuthService
) { }
@Input()
set hiddenIfUnauthorised(commands: any[] | string) {
if (commands != null) {
this.commands = Array.isArray(commands) ? commands : [commands];
} else {
this.commands = [];
}
console.log(this.commands);
}
get urlTree(): UrlTree {
// TODO: Mimic the rest of the relevant options as per the RouterLink source
return this.router.createUrlTree(this.commands, {
relativeTo: this.route
});
}
public ngOnInit() {
const urlTree = this.urlTree;
const sUrl = this.router.serializeUrl(urlTree);
const url = this.locationStrategy.prepareExternalUrl(sUrl);
// TODO: I need to generate an ActivatedRoute object for the "url" created above
// so I can get 'allowedRoles' from route data - or get it some other way.
// Are there any helper methods anywhere?
const targetRoute = this.route;
const userTypes = targetRoute.data['allowedRoles'] as Array<UserTypes>;
const authState = this.auth.getAuthState(userTypes);
if (authState !== AuthState.Authorised) {
// Not authorised, remove the DOM container
this.viewContainer.clear();
} else {
// Show the DOM container
this.viewContainer.createEmbeddedView(this.templateRef);
}
}
}
This directive is registered in my module and utilized in a template like so:
<div *hiddenIfUnauthorised="['customers']">
Secure Content!
</div>
Still searching for a solution. Any ideas would be greatly appreciated.