In Angular templates, what does the developer-preview @boundary block with @error catch, and what does it not catch?
answer
- new in 22.2, developer preview
- view creation and change detection
- $error and $reset in the fallback
- listeners and async work escape it
- onViewError on the handler
basics
~20 s@boundary catches errors thrown while the components inside it are created or checked, and renders its @error block instead, exposing $error and $reset. Errors from event listeners, independent async code and projected content are not caught by it.
solid answer
~40 s`@boundary { ... } @error { ... }` arrived in Angular 22.2 as a developer-preview template block. If a component or directive inside the boundary throws during creation — a constructor, `ngOnInit` — or during change detection, Angular removes the failed view and renders the `@error` block, which can read `$error` and call `$reset()` to try again. The error is still reported: to the `ErrorHandler`'s optional `onViewError(error, details)` if it exists, otherwise to `handleError`. What it does not catch: errors thrown in template event listeners, which go straight to the handler; errors from timers or promises the component started; and content projected into a wrapper's `<ng-content>`, which belongs to the declaring template. Being developer preview, the API may still change.
code
ts · 16 linesimport { Component } from '@angular/core';
import { RevenueChart } from './revenue-chart';
@Component({
selector: 'app-dashboard',
imports: [RevenueChart],
template: `
@boundary {
<app-revenue-chart />
} @error {
<p>The chart could not be shown: {{ $error.message }}</p>
<button (click)="$reset()">Try again</button>
}
`,
})
export class Dashboard {}go deeper
Recall that @boundary shows its @error block when something inside fails to render, and that it is developer preview.
Explain what it catches, creation and change detection errors, and what escapes it: listeners, async work and projected content.
Place boundaries around independent widgets, make $reset meaningful, and route contained errors through onViewError for reporting.
Decide whether a developer-preview API belongs in production yet, and where containment helps users versus hiding real failures.
## What the block is `@boundary` is a template control-flow block added in Angular 22.2 and marked **developer preview**, meaning it is usable but its API may change before it is stable. It wraps part of a template; when something inside fails while rendering, Angular shows a fallback instead of leaving a broken view: ```html @boundary { <app-revenue-chart /> } @error { <p>The chart could not be shown: {{ $error.message }}</p> <button (click)="$reset()">Try again</button> } ``` The `@error` block has two implicit variables: **`$error`**, the caught error, and **`$reset`**, a function that clears the boundary state and renders the original content again. Several `@error` blocks can be chained with `when` conditions, evaluated in order; one unconditional `@error` block, which must come last, acts as the catch-all. ## What it catches - Errors thrown while **creating** the views inside it: component and directive constructors, input setup and `ngOnInit`. - Errors thrown during **change detection** of those views: template expressions, lifecycle hooks that run during checking. - Errors thrown from a nested `@error` block propagate to the next outer `@boundary`, or become an ordinary application error. When a boundary catches an error, Angular destroys the failed view, records the error, and refreshes the host so the `@error` block renders. ## What it does not catch | Error source | Caught by `@boundary`? | Where it goes | |---|---|---| | Constructor or `ngOnInit` inside the boundary | Yes | `@error` block, plus the handler's reporting hook | | Template binding during change detection | Yes | `@error` block, plus the handler's reporting hook | | `(click)` or other template listener | No | Straight to `ErrorHandler.handleError` | | Timer, promise or subscription the component started | No | Global error path (zone.js or the global listeners) | | Content projected into a wrapper's `<ng-content>` | Not by a boundary inside the wrapper | The boundary in the template that declares that content | The projection rule is easy to miss: projected content belongs to the view that **declares** it. A wrapper component that places `@boundary` around its own `<ng-content>` does not protect the components passed into it; the boundary must be in the parent template, around the wrapper and its content. ## How it interacts with `ErrorHandler` A caught error is not hidden from reporting. Angular looks up the `ErrorHandler` and: 1. calls **`onViewError(error, details)`** if the handler defines that optional method — `details` carries the declaring component's type and instance and, for boundary catches, a `boundary` object with the boundary component's type and a `reset` function; 2. otherwise calls **`handleError(error)`**, as for any other error. Non-`Error` values thrown inside a boundary are wrapped in an `ErrorBoundaryWrappedError` with the original value as `cause`. ## Making `$reset()` useful `$reset()` clears the boundary's error state and renders the original content again, creating fresh component instances. That only helps if something has changed since the failure: - the data that caused the error has been reloaded or corrected; - a transient condition, such as a missing service response, has passed; - the user has changed an input that fed the failing component. If nothing changed, the same constructor or binding throws again and the fallback reappears immediately. Pair the reset button with whatever refresh the failing widget needs, and consider limiting automatic retries. ## Practical guidance - Put boundaries around **independent widgets** — a chart, a recommendations panel — whose failure should not blank the whole page. - Make `$reset()` meaningful: if the same input will throw again, resetting only repeats the failure. - Keep the `@error` content simple; if it throws, the error escapes to the next boundary. - Do not confuse it with **`@defer`'s `@error` block**, which shows a fallback when lazy dependencies fail to load; that is a different block with a different trigger. - Treat it as developer preview in production code and track changes between minors.
- If ErrorHandler has no onViewError method, is a boundary-caught error still reported?Yes. Angular falls back to `handleError(error)`, so the error reaches the same place as any uncaught one. Defining `onViewError` only lets you treat contained errors differently, for example logging them at a lower severity.
- Why does a boundary inside a wrapper component not catch errors from projected children?Projected content is created and checked as part of the view that declares it, the parent template, not the wrapper's view. The boundary's error hook sits on the wrapper's embedded view, so errors from projected content never pass through it. Wrap the wrapper and its content in the parent instead.
saying these in an interview costs you the question
- @boundary catches errors thrown in its (click) handlers
- Errors caught by @boundary are hidden from ErrorHandler
- @boundary is a stable API available since v17
- A boundary around ng-content protects projected components
- @boundary's @error is the same as @defer's @error