Angular 22.2 brought error boundaries to templates with @boundary and @error. Components you create from code get their own version: an onError option on ViewContainerRef.createComponent, on ViewContainerRef.createEmbeddedView and on the standalone createComponent() function.

What onError catches

The callback gets the error and an ErrorDetails object. It runs when the view throws during change detection or in a lifecycle hook such as ngOnInit, for example when a template binding hits bad data. Errors from views nested inside it bubble up to it too, and anything thrown that is not an Error arrives wrapped in one.

@Component({
  selector: 'app-widget-slot',
  template: `
    <ng-container #slot />
    @if (failed()) {
      <p>This widget couldn't load.</p>
    }
  `,
})
export class WidgetSlot {
  private slot = viewChild.required('slot', { read: ViewContainerRef });
  failed = signal(false);

  show(widget: Type<unknown>) {
    this.failed.set(false);
    this.slot().clear();
    this.slot().createComponent(widget, {
      onError: (error, details) => {
        console.warn('Widget failed:', details.declarationType, error);
        this.slot().clear();
        this.failed.set(true);
      },
    });
  }
}

What it leaves to you

Unlike @boundary, onError does not swap anything out. Angular calls your function, treats the error as handled and leaves the failed view in the container. Removing it and showing a fallback is up to you; in a quick test, calling clear() inside the callback removed the broken widget and the rest of the page kept updating. There is no reset() either, because details.boundary is only set for @boundary. A retry means creating the component again.

Writing to a signal inside onError, as failed.set(true) does above, needs 22.2.2. In 22.2.0 and 22.2.1 the callback was not untracked yet, and the write failed with NG0600.

What it does not catch

Errors in the constructor are not caught: createComponent throws them straight away, so wrap the call in try/catch if a widget can fail that early. Errors in event listeners and in afterRender hooks still go to your ErrorHandler.

Logging is your job as well. ErrorHandler has a new optional onViewError hook, but Angular calls it for errors caught by @boundary, not for onError. And details.declarationType is the class itself. In a minified build its name won't match your source, so compare the class (details.declarationType === RevenueWidget) instead of logging names.

@boundary and ErrorDetails are in developer preview. For dashboards that load widgets from configuration, it means one widget with bad data no longer has to take the whole page down.

Sources

I post short notes like this on LinkedIn.

Need help with this?

Two panels: without onError, a widget created with vc.createComponent(Widget) throws in its template and the whole dashboard breaks; with onError: (err, details) => showFallback(details), the other widgets keep rendering. Below, createComponent(RevenueWidget) with an onError callback that records details.declarationType.name and logs the error.

Have an Angular app that needs a senior hand?

Tell me about your app and what is slowing your team down. We go through it on a call and you leave with concrete next steps.