Angular templates are often composed of many components, and these components are commonly inserted directly into a template using their selector tag. There are cases, however, when it would be useful to add components dynamically at runtime. Why? Modals and tooltips are two common examples of uses cases that can fit this description. In these situations we may want to programmatically create new components.
The basic flow goes something like this. We have a component, we’ll call it HostComponent. In HostComponent, we want to dynamically create components and then insert them into our view. Let’s start with a trivial example: every time the button in the HostComponent view is pressed, we want to create a dynamic component of type DynamicComponent and add it to our view. Let’s set up the skeleton components and templates.
The main players in the dynamic component game are ComponentFactory, ComponentFactoryResolver, ComponentRef, and ViewContainerRef. Using a combination of these utilities, we are able to create components and add them to a view from within a component.ts file. But WTF do they actually do?
The Angular docs provide no non-code description of the ComponentFactoryResolver, but you can think of this as the bridge between a Component class, and the ComponentFactory (we’ll get to that next). This is a service that we need to inject into our HostComponent to get access to, and it has one important method which is the resolveComponentFactory method:
This method takes as a parameter the Component type you would like to create, and creates a ComponentFactory for that Component type.
The Angular docs also provide no overview of what the ComponentFactory is, but it pretty much does exactly what its name says. It’s a factory that can create Components. The most important method on this ComponentFactory class is the create method:
create(injector: Injector, projectableNodes?: any, rootSelectorORNode?: string | any, ngModule?: NgModuleRef
And what this does is it creates a Component of whatever type we used in the resolveComponentFactory method. This method returns the resulting ComponentRef.
So far we know our dynamic component creation flow starts with a Component type that we want to create, then uses the ComponentFactoryResolver to create a ComponentFactory, and then uses the ComponentFactory to create a Component. And at the end of all of that we are left with a ComponentRef. The ComponentRef is a representation of the instance of a Component created by the factory. The ComponentRef is incredibly useful, as it gives us access to the Component instance as well as some helpful utility properties and methods. One particularly important property is the hostView property, which gives us access to the Component instance’s view. And one particularly important method on this class is the destroy method, which we can use to destroy the Component instance.
All of this dynamic component creation is not terribly helpful if we have no way of getting our new component into the view. The ViewContainerRef represents a container where one or more Views can be attached. We saw that the ComponentRef exposes the Component instance hostView, and this is what we need to send to our ViewContainerRef via its insert method:
insert(viewRef: ViewRef, index?: number): ViewRef
Ok let’s build something
Now that we’ve gotten past the (necessary but dense) jargon, we can actually get started. Our goal is to have a host component (HostComponent), which will create dynamic components (of type DynamicComponent) on the click of a button.
What we need before we go into the trenches are
- A predefined list of component types we want to dynamically create;
- Somewhere to put the dynamically created components in the view.
We already know that we are dynamically creating instances of DynamicComponent, but our HostComponent also needs to know this. We use the entryComponents array in the HostComponent’s decorator metadata to indicate any components that will be dynamically added.
The entryComponents array tells Angular about any other components that need to be compiled at the same time as the HostComponent. For each component in the array, Angular will create a ComponentFactory and store it in the ComponentFactoryResolver.
Now that our HostComponent knows which components we will be creating, we need somewhere to put their views. We can use any view element as the View Container, but for this example we will use an ng-container element to avoid introducing redundant elements into the DOM.
The local reference we added to the container element allows us to get access to this element in the Component. We can use the ViewChild decorator in the component to get access to this element as a ViewContainerRef:
And now we’re pretty much ready to go. We’ll add a button to the host template to trigger component creation, and we’ll build the corresponding component method.
First we injected the ComponentFactoryResolver service into the component. Let’s break this addComponent method down.
- We use the resolver service and the imported DynamicComponent to create a ComponentFactory
- We use the ComponentFactory to create a ComponentRef
- We insert the ComponentRef’s hostView into the ViewContainerRef
It should be mentioned that we could have also used the ViewContainerRef instead of the ComponentFactory to create the component, which would have looked like the following:
The createComponent method creates the component and adds its view to the View Container.
Continuing using the first method, we now have successfully created a new component on button click and added it to the view. However, the DynamicComponent has an input property which we have not dealt with, so it will show only ‘Hello !’ instead of ‘Hello (name)!’. We will add an input to the host template so that the user can enter a name and then create a new DynamicComponent using that name.
And in the addComponent method we will add this name as the input to our DynamicComponents.
Notice that we set the Component instance input property before we insert it into the view. Using the second method of ViewContainerRef.createComponent we would have to set the input property after it has already been added to the view.
The ViewContainerRef insert method can take an optional second parameter for the index at which to insert the View in the View Container. If not specified, the View will be inserted as the last View in the container. Some more useful properties of ViewContainerRef:
- get length: number: Returns the number of Views currently attached to this container.
- get(index: number): ViewRef | null: Returns the ViewRef for the View located at the specified index in the container.
- move(viewRef: ViewRef, currentIndex: number): ViewRef: Moves a View identified by a ViewRef to the container at the specified index. Returns the inserted ViewRef.
- indexOf(viewRef: ViewRef): number: Returns the index of the View, specified via ViewRef, within the current container or -1 if not found.
- remove(index?: number): void: Destroys a View attached to this container at the specified index. If index not specified, last View in container will be removed.
- detach(index?: number): ViewRef | null: you can use this with insert to move a View within the container. If index parameter is omitted, the last ViewRef is detached.
These are useful utility functions for moving and retrieving Views from a View Container. We will continue with our simple example where the Views are added to the end of the list, but it is helpful to know that these functions are available for more complicated use cases.
Now we can add unlimited instances of the DynamicComponent to the view, but maybe we want to be able to trim the list. We’ll add buttons to Delete the view at the end of the list and Clear all views.
When we remove a Component from the view, we need to ensure that the Component instance gets destroyed as well as its View. So instead of using the ViewContainerRef remove method, we will use the ComponentRef destroy method. This takes care of the Component and the View, since we are attaching the hostView from the ComponentRef.
To delete the ComponentRefs, we need to have access to them and therefore keep track of them. So we create a dynamicComponents array to which we add new ComponentRefs in addComponent.
Wrap it all up
So we started out with a HostComponent and a DynamicComponent. We got the HostComponent ready to create instances of DynamicComponent by modifying the entryComponents, injecting the ComponentFactoryResolver, and creating a ViewContainerRef in the template.
We used the ComponentFactoryResolver to create a ComponentFactory, which we used to create Component instances. We modified the instances to add input properties and then used the ViewContainerRef to insert the instance’s hostView into our container.
Finally, we used an array to keep track of all of our ComponentRefs and we used the ComponentRef destroy method to remove our dynamically added views and destroy the Component instances.
You can check it out further with this StackBlitz working demo.