skip to content

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%

answer

  1. flicker on fast networks
  2. a state held for a floor time
  3. after delays showing the spinner
  4. fast load skips the loading block

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.

solid answer

~40 s

Both parameters exist to stop flicker. `@placeholder (minimum 500ms)` keeps the skeleton for at least 500 ms after it first renders, even if the trigger fires and the chunk arrives sooner; the next state waits for the floor. `@loading (after 150ms; minimum 1s)` means: once loading starts, wait 150 ms; if the chunk has arrived by then, skip the loading block entirely and render the player. If not, show the loading block and keep it for at least one second, then swap in the player. Values are written in `ms` or `s`. The result for the video section is skeleton, then player on fast connections, and skeleton, spinner, player on slow ones — never a spinner flashing for 40 ms.

code

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

@Component({
  selector: 'app-video-section',
  imports: [VideoPlayer, Spinner],
  styles: `.player-skeleton { aspect-ratio: 16 / 9; }`,
  template: `
    @defer (on viewport) {
      <app-video-player [src]="src()" />
    } @placeholder (minimum 500ms) {
      <div class="player-skeleton"></div>
    } @loading (after 150ms; minimum 1s) {
      <app-spinner />
    }
  `,
})
export class VideoSection {
  readonly src = input.required<string>();
}

go deeper

for a junior

Recall that minimum keeps a placeholder or loading block visible for a floor time and after delays the loading block.

for a middle

Walk through fast, medium and slow loads and explain why a fast load skips @loading entirely.

for a senior

Tune after and minimum from real chunk timings, and keep skeleton sizes stable so timing fixes do not add layout shift.

for a principal

Standardise flicker thresholds across the design system instead of letting each team pick its own timings.

## The problem: flicker An Angular **`@defer` block** switches between states: `@placeholder`, then `@loading`, then the main content (or `@error`). On a fast network the deferred chunk may arrive a few milliseconds after the trigger. Without any timing rules, the user sees the skeleton, a spinner for one frame, and the content — a visual stutter that looks worse than a slightly slower but steady transition. Angular gives the two waiting blocks timing parameters: | Block | Parameter | Meaning | |---|---|---| | `@placeholder` | `minimum` | once rendered, keep it for at least this long | | `@loading` | `after` | once loading starts, wait this long before showing the loading block | | `@loading` | `minimum` | once the loading block appears, keep it for at least this long | Values are durations in **`ms`** or **`s`**. `@loading` takes both, separated by a semicolon: `@loading (after 150ms; minimum 1s)`. ## Walking through the video player section ```html @defer (on viewport) { <app-video-player [src]="src()" /> } @placeholder (minimum 500ms) { <div class="player-skeleton"></div> } @loading (after 150ms; minimum 1s) { <app-spinner /> } ``` **Fast network (chunk in 80 ms):** 1. The skeleton renders; its 500 ms floor starts. 2. The user scrolls it into view; loading starts. 3. The chunk arrives within the 150 ms `after` window, so the loading block is **never shown**. 4. The player renders once the skeleton's 500 ms floor has passed (immediately, if it already has). For the next two cases, assume the user scrolls down well after the skeleton's 500 ms floor has passed. **Slow network (chunk in 2 s):** 1. The skeleton renders and later triggers. 2. After 150 ms with no chunk, the spinner replaces the skeleton, and its 1 s floor starts. 3. The chunk arrives at 2 s; the floor has passed, so the player renders at once. **Medium network (chunk in 400 ms):** 1. After 150 ms the spinner appears. 2. The chunk arrives at 400 ms, but the spinner's 1 s floor is still running. 3. The player renders when the floor ends — about 1.15 s after loading started. The floor trades a little latency for a calm transition. ## How Angular implements it - When a state with a `minimum` renders, the block is **frozen** until the floor passes. A newer state that arrives meanwhile is queued and applied when the timer ends; only the latest queued state is rendered. - When the loading state is requested and `after` is set, Angular schedules it instead of rendering. If the content (or an error) is ready before that timer, the timer is cancelled and the loading block is skipped. - The timer logic is only included when a template actually uses `minimum` or `after`, so blocks without them pay nothing. - On the server, these timers are not scheduled. ## Choosing values - **`after`** should be just above the typical cached-chunk time, so repeat visitors never see a spinner. Around 100–200 ms is common. - **`minimum` on `@loading`** should be long enough to be perceived as intentional, often 500 ms to 1 s. Too long, and every slow load gets artificially slower. - **`minimum` on `@placeholder`** matters when the trigger can fire immediately (`on immediate`, or a viewport target already on screen) and the skeleton would otherwise flash. - Give the skeleton and spinner the **same size** as the player, so the timing smooths content changes without adding layout shift. ## What these parameters do not do - They do not change **when** loading starts; that is the trigger's job. - They do not retry a failed load; a failure goes to `@error`. - They are skipped when a test drives the block with `DeferBlockFixture.render()`, which renders a requested state immediately.

  • If the chunk arrives before the after delay, is the @loading block ever shown?
    No. With `after`, Angular schedules the loading state instead of rendering it. When the content is ready before that timer fires, the timer is cancelled and the block goes straight from the placeholder to the content, so fast and cached loads show no spinner.
  • Can a minimum make the user wait longer than the network does?
    Yes. If the chunk arrives while a `minimum` floor is running, the content waits for the floor to end. That is the deliberate trade: a little added latency on medium-speed loads in exchange for no flashing states. Keep floors short for that reason.

A theatre curtain with a stagehand rule: once the curtain is down it stays down for at least a minute, and the 'please wait' sign only goes up if the scene change takes longer than a few seconds. Quick changes happen behind the curtain with no sign at all.

saying these in an interview costs you the question

  • minimum on @loading delays the start of loading.
  • after makes Angular wait before fetching the chunk.
  • With after set, the loading block always appears eventually.
  • minimum can be set on the @error block.
  • These timers speed up the deferred load.