Chapter 10: Advanced Topics (later lessons / follow-up)
Advanced Topics!
This is the “level up” part where we talk about performance tricks, future-proofing your apps, and the patterns that separate good Angular apps from great ones in 2026. These features are what make modern Angular feel fast, SEO-friendly, and scalable — especially for production apps with SSR, heavy components, or complex state.
We’ll cover each topic in detail with examples you can try right now (assuming Angular ~21+ as of Jan 2026). I’ll explain like we’re reviewing your code together over a late lunch in Airoli.
1. Deferred Loading with @defer (Stable since v17–18, heavily used in 2026)
@defer is the way to lazy-load parts of a template (not just routes). It delays loading heavy components, directives, pipes, and their JS bundles until needed — huge win for initial load time, LCP, and bundle size.
Triggers (when to load):
- on viewport — when element scrolls into view (most common!)
- on interaction — on click/hover/focus/etc.
- on timer(5s) — after delay
- on idle — when browser is not busy
- when expression() — custom signal/condition
- on immediate — load ASAP but still async
Sub-blocks:
- @loading { … } — spinner/placeholder
- @placeholder { … } — initial static content (before loading starts)
- @error { … } — if loading fails
Real example: Lazy-load a heavy chart component
First, generate a heavy component (e.g., with Chart.js or big lib):
|
0 1 2 3 4 5 6 |
ng generate component sales-chart --standalone |
Assume SalesChartComponent is large (imports heavy libs).
In parent template (e.g., dashboard.component.html):
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
<h1>Dashboard</h1> <!-- Core content loads immediately --> <p>Quick stats: Revenue ↑15%</p> @defer (on viewport; prefetch on idle) { <app-sales-chart [data]="chartData()" /> } @placeholder { <div class="placeholder-chart"> <p>Chart loading soon... 📊</p> </div> } @loading (after 500ms; minimum 1s) { <div class="loading"> <mat-spinner diameter="40"></mat-spinner> <p>Fetching sales data...</p> </div> } @error { <p style="color: red">Failed to load chart. <button (click)="reloadChart()">Retry</button></p> } |
Benefits in 2026:
- Bundle splitting: chart code in separate chunk → smaller initial JS
- No Zone.js overhead until triggered
- Combine with signals: chartData = resource(…) → fetch only when deferred block activates
Pro tip: Use prefetch on … (e.g., prefetch on idle) to download code early but not execute.
2. Incremental Hydration (Stable in Angular v20+, was experimental in v19)
This is the big SSR game-changer in 2026. Full hydration (old way) loads ALL JS immediately after SSR → slow TTI/INP on big apps.
Incremental hydration: Serve full HTML from server (great SEO/LCP), but hydrate (make interactive) only parts of the page on demand. Dehydrated parts stay static HTML until triggered — no JS executed yet.
Enable it (in app.config.ts or main.ts):
|
0 1 2 3 4 5 6 |
provideClientHydration(withIncrementalHydration()) |
Syntax — add hydrate triggers to @defer blocks:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<!-- Header hydrates on hover (e.g., for dropdowns) --> @defer (hydrate on hover; on viewport) { <app-header-with-dropdowns /> } @placeholder { <header>Static header HTML from server</header> } <!-- Footer only hydrates on interaction (click anywhere) --> @defer (hydrate on interaction; hydrate on timer(10s)) { <app-footer-with-analytics /> } |
hydrate triggers (separate with ;):
- hydrate on idle / viewport / interaction / hover / timer(…)
- hydrate when mySignal() === true
- hydrate never — keep static forever (pure HTML)
Patterns in 2026:
- Above-the-fold content: no defer → full hydration
- Below-fold / modals / tabs: @defer (hydrate on viewport)
- Interactive islands: @defer (hydrate on interaction)
- Event replay: Angular auto-replays clicks/taps during hydration delay (no lost interactions)
Result: 40–70% better INP/LCP in SSR apps, smaller initial JS, no flicker (server HTML reused).
3. linkedSignal & resource() API (Both stable/experimental → production patterns in 2026)
- linkedSignal() — writable signal that’s auto-reset from a computation. Great for “reset on dependency change” patterns (e.g., form reset when userId changes).
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
userId = signal<string | null>(null); userFormData = linkedSignal(() => ({ name: '', email: userId() ? `${userId()}@example.com` : '' })); // When userId changes → form auto-resets with new defaults userId.set('rahul'); console.log(userFormData().email); // "rahul@example.com" |
- resource() — experimental async primitive (Promise-based). For non-HTTP async (fetch, IndexedDB, etc.).
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
userResource = resource<User>({ request: () => ({ id: userId() }), loader: async ({request}) => { const res = await fetch(`/api/users/{request.id}`); return res.json(); } }); // Usage: userResource.hasValue(), .value(), .isLoading(), .error() |
For HTTP → use httpResource() (more convenient, auto-cancel).
These reduce RxJS in UI code → pure signals for async.
4. State Management: Signals Store vs NgRx / Zustand / etc. (2026 Recommendations)
Current landscape:
- Raw signals + services → small/medium apps (your UserService example earlier)
- @ngrx/signals (Signal Store) → recommended for most new apps in 2026. Simpler than classic NgRx, signal-native, extensible (rxMethod for async), great devtools.
- Classic NgRx (actions/reducers/effects) → still supported, but migrate to Signal Store for new code — less boilerplate, better perf.
- Zustand / Jotai / etc. → fine if you prefer lightweight, but NgRx Signals is more integrated (official, Angular-optimized).
- Akita / NGXS → legacy now.
Quick recommendation table (2026)
| App Size / Team | Recommended | Why? |
|---|---|---|
| Small–Medium | Raw signals + services | Zero extra deps, simple |
| Medium–Large | @ngrx/signals (Signal Store) | Structured, testable, async via rxMethod, devtools, scales well |
| Enterprise / Very Complex | NgRx Signal Store + classic NgRx if needed | Full Redux-like debugging, middleware |
| React devs migrating | Zustand-like (but prefer NgRx Signals) | Familiar, but Angular integration weaker |
Signal Store example (quick taste):
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import { signalStore, withState, withMethods, patchState } from '@ngrx/signals'; export const UserStore = signalStore( { providedIn: 'root' }, withState<{ users: User[]; loading: boolean }>({ users: [], loading: false }), withMethods((store, http = inject(HttpClient)) => ({ async loadUsers() { patchState(store, { loading: true }); const users = await http.get<User[]>('/api/users').toPromise(); patchState(store, { users, loading: false }); } })) ); |
→ Inject: userStore = inject(UserStore); → userStore.users()
5. SSR / Hydration Patterns Summary (2026 Best Practices)
- Hybrid rendering (new projects default): SSR + SSG + CSR mix
- Full SSR + incremental hydration — default for public-facing apps (SEO + fast)
- @defer + hydrate triggers everywhere non-critical
- Event replay — auto-enabled with incremental hydration
- Avoid heavy logic in constructors — defer it
- Test with @angular/ssr tools for hydration mismatches
Quick checklist:
- Enable SSR → ng add @angular/ssr
- Add provideClientHydration(withIncrementalHydration())
- Wrap heavy/interactive parts in @defer (hydrate on …)
- Use signals/resource for data → defer blocks fetch on trigger
You’ve now covered the full modern Angular path — from first component to production-grade advanced patterns!
Want to build a mini-project together (e.g., SSR dashboard with defer, signals store, httpResource)? Or revisit any chapter / debug your code? Or dive deeper into one topic (like full Signal Store tutorial)? Just say — super proud of how far we’ve come! 🚀 😊
