# Pattern: Angular Signals (the way we use them)

How to use Angular Signals consistently in `apps/web-angular`.

## Component inputs

```ts
readonly title    = input.required<string>();           // required
readonly subtitle = input<string | null>(null);         // optional with default
readonly tone     = input<'neutral' | 'info'>('neutral');
```

Templates call them as functions: `{{ title() }}`.

## Service state

```ts
@Injectable({ providedIn: 'root' })
export class LoadingService {
  private readonly _count = signal(0);
  readonly isLoading = computed(() => this._count() > 0);
  start() {
    this._count.update((n) => n + 1);
  }
  stop() {
    this._count.update((n) => Math.max(0, n - 1));
  }
}
```

## Feature facade

```ts
@Injectable()
export class DocumentsFacade {
  private readonly _docs = signal<readonly Document[]>([]);
  readonly docs = this._docs.asReadonly();
  readonly count = computed(() => this._docs().length);

  constructor(private readonly api: DocumentsApi) {}

  async load() {
    const result = await this.api.list();
    this._docs.set(result);
  }
}
```

Component injects the facade and reads signals; the facade owns the writes.

## Effects (use sparingly)

`effect()` is for syncing signal state to non-signal sinks (DOM attributes, localStorage). See `ThemeService.constructor()` for an example. Don't use effects to fetch — call the API explicitly.

## Anti-patterns

- Don't `Subject`/`BehaviorSubject` if a signal will do. (RxJS still wins for streaming + cancellation.)
- Don't expose mutable signals from a service — always `.asReadonly()`.
- Don't put async fetch inside `computed()` — `computed` must be pure + synchronous.
- Don't inject `HttpClient` directly in a component — call through `BaseApiService` + a feature API.

## Related

- `CLAUDE.md` §4 (architecture principles)
- `apps/web-angular/src/app/core/services/` (the canonical examples)
