Defining Abilities
Abilities are the core building block of ng-ability. Each ability class handles the permission logic for a specific resource or concern.
Ability Context
Before defining abilities, you need an ability context — a service that provides the current user (or subject) as a Signal.
import { Injectable, inject } from '@angular/core'
import { toSignal } from '@angular/core/rxjs-interop'
import { AbilityContext } from 'ng-ability'
@Injectable({ providedIn: 'root' })
export class AbilityUserContext implements AbilityContext<User> {
readonly #auth = inject(AuthService)
readonly abilityContext = toSignal(this.#auth.getCurrentUser())
}The AbilityContext<S> interface requires a single property:
interface AbilityContext<S> {
abilityContext: Signal<S>
}If your auth service already exposes a signal, you can use it directly:
@Injectable({ providedIn: 'root' })
export class AbilityUserContext implements AbilityContext<User> {
readonly abilityContext = inject(AuthService).currentUser
}The @AbilityFor Decorator
The @AbilityFor() decorator registers one or more matchers for an ability class. When can() is called, ng-ability finds the ability whose matchers match the first argument.
import { AbilityFor, Ability } from 'ng-ability'
@AbilityFor('Article')
export class ArticleAbility implements Ability<User, Article> {
can(currentUser: User | null, action: string, article?: Article): boolean {
// ...
}
}Matchers
Three types of matchers are supported:
String matcher
The simplest form — a string key that you pass to can():
@AbilityFor('Article')
export class ArticleAbility implements Ability<User> { ... }abilityService.can('Article', 'create')Class matcher
Pass a class constructor. ng-ability uses instanceof checks automatically:
@AbilityFor(Article)
export class ArticleAbility implements Ability<User, Article> { ... }const article = new Article(...)
abilityService.can(article, 'edit') // matched via instanceof ArticleFunction matcher
A predicate that receives the thing and returns true if the ability applies:
@AbilityFor(article => article.__typename === 'Article')
export class ArticleAbility implements Ability<User, Article> { ... }Useful for GraphQL responses and other data without a class hierarchy.
GraphQL recipe
See the GraphQL Integration recipe for a reusable gqlMatcher() factory and combined string + function matcher patterns.
Multiple matchers
You can combine matchers on a single ability class:
@AbilityFor(Article, 'Article', article => article.__typename === 'Article')
export class ArticleAbility implements Ability<User, Article> {
can(currentUser: User | null, action: string, article?: Article): boolean {
if (currentUser?.admin) return true
switch (action) {
case 'view': return true
case 'create': return currentUser != null
case 'edit': return currentUser != null && currentUser.id === article?.authorId
default: return false
}
}
}Full Example
import { AbilityFor, Ability } from 'ng-ability'
interface User {
id: number
admin: boolean
}
class Article {
constructor(
public id: number,
public title: string,
public authorId: number,
) {}
}
@AbilityFor(Article, 'Article', (obj: any) => obj.__typename === 'Article')
export class ArticleAbility implements Ability<User, Article> {
can(currentUser: User | null, action: string, article?: Article): boolean {
if (currentUser?.admin) return true
switch (action) {
case 'view': return true
case 'create': return currentUser != null
case 'edit': return currentUser != null && currentUser.id === article?.authorId
default: return false
}
}
}
@AbilityFor('AdminArea')
export class AdminAreaAbility implements Ability<User> {
can(currentUser: User | null, action: string): boolean {
switch (action) {
case 'view': return currentUser?.admin ?? false
default: return false
}
}
}Registering Abilities
Standalone (recommended)
Use provideAbilities() in your application or module providers:
import { bootstrapApplication } from '@angular/platform-browser'
import { provideAbilities } from 'ng-ability'
bootstrapApplication(AppComponent, {
providers: [
provideAbilities(AbilityUserContext, [
ArticleAbility,
AdminAreaAbility,
]),
],
})You can also call provideAbilities() without a context class if the context is provided elsewhere:
provideAbilities([ArticleAbility, AdminAreaAbility])Feature modules
You can call provideAbilities() in lazy-loaded route providers to add abilities scoped to a feature. See the Feature Modules recipe for a full example.
NgModule (deprecated)
import { NgModule } from '@angular/core'
import { NgAbilityModule } from 'ng-ability'
@NgModule({
imports: [
NgAbilityModule.withAbilities(AbilityUserContext, [
ArticleAbility,
AdminAreaAbility,
]),
],
})
export class AppModule {}