skip to content

In Angular code, how do you create a component with ViewContainerRef.createComponent, and where does it end up in the DOM and view tree?

level: middleimportance: must knowfreq 58%

answer

  1. a container at a location
  2. next sibling of the anchor
  3. class in, ComponentRef out
  4. destroyed with its container
  5. standalone createComponent attaches nowhere

basics

~20 s

Inject or query a ViewContainerRef and call createComponent(ComponentClass, options). Angular inserts the new host view into that container, after its anchor element, and returns a ComponentRef for setting inputs and destroying it; the view is checked and destroyed with its container.

solid answer

~40 s

A `ViewContainerRef` is a location in Angular's view tree that can hold views. Get one by injecting it (the location of the current component or directive) or with `viewChild('slot', { read: ViewContainerRef })` on an `<ng-container #slot />` anchor. `vcr.createComponent(Widget, { bindings, index, injector })` instantiates the class, inserts its host view into the container (by default last, rendered after the anchor) and returns a `ComponentRef` with `instance`, `setInput()`, `location`, `hostView` and `destroy()`. Because it is part of the view tree, the component is checked with the surrounding view, and it is destroyed when you call `ref.destroy()`, `vcr.clear()`, or when the container's own view is destroyed. The standalone `createComponent(Widget, { environmentInjector, hostElement })` is different: it attaches to nothing, so you insert the host element, call `appRef.attachView(ref.hostView)` for change detection, and destroy it yourself.

code

ts · 25 lines
ts
import { Component, ViewContainerRef, input, inputBinding, signal, viewChild } from '@angular/core';

@Component({ selector: 'app-toast', template: `<p>{{ message() }}</p>` })
export class Toast {
  readonly message = input.required<string>();
}

@Component({
  selector: 'app-toast-stack',
  template: `<button type="button" (click)="add()">Add</button><ng-container #slot />`,
})
export class ToastStack {
  private readonly slot = viewChild.required('slot', { read: ViewContainerRef });
  private readonly count = signal(0);

  add(): void {
    this.count.update((n) => n + 1);
    const text = `Saved item #${this.count()}`;
    const ref = this.slot().createComponent(Toast, {
      index: 0, // newest first
      bindings: [inputBinding('message', () => text)],
    });
    setTimeout(() => ref.destroy(), 3000);
  }
}

go deeper

for a junior

Recall that ViewContainerRef.createComponent takes a component class and returns a ComponentRef, and that an ng-container anchor marks where it goes.

for a middle

Explain placement after the anchor, index ordering, the ComponentRef API, and that the view is checked and destroyed with its container.

for a senior

Pick between NgComponentOutlet, a view container and standalone createComponent by placement, events and lifetime, and manage overlays attached through ApplicationRef without leaking them.

for a principal

Define where runtime-created UI is allowed to live, such as one overlay service and container-based plugin slots, so lifetimes and injectors stay predictable.

## View containers Angular renders a tree of **views**. A **view container** is a slot in that tree that can hold any number of views created at runtime; `ViewContainerRef` is its handle. Every element can act as one, and there are two usual ways to get a reference: - `inject(ViewContainerRef)` in a component or directive gives the container at **its own** location; created components are placed as siblings after that host element. - `viewChild.required('slot', { read: ViewContainerRef })` on an `<ng-container #slot />` gives a container at a specific **anchor** in the template, which is the tidier choice because `ng-container` renders no element. ## Creating the component `vcr.createComponent(ComponentClass, options?)` takes the component **class** (no factory, since v22 no factory exists) and optional settings: | Option | Purpose | |---|---| | `index` | position among the container's views; default is last | | `bindings` | `inputBinding`, `outputBinding`, `twoWayBinding` set up at creation (v20) | | `directives` | host directives to apply to the new component's host (v20) | | `injector` / `environmentInjector` | override where dependencies come from | | `projectableNodes` | `Node[][]`, one array per `<ng-content>` slot | | `onError` | callback for rendering errors (developer preview in 22.2) | It returns a `ComponentRef`: - `instance`: the component object. - `setInput(name, value)`: set an input by public name, marking the view dirty. - `location`: an `ElementRef` for the host element. - `hostView` / `changeDetectorRef`: the host view. - `destroy()` and `onDestroy(callback)`. ## Where it lands The new host element is inserted into the DOM **after the anchor**, in the order of the container's views. The docs' example: a component injecting its own `ViewContainerRef` and creating `LeafContent` produces `<inner-item>…</inner-item><leaf-content>…</leaf-content>`, a sibling, not a child. In the view tree it becomes a child of the container's declaring view, which has three consequences: 1. **Change detection.** It is checked when that part of the tree is checked, like any template child. 2. **Dependency injection.** By default it resolves dependencies from the container's location, so it sees services provided by ancestors there. 3. **Lifetime.** It is destroyed with the container's view; explicit cleanup is only needed to remove it earlier. ## Removing it - `ref.destroy()` removes and destroys one component. - `vcr.remove(index)` destroys the view at an index; `vcr.clear()` destroys all of them. - `vcr.detach(index)` removes a view without destroying it, for re-insertion elsewhere. All of these run the component's `ngOnDestroy` and `DestroyRef` callbacks. ## The standalone createComponent function `createComponent(ComponentClass, { environmentInjector, hostElement?, elementInjector?, bindings?, ... })` from `@angular/core` creates a component **without** attaching it anywhere. Use it for overlays, toasts and dialogs rendered outside the current view hierarchy, typically near the end of `<body>`: - Pass `hostElement` to render into an element you created; otherwise Angular creates a detached host element matching the selector. - Call `appRef.attachView(ref.hostView)` so the component takes part in change detection. - Append the host element to the DOM yourself. - Call `ref.destroy()` when done; destroying also detaches the view from `ApplicationRef`, and you remove the host element you appended. ## Common mistakes - **Querying the anchor too early.** A `viewChild` view-container query has no result until the view is created, so call `createComponent` from an event handler, `ngAfterViewInit` or later, not from the constructor. - **Injecting when you wanted "inside".** An injected `ViewContainerRef` places components after your host; use an `<ng-container>` anchor to place them within your template. - **Leaking standalone components.** Anything made with the standalone `createComponent` lives until you destroy it; tie `ref.destroy()` to the owner's `DestroyRef`. - **Assigning inputs on `instance`.** Use `setInput` or `bindings`, or an `OnPush` component will not refresh. ## Choosing | Need | Use | |---|---| | component chosen by template state, inputs only | `NgComponentOutlet` | | inline, ordered, part of this view, outputs or host directives | `ViewContainerRef.createComponent` | | outside the view hierarchy, e.g. a body-level overlay | standalone `createComponent` + `ApplicationRef` |

  • Why is the created component a sibling of the host that injected ViewContainerRef rather than a child?
    A component's injected `ViewContainerRef` represents its own location in the parent's view, and views in a container are rendered after the anchor node. The component's inner DOM belongs to its template, which Angular manages, so dynamic views go next to it. Use an `<ng-container #slot>` inside the template to render inside instead.
  • When would you use the standalone createComponent function instead of a ViewContainerRef?
    When the component must live outside the current view hierarchy, such as a popup appended to `document.body`. `createComponent` returns an unattached `ComponentRef`; you pass an `environmentInjector` and optionally a `hostElement`, attach the host view with `ApplicationRef.attachView`, insert the element, and destroy it yourself.

saying these in an interview costs you the question

  • createComponent needs a factory from ComponentFactoryResolver first.
  • A component created via an injected ViewContainerRef is rendered inside that host element.
  • Dynamically created components are never destroyed unless you call destroy() yourself.
  • The standalone createComponent function inserts the component at the caller's location.
  • Components from ViewContainerRef.createComponent are skipped by change detection.