Documenting Signals Stores That Recruiters Can Read: Derived Selectors, Mutators, and Analytics Hooks in Angular 20+

Documenting Signals Stores That Recruiters Can Read: Derived Selectors, Mutators, and Analytics Hooks in Angular 20+

A practical blueprint I use on enterprise Angular 20+ teams to make state self‑describing: derivations, mutators, and telemetry documented for fast reviews and safer changes.

If a senior can’t understand your store in five minutes, the problem isn’t Signals—it’s documentation.
Back to all posts

I’ve walked into plenty of Angular reviews where a recruiter or director opens a store file and can’t tell what’s safe to call. If your derived selectors, mutators, and analytics hooks aren’t self‑describing, you slow hiring, code reviews, and incident response. This is the documentation blueprint I use on Angular 20+ projects with Signals and SignalStore.

It’s opinionated, production-tested across telecom analytics dashboards, airport kiosks, and multi-tenant portals. The goal: a hiring manager can understand your state in five minutes, and a senior can reason about side effects and telemetry without opening the template.

The 5‑Minute Recruiter Test: Can They Follow Your State?

Pass the five‑minute test by making selectors, mutators, and analytics discoverable, typed, and linked to outcomes. Tie it to CI so it sticks.

What I look for in a quick scan

On real engagements (telecom analytics, IoT device portals, airport kiosks), I ask reviewers to cold‑read a store file. If they can’t map state to business terms and find where analytics fire in five minutes, we’re documenting the wrong things—or not at all.

  • Selectors read like business terms

  • Mutators are verbs with invariants

  • Telemetry is typed and centralized

  • No logic leaks into templates

Why this matters in 2025 roadmaps

As budgets reset, hiring teams want proof you can ship safely. Clear documentation around Signals and SignalStore is a credibility shortcut. It reduces onboarding time, cuts PR review friction, and prevents late‑stage surprises during Angular 20+ upgrades.

Why Angular 20 Teams Should Document Derived Selectors and Mutators

Documentation is not a wiki. It’s inline TSDoc with automation: fast to read, impossible to forget in code review.

Signals make state explicit—docs make it durable

Signals in Angular 20 give us crisp read/write boundaries: computed for derivations, functions for mutations. Documentation turns that clarity into a durable system. With typed derivations, verb‑based mutators, and explicit analytics, you can review changes quickly and catch performance regressions before they hit prod.

  • Signals/SignalStore surfacing read vs write

  • Avoiding template logic and re-computation

  • Reviewability under pressure

Recruiter and director friendly

Non-engineering reviewers skim for business terms. Name selectors in the language of outcomes (filteredEmployees, overduePolicies). Link each mutator to analytics events and performance notes. Generate docs on every PR so decision‑makers can inspect without a local build.

  • Business language in names

  • Cross-links to metrics and ADRs

  • CI previews of docs

A Documentation Blueprint: Signals Store with Derivations, Mutators, and Analytics Hooks

import { computed, inject, signal } from '@angular/core';
import { SignalStore, withState } from '@ngrx/signals';
import { toSignal } from '@angular/core/rxjs-interop';
import { TelemetryService, AnalyticsEvent } from '../telemetry/telemetry.service';
import { EmployeesApi } from '../data/employees.api';

export interface EmployeesState {
  employees: ReadonlyArray<Employee>; // source of truth
  filter: { term: string; team?: string };
  loading: boolean;
  lastSyncedAt?: number;
}

interface Employee { id: string; name: string; team: string; active: boolean }

/**
 * Telemetry contract for this store.
 */
export type EmployeesEvents =
  | AnalyticsEvent<'employee_filter_changed', { term: string; team?: string }>
  | AnalyticsEvent<'employee_deactivated', { id: string; team: string }>
  | AnalyticsEvent<'sync_completed', { count: number; durationMs: number }>; 

export const initialState: EmployeesState = {
  employees: [],
  filter: { term: '' },
  loading: false,
};

export class EmployeesStore extends SignalStore(withState(initialState)) {
  private readonly api = inject(EmployeesApi);
  private readonly telemetry = inject(TelemetryService<EmployeesEvents>());

  /**
   * @derived List of employees matching current filter.
   * @performance O(n) over employees; memoized by Signals. Avoid chaining expensive sorts in templates.
   */
  readonly filteredEmployees = computed(() => {
    const { term, team } = this.state().filter;
    const t = term.toLowerCase();
    return this.state().employees.filter(e =>
      (!team || e.team === team) &&
      (!t || e.name.toLowerCase().includes(t))
    );
  });

  /**
   * @derived Active count for header badges and A11y live regions.
   */
  readonly activeCount = computed(() =>
    this.filteredEmployees().filter(e => e.active).length
  );

  /**
   * @mutates Sets the filter. Debounce upstream when binding to inputs.
   * @analytics Fires employee_filter_changed with term/team for funnel analysis.
   * @invariants term <= 64 chars, team must exist when provided.
   */
  setFilter(term: string, team?: string) {
    this.patchState(s => ({ filter: { term, team } }));
    this.telemetry.track('employee_filter_changed', { term, team });
  }

  /**
   * @mutates Marks employee inactive and persists to API.
   * @analytics Fires employee_deactivated for audit; emits only on success.
   * @concurrency Idempotent; safe to retry on network flaps.
   */
  async deactivate(id: string) {
    const emp = this.state().employees.find(e => e.id === id);
    if (!emp || !emp.active) return; // guard
    this.patchState(s => ({
      employees: s.employees.map(e => e.id === id ? { ...e, active: false } : e)
    }));
    await this.api.deactivate(id);
    this.telemetry.track('employee_deactivated', { id, team: emp.team });
  }

  /**
   * @mutates Syncs employees from API; updates lastSyncedAt.
   * @analytics sync_completed with count/duration for ops dashboards.
   */
  async sync() {
    const start = performance.now();
    this.patchState({ loading: true });
    const data = await this.api.list();
    this.patchState({ employees: data, loading: false, lastSyncedAt: Date.now() });
    this.telemetry.track('sync_completed', { count: data.length, durationMs: performance.now() - start });
  }
}
// telemetry.service.ts (typed analytics)
export type AnalyticsEvent<T extends string = string, P extends object = {}> = {
  type: T; payload: P;
};

export class TelemetryService<Evt extends AnalyticsEvent = AnalyticsEvent> {
  track<K extends Evt['type']>(type: K, payload: Extract<Evt, { type: K }>['payload']) {
    // Route to GA4/Firebase/BigQuery or custom sink; batch + offline queue
  }
}
# .github/workflows/docs.yml
name: docs
on:
  pull_request:
    paths: ['apps/**', 'libs/**']
jobs:
  typedoc:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20' }
      - run: npm ci
      - run: npx nx run employees-store:typedoc
      - uses: actions/upload-artifact@v4
        with: { name: store-docs, path: dist/docs/employees-store }

Store anatomy with TSDoc tags

Use a minimal taxonomy that reviewers learn once. Every public selector/mutator documents purpose, inputs, invariants, and events fired. Keep computed pure and memoized; keep mutators small and transaction‑like.

  • @derived for computed selectors

  • @mutates for write APIs

  • @analytics for telemetry hooks

TypeScript example: SignalStore with docs

Typed analytics via a single service

Centralize analytics to a TelemetryService with strict types. Stores call analytics at business boundaries. This keeps templates presentational and makes metrics reliable (see my IntegrityLens work for zero PII drift).

  • No telemetry in templates

  • Compile-time safety

  • Batching and offline queues

CI guardrails: generate docs on PR

Automate docs to keep them honest. I wire an Nx target to TypeDoc and publish artifacts on every PR so reviewers can skim derived selectors and mutators in the browser.

  • Nx target + TypeDoc

  • Artifacts in GitHub Actions

  • Fail fast on missing docs

Real‑World Example: Telecom Analytics Store Docs That Cut Review Time 32%

Documentation wasn’t a PDF; it was TSDoc + CI artifacts tied to real performance metrics and typed analytics.

Context

In a telecom analytics dashboard, we migrated to Angular 20 with Signals/SignalStore and documented derivations and mutators as above. Recruiters and directors could follow state shape and analytics flow without spinning up the app. PR review time dropped 32% and incident triage was faster.

  • Angular 11 → 20 upgrade

  • High‑volume WebSocket telemetry

  • Multi‑tenant role‑based access

Derived selector clarity

Complex KPIs (like minute‑level INP bands) lived in computed selectors with @performance notes referencing Angular DevTools flame charts. We kept templates dumb: no ad‑hoc filters or sorts. DevTools confirmed render counts dropped after we consolidated derivations.

Typed analytics you can trust

Events flowed through a typed TelemetryService and into GA4/BigQuery with build metadata (git sha, app version). That alignment let us prove UX improvements alongside feature delivery—useful in Q1 budget reviews.

Documentation Conventions That Scale in Nx

Conventions only stick when automated. Nx + ESLint + TypeDoc keep drift low even as teams scale.

Naming rules

Name for business clarity. If a recruiter can guess usage from the name alone, you’re doing it right. Avoid leaking implementation (e.g., setFilterSignal).

  • Selectors: nouns (filteredEmployees, activeCount)

  • Mutators: verbs (setFilter, deactivate, sync)

  • Analytics: past-tense types (employee_deactivated)

TSDoc must-haves

Every public API gets a TSDoc block. For heavy selectors, include @performance notes with O(n) hints or cache policy (SWR, polling) and a link to an ADR.

  • @derived or @mutates tag

  • Inputs/outputs and invariants

  • @analytics events fired

ESLint + CI enforcement

Use ESLint to forbid undocumented exports and template analytics. In Nx, wire a lint rule to fail PRs if @derived/@mutates are missing. That’s how this becomes culture, not preference.

  • ban undocumented exports

  • ban telemetry calls in templates

  • prefer-computed over inline array ops

How an Angular Consultant Approaches Signals Documentation

If you need to hire an Angular developer to formalize your stores fast, this is the playbook I run on week one.

1) Audit

I start by tracing state to outcomes and noting logic that leaked into templates. Angular DevTools gives me render counts and flame charts to target the right derivations.

  • Map state → business outcomes

  • Collect hidden template logic

  • Profile renders with DevTools

2) Normalize

We shape stores around read/write boundaries, move filters/sorts into computed selectors, and define typed analytics events in one place.

  • Introduce SignalStore + computed

  • Consolidate derivations

  • Create telemetry interface

3) Automate

Docs generate in CI, missing tags fail lint, and Storybook stories exercise mutators while stubbing analytics.

  • TypeDoc previews per PR

  • ESLint rules for tags

  • Storybook interaction tests

When to Hire an Angular Developer for Legacy Rescue

Strong documentation accelerates upgrades, hiring, and delivery. Bring a senior in before the next outage—not after.

Signals to bring in help

If reviewers can’t follow your store without running the app, or if analytics can’t answer basic funnel questions, bring in an Angular consultant. In a week, we can untangle derivations, centralize telemetry, and set up CI docs.

  • Template logic everywhere

  • Unclear mutator side effects

  • Analytics scattered and untyped

Expected timeline

Typical engagements ship documentation artifacts by day three, with measurable UX improvements (render counts, INP) by week two.

  • 2–4 weeks rescue

  • 4–8 weeks full upgrade

  • Docs in PR by day 3

Key Takeaways for Angular 20 Signals Documentation

  • Document derivations (@derived), mutators (@mutates), and analytics (@analytics) inline with TSDoc.
  • Keep templates dumb; route all analytics through a typed service.
  • Automate docs with Nx + TypeDoc and enforce with ESLint.
  • Use Angular DevTools to justify performance notes and track wins in CI telemetry.

Code Review Questions to Ask Your Team

  • Can a new engineer or recruiter follow the store in five minutes?
  • Do derivations capture all template logic, and are they pure?
  • Are mutators small, verb‑based, and documented with invariants?
  • Are analytics typed, centralized, and linked to business outcomes?
  • Does CI fail when docs are missing or telemetry leaks to templates?

Related Resources

Key takeaways

  • Use a consistent TSDoc taxonomy: @derived for selectors, @mutates for write APIs, @analytics for telemetry hooks.
  • Keep derived selectors pure and named by business language; expose read-only computed signals for templates.
  • Group mutators by intent (not fields) and document invariants, concurrency guarantees, and events fired.
  • Define a typed analytics schema once; make stores call analytics hooks at business boundaries, not in templates.
  • Automate docs with Nx + TypeDoc and fail CI on undocumented public APIs using ESLint rules.

Implementation checklist

  • Adopt naming: noun-based selectors (past-tense for derived aggregates), verb-based mutators (imperative).
  • Add TSDoc to every exported selector/mutator; include inputs, invariants, and emitted events.
  • Centralize analytics via a TelemetryService with typed event contracts.
  • Gate expensive derivations with memoized computed and defend with unit tests.
  • Generate store docs on each PR with TypeDoc; publish as CI artifact and preview link.
  • Use Angular DevTools to capture render counts; annotate selectors to justify heavy derivations.
  • Back stores with e2e stories in Storybook that exercise mutators and verify analytics stubs.
  • Add ADR linking: state decisions recorded in /docs/adr with commit hashes and perf deltas.

Questions we hear from teams

What should be documented in an Angular Signals store?
Document derived selectors, mutators, and analytics hooks. Include purpose, inputs/outputs, invariants, performance notes, and the typed events fired. Keep templates free of business logic and ensure analytics calls are centralized.
How long does it take to add documentation to an existing Angular app?
For a typical feature area, expect 2–3 days to standardize naming, add TSDoc, centralize analytics, and wire TypeDoc in CI. Larger apps take 2–4 weeks to cover all stores with guardrails and Storybook interaction tests.
Do I need NgRx to document selectors and mutators?
No. In Angular 20+ with Signals and SignalStore you can model state, derivations, and mutations without full NgRx. If you have NgRx, use selectSignal/toSignal bridges and keep the same documentation conventions.
How much does it cost to hire an Angular developer to formalize store docs?
Scope matters, but most teams see value in a 2–4 week engagement to audit stores, document derivations/mutators, and automate CI docs. I offer fixed‑scope assessments and short sprints to prove ROI before longer commitments.
Where should analytics live in Angular?
Use a typed TelemetryService and call it from stores at business boundaries. Avoid template‑level telemetry. Send events to GA4/BigQuery or your sink, tagged with build metadata for reliable analysis.

Ready to level up your Angular experience?

Let AngularUX review your Signals roadmap, design system, or SSR deployment plan.

Hire Matthew — Remote Angular Expert, Available Now Request a 30‑minute review of your Signals documentation

NG Wave

Angular Component Library

A comprehensive collection of 110+ animated, interactive, and customizable Angular components. Converted from React Bits with full feature parity, built with Angular Signals, GSAP animations, and Three.js for stunning visual effects.

Explore Components
NG Wave Component Library

Related resources