skip to content

In an Angular template, how do you render a component whose class is only known at runtime, and pass it inputs?

level: juniorimportance: should knowfreq 44%

answer

  1. a structural directive from @angular/common
  2. the class goes in, not a selector
  3. an inputs object
  4. new type, new instance

basics

~10 s

Use NgComponentOutlet: <ng-container *ngComponentOutlet="widgetType; inputs: widgetInputs" />. Angular creates the given component class in place, applies the inputs object with setInput on every check, and recreates the instance when the class changes.

solid answer

~40 s

Import `NgComponentOutlet` from `@angular/common` and write `<ng-container *ngComponentOutlet="widgetType(); inputs: widgetInputs()" />`, where `widgetType` evaluates to a component class, not a selector string. The outlet creates that component in place through its own `ViewContainerRef`. Keys of the `inputs` object are the component's public input names; the directive applies them with `ComponentRef.setInput` on each check, so changes flow in and OnPush components update, and a key removed from the object is reset to `undefined`. Changing the component class, the `content`, `injector` or `environmentInjector` destroys the old instance and creates a new one; a falsy class just clears it. The outlet has no outputs option; for events, read the instance through `#o="ngComponentOutlet"` and `o.componentInstance` (null until rendered), or create the component from code instead.

code

ts · 19 lines
ts
import { Component, computed, input } from '@angular/core';
import { NgComponentOutlet } from '@angular/common';

@Component({ selector: 'app-admin-bio', template: `<p>Admin: {{ name() }}</p>` })
export class AdminBio { readonly name = input.required<string>(); }

@Component({ selector: 'app-standard-bio', template: `<p>{{ name() }}</p>` })
export class StandardBio { readonly name = input.required<string>(); }

@Component({
  selector: 'app-profile',
  imports: [NgComponentOutlet],
  template: `<ng-container *ngComponentOutlet="bio(); inputs: bioInputs()" />`,
})
export class Profile {
  readonly user = input.required<{ name: string; isAdmin: boolean }>();
  readonly bio = computed(() => (this.user().isAdmin ? AdminBio : StandardBio));
  readonly bioInputs = computed(() => ({ name: this.user().name }));
}

go deeper

for a junior

Recall that NgComponentOutlet renders a component class held in a variable, that it goes on an ng-container, and that inputs are passed as an object of input names.

for a middle

Explain that inputs are applied with setInput on each check, which inputs recreate the instance, and why outputs need componentInstance or createComponent.

for a senior

Choose between the outlet and ViewContainerRef.createComponent based on outputs, host directives, ordering and state preservation, and map string types to classes through a registry.

for a principal

Decide where runtime-selected components belong in an architecture, and how a registry keeps configuration-driven rendering safe and reviewable.

## The problem it solves A template normally names its children statically: `<app-revenue-chart />`. Sometimes the component is only known at runtime, for example an admin bio versus a standard bio chosen from user data, or a widget picked from configuration. `NgComponentOutlet`, a directive from `@angular/common`, renders **a component class held in a variable** at a spot in the template. ## Basic usage 1. Add `NgComponentOutlet` to the component's `imports`. 2. Hold the class in a field or a `computed()`, such as `computed(() => user().isAdmin ? AdminBio : StandardBio)`. 3. Put `<ng-container *ngComponentOutlet="bio()" />` where it should render. `ng-container` is used because it adds no element of its own; the outlet inserts the created component's host element after it. ## Options (microsyntax keys) | Key | Input | Effect | |---|---|---| | `inputs` | `ngComponentOutletInputs` | object of public input names to values | | `injector` | `ngComponentOutletInjector` | parent injector for the created component | | `environmentInjector` | `ngComponentOutletEnvironmentInjector` | environment for the component | | `content` | `ngComponentOutletContent` | `Node[][]`, one array per `<ng-content>` slot | | `ngModule` | `ngComponentOutletNgModule` | NgModule to load the component from (legacy apps) | ## How inputs are applied The outlet does not just assign fields. On every check of the host template it walks the `inputs` object and calls `ComponentRef.setInput(name, value)` for each key. That has useful consequences: - **Public names.** Keys are the input's template name, so aliases work, and both `input()` signals and `@Input()` fields are supported. - **Change detection works.** `setInput` marks the created component's view dirty, so an `OnPush` component (the default since v22) refreshes. - **No wasted work.** `setInput` ignores a value identical to the previous one, like a template binding. - **Removed keys are reset.** A key that disappears from the object is set to `undefined` on the next check. Because the outlet re-reads the object on each check, returning it from a signal or `computed()` keeps it cheap and predictable. ## When the instance is recreated - Changing the **component class** destroys the previous instance and creates a new one. - Changing `content`, `injector`, `environmentInjector` or `ngModule` also recreates it. - Changing only `inputs` never recreates; it updates the existing instance. - Setting the class to `null` or `undefined` clears the outlet and destroys the instance. Recreation discards the old instance's internal state, which matters for widgets with local UI state such as an open panel. ## Limits worth knowing - **No outputs.** There is no outputs key. To react to events, export the directive (`#o="ngComponentOutlet"`) and use `o.componentInstance`, which is `null` before the component renders, or switch to `ViewContainerRef.createComponent` with `outputBinding()`. - **Class references only.** The value must be the component class itself; a selector string or a name from JSON must first be mapped to a class, usually through an explicit registry. - **Lazy code.** The outlet renders what it is given; to load the class lazily, resolve a dynamic `import()` first, or use `@defer` when the choice is static. ## Common mistakes - **Passing a string.** `*ngComponentOutlet="'app-admin-bio'"` renders nothing useful; the outlet needs the class. - **Unstable class references.** Producing a different class reference on each check, for example a freshly created wrapper, recreates the instance every time; keep the class reference stable. - **Forgetting the import.** Without `NgComponentOutlet` in the component's `imports`, the template compiler rejects the unknown binding. - **Smuggling events through inputs.** Passing a callback as an input works mechanically, but hides an event behind an input; prefer `outputBinding` with `createComponent` when events matter. ## When to choose it Prefer `NgComponentOutlet` when the choice of component is driven by template state and inputs are all you need: it is declarative, participates in the surrounding view's lifecycle and needs no manual cleanup. Move to `ViewContainerRef.createComponent` when you need outputs, host directives, precise insertion order, or control over when instances are created and destroyed.

  • How do you listen to an output of a component rendered with NgComponentOutlet?
    The outlet has no outputs option. Export it with `#o="ngComponentOutlet"` and reach the instance through `o.componentInstance`, which is `null` until the component has rendered, or create the component with `ViewContainerRef.createComponent` and pass `outputBinding()` in its `bindings`, which also cleans the listener up with the component.
  • What happens to a widget's internal state when only the inputs object changes?
    Nothing is lost: the outlet keeps the same instance and applies changed values with `setInput`. State is discarded only when the component class, content, injector, environment injector or NgModule changes, because those recreate the instance.

saying these in an interview costs you the question

  • NgComponentOutlet takes the component's selector string, like 'app-admin-bio'.
  • Inputs passed to NgComponentOutlet are plain field assignments that bypass change detection.
  • Changing the inputs object destroys and recreates the component.
  • NgComponentOutlet has an outputs option for binding event handlers.
  • NgComponentOutlet is deprecated along with *ngIf and *ngFor.