Dynamic Components with Content Projection in Angular

Reading Time: 4 minutesIn this article, we are going to learn how to apply content projection to dynamically generated components.

The idea is this: imagine you have a modal window component — you want to encapsulate the idea of a popup message that can be reused in many different situations.

But there’s a problem — how do you customize the modal for each individual situation? In this example, we’ll show how to create a dynamically generated component that also supports content projection.

You can run the final example on StackBlitz here.

We’ll create a modal component and expose a modal service that will give our consumers the ability to open the modal in one of the following methods:

Let’s start by creating the modal component:

We have wrapping elements for styling purposes and ng-content tag that will be replaced with a custom component, template or a string that will get from the consumer.

Let’s continue with the service.

The resolveComponentFactory() method takes a component and returns a ComponentFactory. You can think of ComponentFactory as an object that knows how to create a component. Once we have a factory, we can use the create() method to create a componentRef instance, passing the current injector.

A componentRef exposes a reference to the native DOM element which we append to the body.

In this stage, we can call the service open() method, and we’ll get an empty working modal.

What we want now is to give our consumers the ability to pass a custom component, a template or a string and inject it as ng-content. Let’s see how can we do it.

The factory create() method excepts as the second parameter projectableNodes, which is a two-dimensional array of DOM elements that Angular will pass as ng-content to the current component.

Let’s see how can we get a reference to the DOM elements in each of the cases mentioned above.

Handling Strings

Strings are the most straightforward case. If the content is a string, we create a text node.

Now we can call the open() method and pass a string that’ll be displayed in the modal.

Handling Templates

By template, we mean a TemplateRef. For example, we want to be able to pass a reference to the template:

Let’s update our resolveNgContent() method and add support for this functionally.

We instantiate an embedded view based on the TemplateRef which returns a viewRef instance. This instance exposes a rootNodes property, which is an array of DOM nodes that are extracted from the template.

Handling Components

The last and most powerful option is to be able to pass a custom component that will be injected as ng-content. For example:

Let’s update our resolveNgContent() method and add support for this functionally:

The same process follows: get the factory, create the component and pass the reference to the native DOM element.

Pay attention that every dynamic component must be declared as entryComponent.

You may have wondered why the projectableNodes parameter is a two-dimensional array. The reason is that we can pass more than one ng-content. For example:

Complete Code Example

You can view the complete code example here

I want to take this opportunity to mention that we recently came out with Akita, which offers simple and effective state management for Angular applications. Check it out here.

About the Author

Netanel is a Frontend Architect who works at Datorama, blogs at netbasal.com, open source maintainer, creator of Akita and Spectator, Husband, Father and the Co-founder of HotJS.

  • http://www.groupe-sma.fr Alain Boudard

    Oh that’s really handy !
    Very clever use of content projection !

    BTW, didn’t you mean “expects” ?
    “The factory create() method excepts as the second parameter projectableNodes”

    Thanks for sharing.

    • NetanelBasal

      Yes, thanks Alain!

  • Fatih Emre

    Cool solution! Thank you but how do we close the modal? Esc button or backdrop click not working like other modals. What is best practise for this problem?

    • Fatih Emre

      Sorry, this is not problem. This is a feature

      • NetanelBasal

        You should emit an event from the modal and listen to it from the open() method and call destroy.

  • guyn

    Hi,
    Thanks for taking the time to write this post – however there’s a bug / problem with your implementation.
    the ngOnInit of the child component (login-modal) isn’t being called which I suspect points at a larger problem.

    I believe your just missing after line 54
    this.appRef.attachView(componentRef.hostView);