Before v16.2.0-next.4

In earlier versions, setting up inputs for dynamic components created with NgComponentOutlet required manually assembling an injector. That injector then had to be passed into the component so the values could be accessed.

Consider this example to see how it worked.

Start by defining an InjectionToken to keep things type-safe:

interface TitleInputs {
  title: string;
  subTitle: string;
}

const INPUTS = new InjectionToken<TitleInputs>('title inputs');
Enter fullscreen mode Exit fullscreen mode

Then, within the host component, build a custom injector that carries the input values.

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [NgComponentOutlet],
  template: `
    <ng-template *ngComponentOutlet="template; injector: customInjector" />
  `,
})
export class AppComponent implements OnInit {
  template = OldTitleComponent;

  titleInputs = {
    title: 'Inputs for Component outlets',
    subTitle: `That's awesome`,
  };

  customInjector = Injector.create({
    providers: [{ provide: INPUTS, useValue: this.titleInputs }],
  });
}
Enter fullscreen mode Exit fullscreen mode

Finally, inside OldTitleComponent, inject that token to pull the values out.

@Component({
  selector: 'app-title',
  standalone: true,
  template: `OldWay: {{ inputs.title }} {{ inputs.subTitle }}`,
})
export class OldTitleComponent {
  inputs = inject(INPUTS);
}
Enter fullscreen mode Exit fullscreen mode

That approach was clunky, but it was the only one available.

After v16.2.0-next.4

With the introduction of dedicated input binding, you can now supply an inputs object straight to the directive. The dynamic component reads them through the standard @Input decorator — exactly how you'd expect.
Here is how it looks.

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [NgComponentOutlet],
  template: `
    <ng-template *ngComponentOutlet="template; inputs: titleInputs" />
  `,
})
export class AppComponent implements OnInit {
  template = TitleComponent;

  titleInputs = {
    title: 'Inputs for Component outlets',
    subTitle: `That's awesome`,
  };
}
Enter fullscreen mode Exit fullscreen mode
@Component({
  selector: 'app-title',
  standalone: true,
  template: `NewWay: {{ title }} {{ subTitle }}`,
})
export class TitleComponent{
  @Input() title!: string;
  @Input() subTitle?: string;
}
Enter fullscreen mode Exit fullscreen mode

Much cleaner, right?

And if any property on the titleInputs object changes, TitleComponent gets updated automatically. 👍

Note: This binding is not typed. The inputs property accepts any value.

Using CreateComponent

Another Angular API for dynamic component creation exists outside the template layer. Using the createComponent function from TypeScript gives you the same result.

@Component({
  selector: 'app-root',
  standalone: true,
  template: ``,
})
export class AppComponent implements OnInit {
  ref = inject(ViewContainerRef);
  envInjector = inject(EnvironmentInjector);

  ngOnInit(): void {
    const comp = this.ref.createComponent(TitleComponent, {
      environmentInjector: this.envInjector,
    });
    comp.setInput('title', 'Inputs with createComponent');
    comp.setInput('subTitle', 'Still works!');
  }
}
Enter fullscreen mode Exit fullscreen mode

For input binding, the setInput method is the way to go. Assigning directly with comp.instance.title = ... works, but it won't notify TitleComponent when the value changes.

Note: Internally, NgComponentOutlet relies on createComponent and setInput. 😉


Building dynamic components is now significantly simpler. 🚀