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