Documentation Index
Fetch the complete documentation index at: https://mintlify.com/bitwarden/clients/llms.txt
Use this file to discover all available pages before exploring further.
This guide covers Angular-specific patterns and best practices enforced by ESLint rules and team conventions.
Component Patterns
Component Class Suffix
All components must use the Component suffix (@angular-eslint/component-class-suffix):
// Good
@Component({
selector: 'app-user-profile',
templateUrl: './user-profile.component.html'
})
export class UserProfileComponent {}
// Bad
@Component({
selector: 'app-user-profile',
templateUrl: './user-profile.component.html'
})
export class UserProfile {}
Directive Class Suffix
All directives must use the Directive suffix (@angular-eslint/directive-class-suffix):
// Good
@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {}
// Bad
@Directive({
selector: '[appHighlight]'
})
export class Highlight {}
Change Detection Strategy
Prefer OnPush change detection for better performance (@angular-eslint/prefer-on-push-component-change-detection):
import { Component, ChangeDetectionStrategy } from '@angular/core';
@Component({
selector: 'app-user-list',
templateUrl: './user-list.component.html',
changeDetection: ChangeDetectionStrategy.OnPush // Required
})
export class UserListComponent {}
Benefits:
- Improved performance by reducing change detection cycles
- Forces better data flow patterns
- Explicit state management
Lifecycle Hooks
Implement Lifecycle Interfaces
Always implement the corresponding interface (@angular-eslint/use-lifecycle-interface):
// Good
import { Component, OnInit, OnDestroy } from '@angular/core';
@Component({ ... })
export class UserComponent implements OnInit, OnDestroy {
ngOnInit(): void {
// Initialization logic
}
ngOnDestroy(): void {
// Cleanup logic
}
}
// Bad - missing interfaces
export class UserComponent {
ngOnInit(): void {
// Initialization logic
}
}
Contextual Lifecycle
Use lifecycle hooks appropriately for their context (@angular-eslint/contextual-lifecycle):
- Don’t use component hooks in directives
- Don’t use directive hooks in components
- Use the right hook for the right job
Don’t use inputs and outputs metadata arrays (@angular-eslint/no-inputs-metadata-property, @angular-eslint/no-outputs-metadata-property):
// Good
@Component({ ... })
export class UserComponent {
@Input() userId: string;
@Output() userChanged = new EventEmitter<User>();
}
// Bad
@Component({
inputs: ['userId'],
outputs: ['userChanged']
})
export class UserComponent {
userId: string;
userChanged = new EventEmitter<User>();
}
No Output Rename
Don’t rename outputs (@angular-eslint/no-output-rename):
// Bad
@Output('onChange') userChanged = new EventEmitter();
// Good
@Output() userChanged = new EventEmitter();
Prefer Signals
Use Angular signals for reactive state (@angular-eslint/prefer-signals):
import { Component, signal, computed } from '@angular/core';
@Component({ ... })
export class CounterComponent {
// Prefer signals over traditional properties
count = signal(0);
doubleCount = computed(() => this.count() * 2);
increment(): void {
this.count.update(c => c + 1);
}
}
EventEmitter Type
Use EventEmitter with proper typing (@angular-eslint/prefer-output-emitter-ref):
// Good
@Output() userChanged = new EventEmitter<User>();
// Better with EventEmitterRef (Angular 19+)
import { output } from '@angular/core';
@Output() userChanged = output<User>();
Dependency Injection
Constructor Injection
Use constructor injection for services:
@Component({ ... })
export class UserComponent {
constructor(
private userService: UserService,
private route: ActivatedRoute,
private i18nService: I18nService
) {}
}
Modern inject() Function
While @angular-eslint/prefer-inject is currently disabled, the inject() function is available:
import { Component, inject } from '@angular/core';
@Component({ ... })
export class UserComponent {
private userService = inject(UserService);
private route = inject(ActivatedRoute);
}
RxJS Best Practices
Automatic Unsubscription
Use takeUntilDestroyed to automatically unsubscribe (rxjs-angular/prefer-takeuntil):
import { Component, OnInit } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@Component({ ... })
export class UserComponent implements OnInit {
private destroy$ = takeUntilDestroyed();
ngOnInit(): void {
this.userService.getUsers()
.pipe(this.destroy$) // Automatically unsubscribes on destroy
.subscribe(users => {
this.users = users;
});
}
}
Subject Protection
Don’t expose raw Subject instances - only expose as Observable (rxjs/no-exposed-subjects):
// Good
export class UserService {
private usersSubject = new Subject<User[]>();
users$ = this.usersSubject.asObservable(); // Exposed as Observable
}
// Allowed - protected subjects
export class UserService {
protected usersSubject = new Subject<User[]>(); // Protected is allowed
}
// Bad
export class UserService {
usersSubject = new Subject<User[]>(); // Public subject exposed
}
RxJS Recommended Rules
All rules from eslint-plugin-rxjs recommended config are enabled, including:
- No nested subscriptions
- No subscribe in subscribe
- Proper error handling
- Unsubscribe from finite observables
Template Patterns
All buttons must have an explicit type attribute (@angular-eslint/template/button-has-type):
<!-- Good -->
<button type="button" (click)="save()">Save</button>
<button type="submit">Submit Form</button>
<!-- Bad -->
<button (click)="save()">Save</button>
Inline Templates
Inline templates are processed automatically by ESLint (angular.processInlineTemplates):
@Component({
selector: 'app-inline',
template: `
<button type="button">Click Me</button>
`
})
export class InlineComponent {}
Custom Bitwarden Rules
Required Labels on Icons
Icon buttons require accessibility labels (@bitwarden/components/require-label-on-biticonbutton):
<!-- Good -->
<bit-icon-button icon="close" aria-label="Close dialog"></bit-icon-button>
<!-- Bad -->
<bit-icon-button icon="close"></bit-icon-button>
<!-- Exception - certain directives auto-label -->
<bit-icon-button icon="eye" bitPasswordInputToggle></bit-icon-button>
No BWI Class Usage
Avoid using bwi-* classes directly in templates (@bitwarden/components/no-bwi-class-usage):
<!-- Avoid (warning) -->
<i class="bwi bwi-lock"></i>
<!-- Prefer -->
<bit-icon icon="lock"></bit-icon>
Don’t nest icons inside bit-button components (@bitwarden/components/no-icon-children-in-bit-button):
<!-- Bad (warning) -->
<bit-button>
<i class="bwi bwi-plus"></i> Add Item
</bit-button>
<!-- Good -->
<bit-button icon="plus">Add Item</bit-button>
Pipes
All pipes must implement the PipeTransform interface (currently disabled):
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({ name: 'truncate' })
export class TruncatePipe implements PipeTransform {
transform(value: string, length: number): string {
return value.length > length ? value.substring(0, length) + '...' : value;
}
}
Standalone Components
While @angular-eslint/prefer-standalone is currently disabled, standalone components are the modern approach:
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-user',
standalone: true,
imports: [CommonModule],
templateUrl: './user.component.html'
})
export class UserComponent {}
Admin Console Strict Rules
The libs/admin-console and admin-console app directories enforce stricter Angular rules:
@angular-eslint/no-empty-lifecycle-method: "error"
@angular-eslint/no-input-rename: "error"
@angular-eslint/no-output-native: "error"
@angular-eslint/no-output-on-prefix: "error"
@angular-eslint/use-pipe-transform-interface: "error"
See eslint.config.mjs:609-631 for the full configuration.
Next Steps