DEV Community

Cover image for New input binding for NgComponentOutlet
thomas for This is Angular

Posted on • Originally published at Medium

New input binding for NgComponentOutlet

In Angular, you can create dynamic components using the NgComponentOutlet directive. However when your component has inputs, it was cumbersome to pass them through. In version 16.2.0-next.4, a new feature has been introduced, allowing you to bind your inputs much more easily.

In this article, we will explore how to achieve input binding in previous versions of Angular and the new approach introduced in version 16.2.0-next.4. Additionally, we will demonstrate another method to create dynamic components.

Before v16.2.0-next.4

Prior to version 16.2.0-next.4, you had to create an injector to set up your inputs and inject it into your dynamic component to access them.

Let's look at an example to better understand this process.

First we need an InjectionToken for better type safety:

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

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

Now in our component we can create a custom injector to set up our inputs.

@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, in OldTitleComponent , we can inject our token to retrieve our inputs.

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

This solution doesn't feel very natural but there was no other way available at that time.

After v16.2.0-next.4

Now, since input binding has been implemented, we can simply pass our inputs object to our directive and retrieve our inputs using the @Input decorator, as we would expect it to be.
Let's see this in action.

@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

So much simpler, isn't it?

Moreover, if any inputs change inside the titleInputs object, TitleComponent will be notified. 👍

Note: Inputs binding is not typed. Anything can be pass to inputs property.

Using CreateComponent

In Angular, there's another API to dynamically create components. Instead of doing it inside the template, you can achieve it in the TypeScript part using the createComponent function.

@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

To bind inputs, you need to use the setInput function. While you could do comp.instance.title =...` , if your input change,TitleComponent` will not be notified.

Note: NgComponentOutlet is using createComponent and setInput under the hood. 😉


Enjoy creating dynamic component in an easier way !! 🚀

You can find me on Twitter or Github.Don't hesitate to reach out to me if you have any questions.

Top comments (5)

Collapse
 
ezzabuzaid profile image
ezzabuzaid

Good writeup, thank you for sharing.
The data channelled through the injector is at hand in the constructor. Is it different with inputs?

Collapse
 
achtlos profile image
thomas

I'm sorry. I didn't really understand your question. 😅

Collapse
 
ezzabuzaid profile image
ezzabuzaid

I meant that with the old -injector- approach we've the data available in the constructor, simply by injecting the token. would it be the same with the new approach inputs?

Thread Thread
 
achtlos profile image
thomas

with the input approach. you have the data with the @input decorator. If you want to still have it inside the constructor with the token, you must stay with the old approach but for me it was a workaround. You don't want to deal with your inputs this way.

Collapse
 
davdev82 profile image
Dhaval Vaghani

Awesome