skip to content

Load & Render Speed

Angular's own speed levers: @defer blocks that split template chunks, NgOptimizedImage, cheap templates, change detection profiling and the service worker. Interviewers probe the trade-offs.

part ofAngularoverview, primer and where to startread it →
on this pageshow

explore

questions

30

In an Angular @defer block, what do the @placeholder, @loading and @error sub-blocks show, and are their dependencies deferred?

level: juniorimportance: must knowfreq 55%

answer

  1. before, during, after a failed fetch
  2. all three optional
  3. their imports ship eagerly
  4. error means the chunk failed

basics

~20 s

In Angular, @placeholder shows before the @defer trigger fires, @loading replaces it while the deferred chunk downloads, and @error shows if that download fails. All three are optional, and their own dependencies are loaded eagerly.

solid answer

~40 s

`@placeholder` is what the `@defer` block shows before it triggers; without one, nothing renders there. `@loading` replaces the placeholder once loading has started and stays until the dependencies resolve; without one, the placeholder simply stays until the content swaps in. `@error` renders if fetching the deferred dependencies fails, for example on a network error. All three are optional, and none of them is deferred: any component, directive or pipe they use goes in the eager bundle, so they should stay light — a skeleton, a spinner, a message. If a load fails and there is no `@error` block, Angular reports runtime error NG0750 to the application's `ErrorHandler` and leaves the current placeholder or loading content on screen.

code

ts · 22 lines
ts
import { Component, signal } from '@angular/core';
import { VideoPlayer } from './video-player';
import { Spinner } from './spinner';

@Component({
  selector: 'app-lesson',
  imports: [VideoPlayer, Spinner],
  template: `
    @defer (on viewport) {
      <app-video-player [src]="videoUrl()" />
    } @placeholder {
      <div class="player-skeleton"></div>
    } @loading {
      <app-spinner />
    } @error {
      <p>The player failed to load.</p>
    }
  `,
})
export class Lesson {
  readonly videoUrl = signal('/media/intro.mp4');
}

go deeper

for a junior

Say what each sub-block shows and when, and that all three are optional.

for a middle

Explain that sub-block dependencies are eager, what shows when a block is missing, and that @error covers loading failures only.

for a senior

Design fallbacks for chunk-load failures after deployments, and route NG0750 and render errors to your error reporting.

for a principal

Set shared placeholder and error patterns so teams keep deferred sections light and failure-tolerant across the app.

## The states of a deferrable view An Angular **`@defer` block** (v17+) moves the standalone components, directives and pipes used only inside it into lazily loaded code. Between "page rendered" and "deferred content visible", the block passes through states, and each can have its own template: | Sub-block | Shown when | Replaced by | |---|---|---| | `@placeholder` | from the first render until the block triggers | `@loading`, or the content | | `@loading` | from the moment loading starts until it finishes | the content, or `@error` | | (main `@defer` content) | once all deferred dependencies have loaded | never — the swap is final | | `@error` | if loading the dependencies fails | nothing | The states only move forward: placeholder, then loading, then complete or error. A block never goes back to its placeholder. ## `@placeholder` - Optional. Without it, **nothing** renders in the block's place until it triggers. - Required in one case: element triggers written without a target (`on viewport`, `on interaction`, `on hover`) watch the placeholder, so the block needs one with **exactly one root element**. - Its content is ordinary template content: text, elements, components. - During server-side rendering, the server outputs the placeholder for blocks without hydrate triggers. ## `@loading` - Optional. It replaces the placeholder once loading has been triggered. - If there is no `@loading` block, the **placeholder stays** on screen while the chunk downloads. - It accepts `after` and `minimum` timing parameters to avoid flicker; the placeholder accepts `minimum`. ## `@error` - Optional. It renders when fetching the deferred dependencies fails — typically a network error or a missing chunk after a new deployment. - It covers **loading** failures. An exception thrown while the deferred component itself is created or rendered is reported to the `ErrorHandler`, not turned into the `@error` state. - Without an `@error` block, a failed load keeps whatever was showing and raises **NG0750** ("`@defer` dependencies failed to load") through the application's error handling. ## Their dependencies are eager Only the main `@defer` content is split out. Anything used in `@placeholder`, `@loading` or `@error` must be available immediately, so it ships in the component's eager bundle. Two consequences: 1. Keep these blocks light: plain HTML and CSS skeletons, a small spinner component, a short message. 2. Do not reuse the deferred component itself as its own placeholder "preview". Referencing it outside the main content makes it eager, which defeats the block. ## A typical block ```html @defer (on viewport) { <app-video-player [src]="videoUrl()" /> } @placeholder { <div class="player-skeleton"></div> } @loading { <app-spinner /> } @error { <p>The player could not be loaded. <a [href]="videoUrl()">Open the video</a></p> } ``` - The skeleton reserves space and acts as the viewport target. - `app-spinner` is eager, which is fine because it is small. - The error state gives the user a working fallback instead of an empty box. ## Accessibility Screen readers that are focused on the placeholder may not announce the switch to loaded content. Wrapping the block in an element with `aria-live="polite"` makes the transitions (placeholder, loading, content or error) announced.

  • Does @error catch an exception thrown in the deferred component's constructor?
    No. `@error` renders when fetching the deferred dependencies fails. Once the code has loaded, creating and rendering the content is ordinary rendering, and an exception there is reported to the application's `ErrorHandler`; the block does not switch to its error template.
  • What does the user see if there is a @placeholder but no @loading block?
    The placeholder stays on screen while the chunk downloads, and the content replaces it when loading finishes. Angular only shows a loading state if a `@loading` block exists; the placeholder is not removed early.
  • Why should a @loading block not use a heavy charting component as its spinner?
    Dependencies of `@placeholder`, `@loading` and `@error` are loaded eagerly. A heavy component there lands in the initial bundle, so you pay for it up front to decorate a wait that deferral was meant to shorten.

saying these in an interview costs you the question

  • Components inside @placeholder are also lazy-loaded.
  • @error catches any runtime exception inside the deferred content.
  • Without @loading, the block goes blank while the chunk downloads.
  • A failed load with no @error block fails silently.
  • Once loaded, a @defer block returns to its placeholder when hidden.
open as a page

In Angular, which triggers can an @defer block use, and what happens when you write @defer with no trigger at all?

level: juniorimportance: must knowfreq 62%

basics

~10 s

An Angular @defer block loads its content when an on trigger fires (idle, viewport, interaction, hover, immediate or timer) or a when condition turns truthy. With no trigger written, it defaults to on idle.

open as a page

In Angular, what does NgOptimizedImage do when you replace an img's src with ngSrc, and why must you give width and height?

level: juniorimportance: must knowfreq 52%

basics

~20 s

Angular's NgOptimizedImage takes over an img tagged with ngSrc: it sets loading, fetchpriority and decoding, builds src and srcset through a loader, and runs dev checks. width and height are required, or fill, so the browser reserves space and avoids layout shift.

open as a page

In an Angular template, why is binding to a component method such as {{ getTotal() }} considered a performance risk?

level: juniorimportance: must knowfreq 72%

basics

~20 s

Angular re-evaluates every binding in a view each time it checks that view, so a template method runs on every check rather than only when its inputs change; a slow or per-row method multiplies that cost.

open as a page

In Angular, why can a component used inside a @defer block still end up in the initial bundle instead of a lazy chunk?

level: middleimportance: must knowfreq 48%

basics

~20 s

Angular defers a dependency only if it is standalone and nothing else in the same file references it as a value. A reference outside the block, a viewChild query, a non-standalone class or a barrel import keeps it eager.

open as a page

In an Angular @defer block, what is the difference between the main trigger and a prefetch trigger, and why combine them?

level: middleimportance: must knowfreq 52%

basics

~20 s

In Angular @defer, the main trigger decides when the deferred content is rendered; a prefetch trigger only downloads its chunk earlier. Combining them, e.g. on interaction; prefetch on idle, hides network latency without rendering too soon.

open as a page

In Angular, a page's hero banner image is its Largest Contentful Paint element; what does adding priority to its NgOptimizedImage tag change?

level: middleimportance: must knowfreq 46%

basics

~20 s

Adding priority to an Angular NgOptimizedImage switches it to loading=eager, fetchpriority=high and decoding=sync, adds a preload link when rendered on the server, and checks for a preconnect. Without it, a hero LCP image is lazy by default and logs a dev error.

open as a page

Typing into an Angular product filter panel feels laggy; how would you use Angular's profiling tools to find which component's change detection is responsible?

level: seniorimportance: must knowfreq 45%

basics

~20 s

Record the typing in the Angular DevTools Profiler, check how many cycles each keystroke causes and how tall they are, open the tallest to find the dominant component and method, then confirm with the Chrome Angular track whether the time is Angular's.

open as a page

How should an Angular app use SwUpdate's versionUpdates, checkForUpdate() and activateUpdate() to move users to a new release, and why is reloading preferred?

level: seniorimportance: must knowfreq 36%

basics

~10 s

Subscribe to versionUpdates, react to VERSION_READY by prompting the user and reloading, call checkForUpdate() for long-lived tabs, and avoid activateUpdate() without a reload because the running shell can mismatch new lazy chunks.

open as a page

In the Angular DevTools Profiler tab, what does each bar of a recording represent, and how do you find the component that made one slow?

level: juniorimportance: should knowfreq 40%

basics

~20 s

Each bar is one change detection cycle, and its height is how long that cycle took; selecting a bar shows per-component and per-directive times as a bar chart or flame graph, so the widest entry is where to look first.

open as a page

After a new deploy of an Angular app using @angular/service-worker, why do returning users see the old version first?

level: juniorimportance: should knowfreq 38%

basics

~20 s

The Angular service worker serves the version it has already cached without waiting for the network, then checks ngsw.json, downloads a changed version in the background, and serves it on the next load or reload.

open as a page

An Angular video player section uses @defer with a skeleton @placeholder; what do @placeholder (minimum 500ms) and @loading (after 150ms; minimum 1s) change?

level: middleimportance: should knowfreq 36%

basics

~20 s

In Angular @defer, minimum holds the @placeholder or @loading block on screen for at least that long, and after waits before showing @loading at all. On a fast load the spinner never appears; on a slow one it stays long enough to read.

open as a page

In Angular, how do the @defer viewport, interaction and hover triggers decide which element to watch, and what rules apply to it?

level: middleimportance: should knowfreq 38%

basics

~20 s

Angular's element-based @defer triggers watch the @placeholder's single root element by default, or an element named by a template reference variable in the same template, reachable from the block. Missing or unreachable targets are compile errors.

open as a page

In Angular, how does @defer (when expr) evaluate its condition, and how does it differ from wrapping content in @if?

level: middleimportance: should knowfreq 36%

basics

~20 s

Angular evaluates a @defer when expression during change detection of its host view; the first truthy value loads and renders the block, permanently. @if toggles content both ways but its dependencies ship in the eager bundle.

open as a page

In Angular's NgOptimizedImage, what is an image loader, how do you configure one, and what stops working without it?

level: middleimportance: should knowfreq 34%

basics

~20 s

An NgOptimizedImage loader is a function that turns { src, width, height, loaderParams } into an image URL. Provide it with IMAGE_LOADER or a built-in CDN loader provider; without one, no srcset is generated and placeholder can't work.

open as a page

In Angular's NgOptimizedImage, how is srcset generated for a fixed-size image versus one with a sizes attribute?

level: middleimportance: should knowfreq 30%

basics

~20 s

With a loader configured, Angular's NgOptimizedImage builds a 1x/2x density srcset from width when there is no sizes attribute, and a width-descriptor srcset from IMAGE_CONFIG breakpoints when sizes is set. ngSrcset or disableOptimizedSrcset override it.

open as a page

In the Angular DevTools Profiler flame graph, what does the Change detection checkbox show, and how does it help confirm that OnPush subtrees are skipped?

level: middleimportance: should knowfreq 30%

basics

~20 s

The Change detection checkbox limits the flame graph highlighting to components that actually went through change detection in the selected cycle, greying out the rest, so an OnPush subtree that should have been skipped but is still coloured stands out.

open as a page

In an Angular ngsw-config.json assetGroup, what do installMode and updateMode set to prefetch or lazy change, and which suits app bundles versus images?

level: middleimportance: should knowfreq 32%

basics

~10 s

installMode decides whether a group's files are cached up front (prefetch) or only when requested (lazy); updateMode decides whether changed cached files are re-downloaded immediately on a new version or when next requested.

open as a page

In Angular's ngsw-config.json, how do the dataGroups strategies performance and freshness answer an API request, and what do maxAge and timeout control?

level: middleimportance: should knowfreq 30%

basics

~10 s

performance (the default) answers from cache while the entry is younger than maxAge; freshness goes to the network and falls back to the cache only when the request fails or exceeds timeout.

open as a page

In an Angular @for over rows re-fetched from an API as new objects, what does track row cost compared with track row.id?

level: middleimportance: should knowfreq 55%

basics

~20 s

With track row, every refetch yields new object references, so no key matches and Angular destroys and recreates every row view and its DOM; with track row.id, rows are matched by id and only their changed bindings update.

open as a page

In Angular, when would you replace an expensive template method with a pure pipe rather than a computed() signal, and what does each cache?

level: middleimportance: should knowfreq 52%

basics

~20 s

A pure pipe caches its last result per template usage and re-runs only when an argument changes by identity, which suits per-row transforms; a computed() caches one value per instance and recomputes when a signal it read changes.

open as a page

An Angular page nests @defer blocks three levels deep, and the network panel shows their chunks loading one after another; why, and how do you fix it?

level: seniorimportance: should knowfreq 24%

basics

~20 s

An inner Angular @defer block only exists once its outer block renders, so its trigger and fetch start after the outer chunk arrives. Same-timed nested triggers therefore form a waterfall; flatten them into one block or give inner blocks later triggers.

open as a page

An Angular dashboard defers a heavy chart below the fold with @defer (on viewport), and fast scrollers see an empty placeholder for a second; how do you fix it?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Angular's on viewport starts the download only when the placeholder intersects, so the chunk fetch is on the critical path. Start it earlier: add prefetch on idle, or a viewport rootMargin, and give the placeholder the chart's size.

open as a page

An Angular product grid using NgOptimizedImage logs aspect-ratio distortion and oversized-image warnings in development; what do they mean, and how do you fix them?

level: seniorimportance: should knowfreq 24%

basics

~20 s

Angular's NgOptimizedImage compares the file, the width/height attributes and the rendered box: distortion means a ratio differs by over 0.1; oversized means the file exceeds twice the rendered size by 1000px. Fix attributes, CSS or requested widths.

open as a page

An Angular field-inspection app must start and show assigned inspections with no signal; how would you configure @angular/service-worker, and what can it not do for offline submissions?

level: seniorimportance: should knowfreq 28%

basics

~20 s

Prefetch all app code, cache inspections in a freshness dataGroup with a short timeout and long maxAge, cache reference data with performance, and make sure the worker installs before going offline; submissions still need an app-level queue.

open as a page

An Angular page lags on every keystroke in an unrelated search box because a 2,000-row price table in the same component formats a total per row via a method; how do you fix it?

level: seniorimportance: should knowfreq 44%

basics

~20 s

Each keystroke marks the shared component view dirty, so all 2,000 per-row method calls rerun; precompute totals in a computed() view model or pure pipe, move the table into its own component so it is skipped, and track rows by id.

open as a page

In Angular's TestBed, how do you test each state of a @defer block, and what do DeferBlockBehavior.Playthrough and Manual change?

level: middleimportance: nice to knowfreq 22%

basics

~10 s

Angular TestBed defaults to DeferBlockBehavior.Playthrough, where @defer blocks behave as in a browser. With Manual, blocks stay on their placeholder and you step them with fixture.getDeferBlocks() and render(DeferBlockState.X).

open as a page

What does calling Angular's enableProfiling() from @angular/core add to a Chrome DevTools Performance recording, and why call it before bootstrapApplication()?

level: middleimportance: nice to knowfreq 22%

basics

~20 s

enableProfiling() sends Angular's own events to Chrome's Performance panel as a custom Angular track (bootstrap, change detection cycles, components, template updates, hooks, DI creation), correlated with the main thread; calling it before bootstrap captures startup too.

open as a page

When a broken build of an Angular app is stuck in users' browsers through @angular/service-worker, how do the ngsw.json fail-safe and safety-worker.js remove it?

level: seniorimportance: nice to knowfreq 20%

basics

~10 s

If ngsw.json returns 404, the Angular worker deletes its caches and unregisters itself; safety-worker.js, served at the old worker script URL, unregisters any worker and removes caches for clients that cannot otherwise be reached.

open as a page

How would you set and enforce a change detection performance budget for a large Angular application, given that Angular's profiling tools only work in development builds?

level: principalimportance: nice to knowfreq 18%

basics

~20 s

Define budgets per key interaction (cycles per event, share of a frame spent in change detection), measure them relatively with the Profiler and the Chrome Angular track in development builds, and confirm against production traces and field data.

open as a page