Live Examples – Modern Angular Patterns (2025): Signals, NgRx, RxJS, Web Components, A11y & Performance Testing
Live demo: StackBlitz · Source: GitHub
I pulled together a hands-on Angular modern patterns showcase that demonstrates how to combine Signals, NgRx, RxJS, Web Components, and a pragmatic performance + a11y playbook. It targets the latest Angular (standalone components & new control flow) and is meant to be read in the code—then run, tweak, and profile.
- Repo: https://github.com/jdavis-software/angular-modern-patterns-showcase
- Instant demo: https://stackblitz.com/edit/angular-modern-patterns-showcase
Why this exists
Signals unlocked fine‑grained reactivity, standalone components simplified architecture, and NgRx matured into an ergonomic state stack. You’ll find a lot of articles about each thing in isolation; this repo focuses on how they fit together in real UI flows.
Quick start
Run it locally
git clone https://github.com/jdavis-software/angular-modern-patterns-showcase
cd angular-modern-patterns-showcase
npm i
npm start
# Visit http://localhost:4200
Or open in your browser (no setup):
Contents at a glance
- Signals vs NgRx — clear division of responsibility
- When RxJS still shines — external streams, back-pressure, and composition
- Web Components in Angular — native + Angular Elements, Shadow DOM styling
- Performance playbook — render less, compute once, stream smart
- Accessibility (WCAG 2.2) — keyboard-first and screen reader friendly
- Performance testing — devtools, lab checks, and CI ideas
Tip: Scan the component folders and open the matching route in the running app to see each pattern live.
1) Signals: feature‑local state & derived data
Use Signals for UI-centric or feature-local state. Compute derivations and memoize expensive work with computed
. Keep it close to the component.
import { signal, computed, effect } from '@angular/core';
type CartItem = { name: string; price: number; qty: number };
export class SignalsCartComponent {
readonly items = signal<CartItem[]>([]);
readonly count = computed(() => this.items().reduce((n, i) => n + i.qty, 0));
readonly total = computed(() => this.items().reduce((s, i) => s + i.price * i.qty, 0));
constructor() {
effect(() => console.log('cart changed', this.items()));
}
add(item: CartItem) { this.items.update(xs => [...xs, item]); }
clear() { this.items.set([]); }
}
Heuristics
- If state is owned by a single feature/view and derived mostly for rendering, keep it in Signals.
- Prefer
computed
over ad‑hoc transforms in templates to remove work from change detection. - Use small, testable derivations instead of big monolithic selectors where only one view cares.
2) NgRx: app‑wide state, effects, and time‑travel
Use NgRx when multiple features need the same source of truth, you need effects orchestration (server calls, caching, retries), or time‑travel/debuggability matters.
Bridge NgRx selectors to Signals with toSignal
for ergonomic templates:
import { Store } from '@ngrx/store';
import { toSignal } from '@angular/core/rxjs-interop';
import { selectVisibleTodos } from './store/selectors';
import { todoToggled } from './store/actions';
export class TodosComponent {
readonly todosSig = toSignal(this.store.select(selectVisibleTodos), { initialValue: [] });
constructor(private store: Store) {}
toggle(id: string) {
this.store.dispatch(todoToggled({ id }));
}
}
Rule of thumb
- Signals: local view state + derived render data
- NgRx: cross‑feature domain state + effects + devtools
3) Where RxJS still shines
Even with Signals, RxJS is unrivaled for external/continuous streams (websockets, DOM events, SSE), back‑pressure, multicasting, and complex async composition. Expose the result to the view as a Signal.
import { interval, Subject } from 'rxjs';
import { bufferTime, map, shareReplay } from 'rxjs/operators';
const source$ = interval(100); // noisy stream
export const batches$ = source$.pipe(
bufferTime(2000),
map(batch => ({ count: batch.length, last: batch.at(-1) })),
shareReplay({ bufferSize: 1, refCount: true }),
);
import { toSignal } from '@angular/core/rxjs-interop';
export class DashboardComponent {
readonly batchSig = toSignal(batches$, { initialValue: { count: 0, last: null } });
}
Guideline
- Keep RxJS at the boundaries and for non-trivial async flows; bridge to Signals for consumption in templates.
4) Web Components in Angular
The repo shows two interop paths:
A) Consume native custom elements
- Bind attributes/props like normal.
- Listen to well-typed
CustomEvent
contracts—treat events as part of your API. - Theme via CSS custom properties that pierce Shadow DOM.
<progress-ring value="{{percent()}}" aria-label="Upload progress"></progress-ring>
B) Export Angular as a custom element (Angular Elements)
import { Injector } from '@angular/core';
import { createCustomElement } from '@angular/elements';
import { ProgressRingComponent } from './progress-ring.component';
export class AppModule {
constructor(injector: Injector) {
const ProgressEl = createCustomElement(ProgressRingComponent, { injector });
customElements.define('progress-ring', ProgressEl);
}
}
Styling across Shadow DOM
- Prefer design tokens via CSS variables (e.g.,
--ring-color
). - Avoid deep selectors; keep styling opt‑in and documented in the component README.
5) Performance playbook (practical)
-
Render less: use
trackBy
on every*ngFor
; avoid recomputing arrays/objects in templates. -
Compute once: push expensive work into
computed
signals or memoized selectors. -
New control flow &
@defer
: lazy‑render non‑critical UI. - Change detection: drive updates via signals to minimize checks.
- Virtualization: use CDK virtual scroll for large lists.
- Network: preconnect critical origins; cache smartly at the HTTP layer.
@defer (on viewport) {
<heavy-widget></heavy-widget>
}
<li *ngFor="let user of users(); trackBy: userId">{{ user.name }}</li>
userId = (_: number, u: { id: string }) => u.id;
6) Accessibility (WCAG 2.2 minded)
- Semantic HTML first; ARIA only to fill gaps.
- Keyboard-first: visible focus, logical tab order, and roving tabindex for composite widgets.
- Focus trapping for dialogs; Escape closes; return focus to the invoker.
- Live regions for async updates (e.g., background save).
// Roving tabindex (essentials)
current = signal(0);
onKey(e: KeyboardEvent) {
if (e.key === 'ArrowRight') this.current.update(i => (i + 1) % this.items.length);
if (e.key === 'ArrowLeft') this.current.update(i => (i - 1 + this.items.length) % this.items.length);
}
7) Performance testing: how to measure this repo
You don’t improve what you don’t measure. Here are low‑friction ways to profile the examples locally or on StackBlitz.
A) Angular DevTools Profiler (Chrome)
- Open the app, install Angular DevTools.
- Navigate to a demo (e.g., list rendering).
- Start profiling, interact (scroll/filter), stop and inspect change detection cycles and component hotspots.
B) Chrome Performance panel
-
npm start
, openhttp://localhost:4200
. - DevTools → Performance → Record.
- Interact with the demo; Stop.
- Inspect Main thread (scripting/layout/paint), long tasks, and FPS.
C) Lighthouse (lab checks)
With the app running locally:
npx lighthouse http://localhost:4200 --only-categories=performance --view
Compare before/after toggling patterns (e.g., adding trackBy
, enabling @defer
).
D) Web Vitals in code (field-ish signals)
Add a basic Web Vitals logger (optional) to see CLS/LCP/INP in DevTools console in dev builds.
// vitals.ts (add where convenient in dev)
import { onCLS, onLCP, onINP } from 'web-vitals';
onCLS(console.log);
onLCP(console.log);
onINP(console.log);
For CI: consider scripting a headless Lighthouse run against a static build and diffing scores to prevent performance regressions.
How to use these patterns at work
- Start with Signals for local, view-level state. Promote to NgRx only when multiple features require the same data, or effects orchestration/debuggability are needed.
- Keep RxJS at the edges and for non-trivial async flows, then bridge to Signals with
toSignal()
. - Prefer Web Components when collaborating across frameworks or publishing design tokens.
- Treat performance and a11y as acceptance criteria, not afterthoughts—measure early.
What’s next
Planned additions:
- Signals + forms patterns (typed models & derived validity)
- Router data with signals and granular prefetching
- Hydration pitfalls checklist (SSR)
- A dedicated “perf lab” comparing render strategies
If you want a pattern added, open an issue or PR. And if this helps, a ⭐ on the repo makes it easier for others to find.
Links
- GitHub: https://github.com/jdavis-software/angular-modern-patterns-showcase
- Live demo (StackBlitz): https://stackblitz.com/edit/angular-modern-patterns-showcase
Built for the 2025 Angular ecosystem. Signals, NgRx, RxJS, Web Components, performance, and a11y—working together, not just in theory.