Angular Enterprise Dashboard - Phase 2.5: Role-Based Access Control with Custom Directives

We’ve secured our routes with Functional Guards, but what about the UI itself? An Admin should see the “Delete” button, but a standard User should not.
Authorization Beyond the Route
In the final post of our Phase 2 series, we’ll implement Role-Based Access Control (RBAC) directly in our templates using a custom Structural Directive.
1 🏗️ The Goal: Clean Template logic
We want to be able to hide or show parts of our UI based on the user’s role with a syntax as simple as this:
<button *appHasRole="'ADMIN'" (click)="deleteEverything()">Danger Zone</button>2 🛠️ Building the HasRoleDirective
A structural directive manages how a template is rendered. In our case, we want to render the template only if the user has the required role.
2.1 The Secret Ingredient: Signals + Effects
Because our AuthService uses Signals, we can use an effect inside our directive. This means the UI will automatically show or hide elements if the user’s role changes during their session (e.g., if their session expires or they log in as a different user).
@Directive({ selector: '[appHasRole]', standalone: true })
export class HasRoleDirective {
private readonly templateRef = inject(TemplateRef<unknown>);
private readonly viewContainer = inject(ViewContainerRef);
private readonly authService = inject(AuthService);
readonly roles = input.required<UserRole | UserRole[]>({
alias: 'appHasRole',
});
constructor() {
effect(() => {
// 1. Reactive check
const hasPermission = this.authService.hasRole(this.roles());
// 2. DOM Management
this.viewContainer.clear();
if (hasPermission) {
this.viewContainer.createEmbeddedView(this.templateRef);
}
});
}
}2.2 Under the Hood: How the * Microsyntax Works
When you write:
<li *appHasRole="[UserRole.ADMIN, UserRole.MANAGER]">...</li>Angular desugars the * prefix into an explicit <ng-template>:
<ng-template [appHasRole]="[UserRole.ADMIN, UserRole.MANAGER]">
<li>
<a routerLink="/projects" routerLinkActive="active">
<span class="nav-icon">📁</span> Projects
</a>
</li>
</ng-template>This is what allows Angular to provide the two key tokens to our directive via inject():
| Injected Token | What It Is |
|---|---|
TemplateRef | A reference to the implicit <ng-template>. It holds the blueprint of the <li> and everything inside it — but it’s not rendered yet. Think of it as the what to render. |
ViewContainerRef | A reference to the insertion point in the DOM where the template was declared. It’s the “slot” where we can stamp out (or remove) the template. Think of it as the where to render. |
Then the two lines in our effect() become crystal clear:
// Stamp the blueprint into the DOM slot → shows the <li>
this.viewContainer.createEmbeddedView(this.templateRef);
// Remove everything from the slot → hides the <li>
this.viewContainer.clear();3 📉 Logic Flow: Permission Handling
graph TD
Trigger[State Change / Directive Init] --> Check[AuthService.hasRole]
Check --> Result{Has Permission?}
Result -- Yes --> Render[viewContainer.createEmbeddedView]
Result -- No --> Clear[viewContainer.clear]
Render --> UI[Template Shown]
Clear --> UI2[Template Hidden]
4 🎓 The Teaching Moment: Structural vs. Attribute
Many beginners try to hide elements using CSS ([style.display]="...").
Why the Directive is better: Structural directives actually remove the element from the DOM.
- Security: The hidden HTML doesn’t exist in the browser’s DOM tree at all.
- Performance: Angular doesn’t need to run change detection on child components that aren’t rendered.
5 🎉 Mission Accomplished: Phase 2 Wrap-up
We have traveled a long way in Phase 2:
- Reactive State: Mastered Signals in our AuthService.
- Navigation Security: Implemented Functional Guards.
- App Architecture: Built a scalable App Shell.
- Visual Excellence: Applied Glassmorphism and Design Tokens.
- Granular Authorization: Created our
HasRoleDirective.
The foundation is now rock-solid. We are ready for Phase 3, where we’ll bring our dashboard to life with real-time data visualization and the new Angular Resource API.
Thank you for following this series! You can find all the code discussed in these posts in the Angular Enterprise Dashboard repository.