Angular Integration
Fire Shield provides Angular integration with services, directives, and route guards for comprehensive RBAC support.
Features
- Injectable RBACService with RxJS observables
- Structural directives (
*fsCanPermission,*fsHasRole,*fsCannotPermission) - Route guards for protecting routes
- Reactive state management with BehaviorSubject
- Full TypeScript support
- Compatible with standalone components (Angular 14+)
Installation
bash
npm install @fire-shield/angular @fire-shield/coreSetup
1. Initialize RBAC
Create an initialization function:
typescript
// app.config.ts (Angular 17+)
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { RBAC } from '@fire-shield/core';
import { RBACService } from '@fire-shield/angular';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
RBACService
]
};typescript
// main.ts
import { APP_INITIALIZER } from '@angular/core';
import { RBACService } from '@fire-shield/angular';
import { RBAC } from '@fire-shield/core';
export function initializeRBAC(rbacService: RBACService) {
return () => {
const rbac = new RBAC();
// Define roles
rbac.createRole('admin', ['user:*', 'post:*']);
rbac.createRole('editor', ['post:read', 'post:write']);
rbac.createRole('viewer', ['post:read']);
// Set hierarchy
rbac.getRoleHierarchy().setRoleLevel('admin', 10);
rbac.getRoleHierarchy().setRoleLevel('editor', 5);
rbac.getRoleHierarchy().setRoleLevel('viewer', 1);
rbacService.initialize(rbac, null);
};
}
// In providers array:
{
provide: APP_INITIALIZER,
useFactory: initializeRBAC,
deps: [RBACService],
multi: true
}2. Update User on Login
typescript
import { Component, inject } from '@angular/core';
import { RBACService } from '@fire-shield/angular';
@Component({
selector: 'app-login',
template: '<button (click)="login()">Login</button>'
})
export class LoginComponent {
private rbacService = inject(RBACService);
login() {
const user = { id: 'user-1', roles: ['editor'] };
this.rbacService.setUser(user);
}
}RBACService
Injectable service that provides RBAC functionality.
Methods
initialize(rbac: RBAC, initialUser?: RBACUser | null)
typescript
constructor(private rbacService: RBACService) {
const rbac = new RBAC();
this.rbacService.initialize(rbac, null);
}setUser(user: RBACUser | null)
typescript
this.rbacService.setUser({ id: 'user-1', roles: ['editor'] });getUser(): RBACUser | null
typescript
const user = this.rbacService.getUser();can(permission: string): boolean
Synchronous permission check:
typescript
const allowed = this.rbacService.can('post:write');can$(permission: string): Observable<boolean>
Reactive permission check:
typescript
this.rbacService.can$('post:write').subscribe(allowed => {
console.log('Can write:', allowed);
});hasRole(role: string): boolean
Synchronous role check:
typescript
const isAdmin = this.rbacService.hasRole('admin');hasRole$(role: string): Observable<boolean>
Reactive role check:
typescript
this.rbacService.hasRole$('admin').subscribe(isAdmin => {
console.log('Is admin:', isAdmin);
});authorize(permission: string): AuthorizationResult
Get detailed authorization result:
typescript
const result = this.rbacService.authorize('post:delete');
if (!result.allowed) {
console.log('Denied:', result.reason);
}canAll(permissions: string[]): boolean
Check if user has all permissions:
typescript
const hasAll = this.rbacService.canAll(['post:read', 'post:write']);canAny(permissions: string[]): boolean
Check if user has any permission:
typescript
const hasAny = this.rbacService.canAny(['post:read', 'post:write']);Directives
*fsCanPermission
Conditionally renders content based on permission:
html
<div *fsCanPermission="'post:write'">
<button>Create Post</button>
</div>*fsHasRole
Conditionally renders content based on role:
html
<div *fsHasRole="'admin'">
<h2>Admin Panel</h2>
</div>*fsCannotPermission
Inverse conditional rendering (show when permission is NOT present):
html
<div *fsCannotPermission="'premium:access'">
<p>Upgrade to Premium!</p>
</div>Component Examples
Basic Component with Directives
typescript
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import {
RBACService,
CanPermissionDirective,
HasRoleDirective,
CannotPermissionDirective
} from '@fire-shield/angular';
@Component({
selector: 'app-posts',
standalone: true,
imports: [
CommonModule,
CanPermissionDirective,
HasRoleDirective,
CannotPermissionDirective
],
template: `
<h1>Posts</h1>
<!-- Using observable -->
<button *ngIf="canWrite$ | async">Create Post</button>
<!-- Using directive -->
<div *fsCanPermission="'post:write'">
<button>Create Post</button>
</div>
<!-- Role check -->
<div *fsHasRole="'admin'">
<button>Admin Options</button>
</div>
<!-- Cannot check -->
<div *fsCannotPermission="'premium:access'">
<p>Upgrade to access premium features</p>
</div>
`
})
export class PostsComponent {
canWrite$ = this.rbacService.can$('post:write');
isAdmin$ = this.rbacService.hasRole$('admin');
constructor(private rbacService: RBACService) {}
}Complex Dashboard Component
typescript
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import {
RBACService,
CanPermissionDirective,
HasRoleDirective
} from '@fire-shield/angular';
import { Observable } from 'rxjs';
@Component({
selector: 'app-dashboard',
standalone: true,
imports: [
CommonModule,
RouterModule,
CanPermissionDirective,
HasRoleDirective
],
template: `
<h1>Dashboard</h1>
<nav>
<a routerLink="/">Home</a>
<a *ngIf="canManagePosts$ | async" routerLink="/posts">Posts</a>
<a *ngIf="canManageUsers$ | async" routerLink="/users">Users</a>
<a *ngIf="isAdmin$ | async" routerLink="/admin">Admin</a>
</nav>
<div class="actions">
<div *fsCanPermission="'post:write'">
<button (click)="createPost()">Create Post</button>
</div>
<div *fsHasRole="'admin'">
<h2>Admin Controls</h2>
<button (click)="openAdminPanel()">Open Admin</button>
</div>
</div>
<div *fsCannotPermission="'premium:access'" class="upgrade-banner">
<p>Upgrade to Premium for more features!</p>
<button (click)="upgradeToPremium()">Upgrade Now</button>
</div>
`,
styles: [`
.upgrade-banner {
padding: 1rem;
background: #f0f0f0;
border-radius: 8px;
}
`]
})
export class DashboardComponent implements OnInit {
canManagePosts$!: Observable<boolean>;
canManageUsers$!: Observable<boolean>;
isAdmin$!: Observable<boolean>;
constructor(private rbacService: RBACService) {}
ngOnInit() {
this.canManagePosts$ = this.rbacService.can$('post:write');
this.canManageUsers$ = this.rbacService.can$('user:write');
this.isAdmin$ = this.rbacService.hasRole$('admin');
}
createPost() {
console.log('Creating post...');
}
openAdminPanel() {
console.log('Opening admin panel...');
}
upgradeToPremium() {
console.log('Upgrading to premium...');
}
}Form with Conditional Fields
typescript
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import {
RBACService,
CanPermissionDirective,
HasRoleDirective
} from '@fire-shield/angular';
@Component({
selector: 'app-user-form',
standalone: true,
imports: [
CommonModule,
FormsModule,
CanPermissionDirective,
HasRoleDirective
],
template: `
<form (ngSubmit)="onSubmit()">
<input name="name" [(ngModel)]="user.name" placeholder="Name" />
<div *fsCanPermission="'user:edit:email'">
<input
name="email"
[(ngModel)]="user.email"
type="email"
placeholder="Email"
/>
</div>
<div *fsCanPermission="'user:edit:role'">
<select name="role" [(ngModel)]="user.role">
<option value="user">User</option>
<option value="editor">Editor</option>
<option *fsHasRole="'admin'" value="admin">Admin</option>
</select>
</div>
<button type="submit">Save</button>
</form>
`
})
export class UserFormComponent {
user = { name: '', email: '', role: 'user' };
constructor(private rbacService: RBACService) {}
onSubmit() {
console.log('Submitting user:', this.user);
}
}Route Guards
canActivatePermission
Protect routes based on permissions:
typescript
import { Routes } from '@angular/router';
import { canActivatePermission } from '@fire-shield/angular';
export const routes: Routes = [
{
path: 'admin',
component: AdminComponent,
canActivate: [canActivatePermission],
data: { permission: 'admin:access', redirectTo: '/unauthorized' }
}
];canActivateRole
Protect routes based on roles:
typescript
export const routes: Routes = [
{
path: 'admin',
component: AdminComponent,
canActivate: [canActivateRole],
data: { role: 'admin', redirectTo: '/unauthorized' }
}
];canActivateRBAC
Combined guard for both permissions and roles:
typescript
export const routes: Routes = [
{
path: 'admin',
component: AdminComponent,
canActivate: [canActivateRBAC],
data: {
permission: 'admin:access',
role: 'admin',
redirectTo: '/unauthorized'
}
}
];Multiple Permissions/Roles
typescript
export const routes: Routes = [
{
path: 'posts',
component: PostsComponent,
canActivate: [canActivatePermission],
data: {
permissions: ['post:read', 'post:write'],
requireAll: true, // Must have all permissions
redirectTo: '/unauthorized'
}
},
{
path: 'dashboard',
component: DashboardComponent,
canActivate: [canActivatePermission],
data: {
permissions: ['admin:view', 'moderator:view'],
requireAll: false, // Must have at least one permission
redirectTo: '/unauthorized'
}
}
];Complete Route Configuration
typescript
import { Routes } from '@angular/router';
import {
canActivatePermission,
canActivateRole,
canActivateRBAC
} from '@fire-shield/angular';
export const routes: Routes = [
{
path: '',
component: HomeComponent
},
{
path: 'posts',
component: PostsComponent,
canActivate: [canActivatePermission],
data: { permission: 'post:read', redirectTo: '/unauthorized' }
},
{
path: 'admin',
component: AdminComponent,
canActivate: [canActivateRole],
data: { role: 'admin', redirectTo: '/unauthorized' }
},
{
path: 'editor',
component: EditorComponent,
canActivate: [canActivateRBAC],
data: {
permissions: ['post:read', 'post:write'],
role: 'editor',
requireAll: true,
redirectTo: '/unauthorized'
}
},
{
path: 'unauthorized',
component: UnauthorizedComponent
}
];Services
Permission Service
typescript
// services/permissions.service.ts
import { Injectable, inject } from '@angular/core';
import { RBACService } from '@fire-shield/angular';
import { Observable } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class PermissionsService {
private rbacService = inject(RBACService);
canManageUsers(): boolean {
return this.rbacService.can('user:manage');
}
canManageUsers$(): Observable<boolean> {
return this.rbacService.can$('user:manage');
}
canEditPost(post: Post): boolean {
const user = this.rbacService.getUser();
if (!user) return false;
return (
this.rbacService.can('post:edit:any') ||
(post.authorId === user.id && this.rbacService.can('post:edit:own'))
);
}
canDeletePost(post: Post): boolean {
const user = this.rbacService.getUser();
if (!user) return false;
return (
this.rbacService.can('post:delete:any') ||
(post.authorId === user.id && this.rbacService.can('post:delete:own'))
);
}
}HTTP Interceptor
Add RBAC context to HTTP requests:
typescript
// interceptors/rbac.interceptor.ts
import { Injectable } from '@angular/core';
import {
HttpRequest,
HttpHandler,
HttpEvent,
HttpInterceptor,
HttpErrorResponse
} from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { RBACService } from '@fire-shield/angular';
import { Router } from '@angular/router';
@Injectable()
export class RBACInterceptor implements HttpInterceptor {
constructor(
private rbacService: RBACService,
private router: Router
) {}
intercept(
request: HttpRequest<unknown>,
next: HttpHandler
): Observable<HttpEvent<unknown>> {
const user = this.rbacService.getUser();
// Add user roles to header
if (user) {
request = request.clone({
setHeaders: {
'X-User-Roles': user.roles.join(',')
}
});
}
return next.handle(request).pipe(
catchError((error: HttpErrorResponse) => {
if (error.status === 403) {
// Handle forbidden error
this.router.navigate(['/unauthorized']);
}
return throwError(() => error);
})
);
}
}Register interceptor:
typescript
// app.config.ts
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { RBACInterceptor } from './interceptors/rbac.interceptor';
export const appConfig: ApplicationConfig = {
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: RBACInterceptor,
multi: true
}
]
};TypeScript Support
typescript
import type { RBACUser } from '@fire-shield/angular';
interface User extends RBACUser {
email: string;
name: string;
}
const user: User = {
id: 'user-1',
roles: ['editor'],
email: 'user@example.com',
name: 'John Doe'
};
this.rbacService.setUser(user);Best Practices
1. Use Observables for Reactive UI
typescript
export class MyComponent {
canEdit$ = this.rbacService.can$('post:edit');
isAdmin$ = this.rbacService.hasRole$('admin');
constructor(private rbacService: RBACService) {}
}2. Centralize Permission Logic
typescript
@Injectable({ providedIn: 'root' })
export class PermissionsService {
constructor(private rbacService: RBACService) {}
canAccessAdminPanel() {
return this.rbacService.hasRole('admin');
}
}3. Use Route Guards
typescript
const routes: Routes = [
{
path: 'admin',
canActivate: [canActivateRole],
data: { role: 'admin' }
}
];Next Steps
- Explore API Reference
- Learn about Permissions
- Check out Examples
