Sitemap

Dynamic Components in Angular: Using createComponent API

7 min readOct 1, 2024

--

Press enter or click to view image in full size
Photo by Robin Glauser on Unsplash

Hey, guys, back again with another awesome blog post. This time it’s about Dynamic Components. You know with modern Angular applications; dynamic components provide the flexibility to create components on the fly based on user interactions or conditions. In this guide, we’ll cover the technique of dynamically creating components Using createComponent API (The Imperative Approach).

We’ll also demonstrate how to pass inputs to these dynamic components, making them adaptable to different use cases. As usual you can access this sample demo project with this link. Feel free to play around.

Dynamic Components with createComponent

So, you must be wondering what this Imperative and declarative programming is. In Angular, an imperative approach refers to a programming style where you explicitly define the steps your code should take to achieve a specific outcome. This is in contrast to a declarative approach, where you describe what you want to happen, and the framework handles the details. As usual I have created a simple demo project for you to show how dynamic components can be created with the Imperative approach with createComponent API.

<div class="product-card">
<div
class="close-button"
style="text-align: end; cursor: pointer"
(click)="onClickCloseButton()"
>
<mat-icon
aria-hidden="false"
aria-label="Example home icon"
fontIcon="close"
></mat-icon>
</div>


<img class="blog_posts_post--image" [src]="imageUri" loading="lazy" />
<div class="price-label">
<h3 class="price-text">Price : $100</h3>
</div>


<h2>{{ productName() }}</h2>
<p class="product-description">
{{ productDescription() }}
</p>
<button
mat-flat-button
style="background-color: rgb(0, 132, 255); color: aliceblue"
>
Order
</button>
<ng-content> </ng-content>
</div>
export class ProductCardComponent implements OnInit {
productName = input('Mountain Bicycle');
productDescription = input(
"Lorem Ipsum is simply dummy text of the printing and typesetting industry.Lorem Ipsum has been the industry's standard dummy text ever since the 1500s"
);


productType = input<ProductType>(ProductType.BIKE);
onCloseProductCard = output<boolean>();


showMessage: boolean = false;
imageUri: string = '';


ngOnInit(): void {
this.setImageURI(this.productType());
}


onClickCloseButton() {
this.onCloseProductCard.emit(true);
}
}

At first glance, it might seem complex, but it’s actually a straightforward component with just three inputs and one output. The unique syntax for these inputs and outputs is what sets it apart. If you’re curious about this, I highly recommend checking out the fantastic article on Angular University: Angular Signal Components: Input, Output (Complete Guide).

If you’re already familiar with signals and signal-based inputs, you’ll quickly grasp the functionality of this component. Let’s dive in and explore the simplicity behind the complexity! 😎

Final Product Card Component

So, our Next step is to dynamically render this component at runtime. For this I’m using the root component which is the app component.

<div class="product-container">
<!-- Buttons row -->
<div class="creator-buttons-container">
<div class="add-to-cart" (click)="onCrateButtonClick()">
<a>Add Dynamic Component</a>
</div>
<div class="add-to-cart" (click)="onDestroyButtonClick()">
<a>Remove Dynamic Component</a>
</div>
</div>


<!-- Product cards row -->
<div class="product-cards-row">
<ng-container #container> </ng-container>
</div>
</div>
export class AppComponent {
vcr = viewChild('container', { read: ViewContainerRef });
#componentRef: ComponentRef<ProductCardComponent>;


onCrateButtonClick() {
this.#componentRef = this.vcr()?.createComponent(ProductCardComponent);


this.#componentRef.setInput('productName', 'PineApple');
this.#componentRef.setInput('productType', ProductType.FRUIT);


this.#componentRef.instance.onCloseProductCard.subscribe((value) => {
if (value) this.#componentRef?.destroy();
});
}


onDestroyButtonClick() {
this.vcr().clear();
}
}

In this example, we demonstrate how to dynamically add and remove components in an Angular application. The HTML template includes a product container with two main sections: a row of buttons and a row for product cards. The buttons allow users to add or remove dynamic components. The ng-container element, marked with a #container template reference variable, serves as the host for these dynamically created components.

Here this vcr @ViewChild decorator is used to get a reference to the ng-container element in the template. It reads the ViewContainerRef, which allows us to dynamically create and insert components into the DOM. #componentRef is a private reference to the dynamically created ProductCardComponent. It holds the instance of the dynamically created component so it can later be manipulated or destroyed.

When onCrateButtonClick method triggers,it Dynamically creates the ProductCardComponent and inserts it into the vcr (the container reference).

Press enter or click to view image in full size
AppComponent

Destroying Dynamically Created Components?

In the above example If you keep pressing the create button you will see that it will create multiple components within the container. But how do we destroy these components?

Press enter or click to view image in full size
 onDestroyButtonClick() {
this.vcr().clear();
}

If you check this onDestroyButtonClick action Listener, this.vcr().clear(); line clears all the components that have been added to the ViewContainerRef (vcr). It effectively removes all dynamically created components from the DOM. But however, if you want to remove a specific component ViewContainerRef by its index. In this case you can use,

this.vcr().remove(0);

This removes the component at index 0. You can specify any index to remove the corresponding component.

How to add Inputs?

We saw that in previous code ProductCardComponent had some signal-based inputs. Let’s see how we can pass these inputs when creating the component dynamically at runtime.

Get Buhuti Manakal’s stories in your inbox

Join Medium for free to get updates from this writer.

In early days we could just access the component instance and directly mutate the input like this.

this. #componentRef.instance.productName = PineApple;

But now since we are using signal-based inputs, Angular has provided us with a more straightforward API that is setInput. (It will work for non singal inputs as well)

this.#componentRef.setInput('productName', 'PineApple');
this.#componentRef.setInput('productType', ProductType.FRUIT);

We could just directly componentRef and set its inputs by passing the input name and the value. But still there is a small downside because setInput method parameters aren’t strongly typed. So be conscious when writing the code.

Adding Outputs to Dynamic Components

Earlier, we discussed how to add inputs to dynamic components. But what about outputs? If you recall our ProductCardComponent, it has a method onClickCloseButton that emits a boolean value using a signal-based output. So, how do we listen to this output from our parent component?

Here’s how we can do it:

TypeScript
this.#componentRef.instance.onCloseProductCard.subscribe((value) => {
if (value) this.#componentRef?.destroy();
});

in this code, we directly subscribe to the onCloseProductCard event emitted by the dynamically created component instance. When the component emits this event, the component will be destroyed with the line this. #componentRef? destroy().

You might be wondering about potential memory leaks since we haven’t explicitly unsubscribed from the onCloseProductCard event. However, since we are using the latest Angular outputs, we don’t need to manually handle the Un subscription. Angular takes care of this for us, ensuring that there are no memory leaks.

For more details, you can refer to the Angular documentation on the output() function.

More Advanced Concept: Content Projection in Dynamic Components

Now that we’ve learned how to dynamically create components and how to add inputs and outputs to them, let’s explore a more advanced concept: projecting content into our dynamically created components. While this requirement might be rare, it is certainly possible and can be quite useful in certain scenarios.

If you’re new to content projection or need a refresher, I highly recommend reading my article on Angular Content Projection. It provides a comprehensive overview of the topic. Saying that, let’s dive into the topic.

First, you need to create a projection slot using <ng-content> in your dynamically rendered component. Here’s an example:

<!-- product-card.component.html -->
<ng-content></ng-content>

Now on our AppComponent I will slightly change our code like this,

vcr = viewChild('container', { read: ViewContainerRef });
notifyMeTemplate = viewChild<TemplateRef<unknown>>('notifyMe');
#componentRef: ComponentRef<ProductCardComponent>;
onCrateButtonClick() {
const contentView = this.vcr().createEmbeddedView(this.notifyMeTemplate());
this.#componentRef = this.vcr()?.createComponent(ProductCardComponent, {
projectableNodes: [contentView.rootNodes],
});
this.#componentRef.setInput('productName', 'PineApple');
this.#componentRef.setInput('productType', ProductType.FRUIT);
this.#componentRef.instance.onCloseProductCard.subscribe((value) => {
if (value) this.#componentRef?.destroy();
});

In our project we have a component called NotifyMeComponent Our goal is to project that content (NotifyMeComponent) inside the projection slot within the product card component.

In the above example,

  1. Template Definition: The <ng-template> with the #notifyMe reference defines a template that contains the content we want to project. This template acts as a blueprint for creating views.
  2. ViewContainerRef: The ViewContainerRef (vcr) is a container where one or more views can be attached. It is injected into the component using the @ViewChild decorator.
  3. Creating the Embedded View: When the onCreateButtonClick method is called, the createEmbeddedView method is used to create an embedded view from the notifyMeTemplate. This method takes the TemplateRef and an optional context object. In our case, we don’t need a context object, so we simply pass the template reference.
  4. Projecting Content: The createEmbeddedView method returns a ViewRef object, which contains the root nodes of the created view. These root nodes are then passed to the projectableNodes array when creating the ProductCardComponent. This allows the content defined in the notifyMeTemplate to be projected into the <ng-content> slot of the ProductCardComponent.

Conclusion

Well in this article we discussed in depth how to create dynamic components using the imperative approach. Dynamic components in Angular provide a powerful way to create flexible and interactive applications. By leveraging the ViewContainerRef and TemplateRef, you can dynamically create and manage components, project content into them, and handle their inputs and outputs efficiently. This approach not only enhances the user experience but also makes your application more modular and maintainable.

Understanding and utilizing dynamic components can significantly improve the responsiveness and flexibility of your Angular applications, allowing you to build more dynamic and user-friendly interfaces.

In our next blog Post we will be continuing this discussion with how to create dynamic Components in Angular with a declarative approach using ComponentOutlet . So, follow me for more awesome content like these. Till we meet again happy coding. 😎💻

--

--

Buhuti Manakal
Buhuti Manakal

Written by Buhuti Manakal

Software Engineer at Random Software LTD