In Angular, how do the @defer viewport, interaction and hover triggers decide which element to watch, and what rules apply to it?
answer
- placeholder or a template reference
- exactly one root element
- same template, reachable view
- viewport options object, minus root
basics
~20 sAngular'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.
solid answer
~40 s`viewport`, `interaction` and `hover` need a DOM element. Written bare — `on viewport` — they watch the root element of the `@placeholder` block, which must have **exactly one** root element; a bare trigger without any placeholder does not compile. Alternatively you pass a template reference: `on interaction(openBtn)` with `<button #openBtn>` elsewhere in the same component template. The compiler resolves that name in the placeholder, then in the view holding the `@defer` and its parent views; a reference inside the deferred content itself, or in another component, is an error. Interaction listens for `click` and `keydown`, hover for pointer entry and `focusin`, and viewport uses a shared IntersectionObserver. Since v21 `on viewport({trigger: ref, rootMargin: '200px'})` passes observer options, all except `root`.
code
ts · 17 linesimport { Component } from '@angular/core';
import { Reviews } from './reviews';
@Component({
selector: 'app-product-page',
imports: [Reviews],
template: `
<h2 #reviewsAnchor>Reviews</h2>
@defer (on viewport({ trigger: reviewsAnchor, rootMargin: '300px' })) {
<app-reviews />
} @placeholder {
<p>Reviews load as you scroll.</p>
}
`,
})
export class ProductPage {}go deeper
Know that a bare element trigger watches the placeholder, and that you can name another element with a template reference.
Explain the one-root-element rule, where the compiler looks for a reference, and what events each trigger listens for.
Use a sentinel and rootMargin to start loading before content scrolls in, and pair interaction with hover prefetch on the same element.
Standardise trigger targets in shared layout components so teams do not fight compile errors from nested or cross-component references.
## Why some triggers need an element Three of the Angular **`@defer`** `on` triggers react to something happening to a DOM element: | Trigger | Listens for | Mechanism | |---|---|---| | `viewport` | the element intersecting the viewport | an `IntersectionObserver` shared by blocks with identical options | | `interaction` | `click` or `keydown` on the element | DOM event listeners | | `hover` | the pointer entering the element, or `focusin` | DOM event listeners | `hover` includes `focusin`, so keyboard users who tab to the element trigger it too. Every element trigger is removed as soon as the block triggers. ## Default target: the placeholder Without an argument, the trigger watches the **`@placeholder` block's root element**: ```html @defer (on viewport) { <app-reviews /> } @placeholder { <div class="reviews-skeleton"></div> } ``` Two compile-time rules apply: - A target-less `viewport`, `interaction` or `hover` needs a **`@placeholder` block**; otherwise the compiler reports that a trigger with no target can only be used on a `@defer` that has one. - That placeholder must have **exactly one root element node**. Two sibling roots or a text-only placeholder are rejected. ## Explicit target: a template reference variable Pass the name of a **template reference variable** to watch any element you choose: ```html <button #reviewsBtn type="button">Show reviews</button> @defer (on interaction(reviewsBtn)) { <app-reviews /> } ``` The compiler resolves the name in a specific order: 1. The root view of the `@placeholder` block, if there is one. 2. The view that contains the `@defer` block. 3. Each enclosing embedded view up to the component's root template (for example, a `@defer` inside an `@if` can reference an element outside that `@if`). If none has it, compilation fails with "Trigger cannot find reference". The reference must be: - in the **same component template** — not in a child component's template, not in a parent component; - **not inside the deferred main content**, which does not exist until the block triggers; - a reference to an **element**, not to a directive instance exported with `#x="dirName"`. ## Viewport options (v21) Since **v21**, `viewport` accepts an object literal with `IntersectionObserver` options. With options, the target moves into a **`trigger`** property; omit it to keep watching the placeholder: ```html @defer (on viewport({trigger: reviewsAnchor, rootMargin: '300px'})) { <app-reviews /> } ``` - All `IntersectionObserverInit` properties are allowed **except `root`**; the viewport is always the root. - `rootMargin` is the practical one: it starts loading before the element actually scrolls into view. - Blocks with identical options share one observer; different options get separate observers. ## Using the same element for prefetch The same reference can drive prefetch on a different event: ```html @defer (on interaction(reviewsBtn); prefetch on hover(reviewsBtn)) { <app-reviews /> } ``` Using the **same trigger and the same element** for both (`on viewport; prefetch on viewport`) is pointless; the `deferTriggerMisconfiguration` extended diagnostic (NG8021) flags it. ## Common mistakes - Wrapping the placeholder in two sibling elements, then wondering why `on viewport` does not compile. - Referencing an element inside a child component's template: references do not cross component boundaries. - Putting a collapsed, zero-height placeholder under `on viewport`: everything below it moves up, so the trigger point is not where the real content will sit, and the page jumps when the content arrives.
- Can a @defer trigger reference an element inside a child component's template?No. Template references are scoped to one component template. The compiler looks in the placeholder, the view containing the `@defer` block and its enclosing views of the same template. To trigger on something inside a child, put the reference on the child's host element instead, or drive the block with a `when` condition fed by an output.
- Why does on hover also fire for keyboard users?Angular's hover trigger listens for `focusin` as well as pointer events, so tabbing to the watched element counts as hovering it. That keeps a `prefetch on hover(btn)` useful for keyboard users, who would otherwise never trigger the prefetch before pressing Enter.
saying these in an interview costs you the question
- on viewport watches the deferred content entering the viewport.
- A placeholder with several root elements works; Angular watches the first one.
- A trigger can reference an element inside a child component's template.
- The viewport options object accepts root to observe a scroll container.
- on interaction reacts to hover as well as clicks.