Angular's *rbacCan: real unmount, not hide
rbac-fs/angular gives you provideRbacClient(), RbacService, and a structural directive, *rbacCan. Structural — meaning it controls whether an element exists in the DOM at all, the same category of directive as *ngIf.
Setup
import { bootstrapApplication } from '@angular/platform-browser';
import { RBACClient } from 'rbac-fs/client';
import { provideRbacClient } from 'rbac-fs/angular';
bootstrapApplication(AppComponent, {
providers: [provideRbacClient(new RBACClient(await fetch('/me/permissions').then((r) => r.json())))],
});The directive
@Component({
standalone: true,
imports: [RbacCanDirective],
selector: 'invoice-actions',
template: `
<button *rbacCan="'invoice'; action: 'approve'">Approve invoice</button>
<button *rbacCan="'expense-report'; action: 'approve'; context: reportContext">
Approve my expense report
</button>
<p>Imperative check: {{ rbac.can('invoice', 'approve') ? 'allowed' : 'denied' }}</p>
`,
})
export class InvoiceActionsComponent {
@Input() ownerId = '';
get reportContext() { return { owner_id: this.ownerId }; }
constructor(public rbac: RbacService) {}
}Why real unmount, not display:none
*rbacCan is built on ViewContainerRef — createEmbeddedView() when the check passes, clear() when it doesn't — the exact same public API Angular's own *ngIf is built on. Unlike Vue's v-can (which toggles display:none deliberately, to mirror v-show), Angular's structural-directive convention is unmount, not hide, and rbac-fs follows that convention rather than introducing a second, non-idiomatic behavior into an Angular codebase. Practical effect: an element gated by *rbacCan that fails the check has its component lifecycle hooks (ngOnDestroy, etc.) actually fire — it's genuinely gone, not just invisible.
For a runnable walkthrough driving RbacService and RbacCanDirective directly, see Tutorial: verifying the Angular adapter without the Angular compiler.