> ## 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.

# Angular Best Practices

> Angular component patterns, lifecycle hooks, and coding standards

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`):

```typescript theme={null}
// 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`):

```typescript theme={null}
// 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`):

```typescript theme={null}
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`):

```typescript theme={null}
// 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

## Input/Output Patterns

### No Input/Output Metadata

Don't use `inputs` and `outputs` metadata arrays (`@angular-eslint/no-inputs-metadata-property`, `@angular-eslint/no-outputs-metadata-property`):

```typescript theme={null}
// 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`):

```typescript theme={null}
// Bad
@Output('onChange') userChanged = new EventEmitter();

// Good
@Output() userChanged = new EventEmitter();
```

### Prefer Signals

Use Angular signals for reactive state (`@angular-eslint/prefer-signals`):

```typescript theme={null}
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`):

```typescript theme={null}
// 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:

```typescript theme={null}
@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:

```typescript theme={null}
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`):

```typescript theme={null}
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`):

```typescript theme={null}
// 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

### Button Type Attribute

All buttons must have an explicit `type` attribute (`@angular-eslint/template/button-has-type`):

```html theme={null}
<!-- 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`):

```typescript theme={null}
@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`):

```html theme={null}
<!-- 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`):

```html theme={null}
<!-- Avoid (warning) -->
<i class="bwi bwi-lock"></i>

<!-- Prefer -->
<bit-icon icon="lock"></bit-icon>
```

### No Icon Children in Buttons

Don't nest icons inside bit-button components (`@bitwarden/components/no-icon-children-in-bit-button`):

```html theme={null}
<!-- Bad (warning) -->
<bit-button>
  <i class="bwi bwi-plus"></i> Add Item
</bit-button>

<!-- Good -->
<bit-button icon="plus">Add Item</bit-button>
```

## Pipes

### Implement PipeTransform

All pipes must implement the `PipeTransform` interface (currently disabled):

```typescript theme={null}
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:

```typescript theme={null}
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

* Review [TypeScript Style Guide](/guide/typescript-style) for TypeScript patterns
* See [Linting](/guide/linting) for complete ESLint rules
* Check [Tailwind CSS](/libs/components) for styling patterns
