In an Angular template, how do you render a component whose class is only known at runtime, and pass it inputs?
answer
- a structural directive from @angular/common
- the class goes in, not a selector
- an inputs object
- new type, new instance
basics
~10 sUse 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 sImport `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 linesimport { 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
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.
Explain that inputs are applied with setInput on each check, which inputs recreate the instance, and why outputs need componentInstance or createComponent.
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.
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.