THN Interview Prep

Angular — signals, change detection & SSR

Modern Angular requires two mental models: the classic zone-driven change detection model many applications still run on, and the newer fine-grained signals model. Senior engineers can explain how both affect performance, testing, SSR, and migration.

Core details

Classic change detection: zone.js patches async primitives so Angular can schedule a change-detection pass after async work. This made the framework ergonomic, but broad checks can become expensive when component trees, bindings, or third-party async activity grow.

OnPush: limits checks to input reference changes, events, observable emissions through AsyncPipe, and explicit marks. It improves predictability only when teams respect immutable data and avoid hidden mutation.

Signals: signal, computed, and effect model fine-grained reactivity. A writable signal updates dependents; computed values derive state; effects synchronize with external systems. Signals reduce the need for manual subscription cleanup in many UI paths.

Standalone APIs: bootstrapApplication, standalone components, route-level imports, and functional providers reduce NgModule overhead in greenfield and migration code. They also make route-level code splitting easier to reason about.

SSR and hydration: server-rendered Angular must reconcile with the browser DOM. Hydration bugs come from the same classes as other frameworks: DOM-only APIs during render, non-deterministic output, invalid markup, or directive timing assumptions.

Forms and DI: reactive forms are better for complex validation and explicit state; template-driven forms are lighter for simple cases. Dependency injection scope (providedIn, route providers, component providers) affects lifetime, testability, and accidental shared state.

Understanding

Signals are Angular’s pivot from broad “async happened, check the tree” toward explicit reactive dependency graphs. That does not make RxJS obsolete. RxJS remains strong for streams, cancellation, retries, and async composition; signals are strong for component-local state and derived view values.

Zone-driven apps often struggle because work appears far from the cause. A timer, SDK callback, or WebSocket event can trigger broad checks. The fix might be OnPush, runOutsideAngular, virtualization, signal adoption, or reducing binding work. The correct answer depends on trace evidence.

SSR adds another constraint: the first browser render must match what the server emitted. DOM measurement, storage reads, and viewport-specific branching belong after render or behind stable placeholders.

Practical examples

ProblemAngular toolSenior note
Derived local statecomputedAvoid duplicating derived values in mutable fields
External observableAsyncPipe or RxJS-to-signal bridgePreserve cancellation and cleanup semantics
Hot scroll/resize looprunOutsideAngular + throttled updateRe-enter Angular only for meaningful UI changes
Large tableCDK virtual scrollChange detection tuning will not save unbounded DOM
Complex formReactive formsModel validation, disabled state, and errors explicitly

Signal-style local state:

const quantity = signal(1);
const price = signal(25);
const total = computed(() => quantity() * price());

The template reads total() and Angular can track the dependency.

Senior understanding

LensSpeak to
PerformanceChange detection topology, binding count, virtual scroll, runOutsideAngular, signal invalidation
MigrationIncremental NgModule to standalone moves; gradual signals beside RxJS; avoid two competing state models
TestingTestBed for integration, pure service tests where possible, explicit async control with fakeAsync
SSRHydration-safe rendering, transfer state, browser-only APIs after render

Trap answers: “Angular is slow” without naming change detection topology; “signals fix everything” without discussing streams, async scheduling, and migration cost.

Practical scenario

For a legacy Angular table with slow typing and polling updates:

  1. Record whether cost is binding checks, DOM size, filtering, polling frequency, or layout.
  2. Add OnPush only where inputs are immutable and update paths are explicit.
  3. Move high-frequency scroll, resize, or polling loops outside Angular's zone, then re-enter only for meaningful UI changes.
  4. Use CDK virtual scroll for unbounded rows; change detection tuning does not fix thousands of live DOM nodes.
  5. Introduce signals for local derived UI state while preserving RxJS for streams, cancellation, retries, and async composition.

This keeps migration incremental instead of creating two competing state systems.

Failure modes

  • Mutating input objects under OnPush and wondering why the view does not update.
  • Creating effects that write back into their own dependencies.
  • Keeping long polling or animation loops inside Angular’s zone.
  • Mixing RxJS subscriptions and signals without clear ownership or cleanup.
  • Reading window during server render and causing hydration divergence.

Interview drill

Question: "How do Angular signals change the classic zone.js mental model?"

Model answer structure:

  1. Classic Angular uses zone-patched async boundaries to schedule broad change detection.
  2. OnPush narrows checks but depends on immutable inputs and explicit marks.
  3. Signals create fine-grained dependency tracking for local state and derived values.
  4. RxJS remains appropriate for async streams, cancellation, retries, and event composition.
  5. Migration should be incremental: measure first, tune topology, avoid mixed ownership, and keep SSR/hydration deterministic.

Follow-ups to expect:

  • "When should work run outside Angular's zone?"
  • "Why can OnPush fail with hidden mutation?"
  • "What belongs in a signal versus an observable?"

Diagram

Loading diagram…

See also

Mark this page when you finish learning it.

Spotted something unclear or wrong on this page?

On this page