This past week, I ran into a need to dynamically add components using Angular. My specific reason for doing this is that the component I am using is a port of an AngularJS component and so it doesn’t quite conform to the Angular way of doing things. Specifically, I can’t change input values and have the component respond reliably. But, regardless of why I need this functionality, or even why you may need this functionality, I think being able to do this at all is pretty cool.
For the purposes of this post, I’m going to assume that you have a project that you’ve created using the Angular CLI (I’m using version 1.0.0).
To start, we are going to strip out the contents of app.component.html so we have an empty component.
If you’ve looked into this subject before, one of the first places you may have landed is the discussion labeled “Dynamic Component Loader” where they discuss dynamically adding an ad component.
The simplified, all you really need to know, version of this is as follows.
Before you create a component dynamically, you need to retrieve the component factory for it. And in order to get the factory, you’ll need a reference to a ComponentFactoryResolver object, which you can inject in your component constructor.
So, the first thing we need to do is that we need to inject the ComponentFactoryResolver into our app component.
When we are ready to add the component, we will use the resolver to create the component. But before we do that, we need a component. For our purposes, create a basic component that does nothing using
ng g component dynamic. This should create a component in your dynamic directory.
While the component was added to your declarations section of your app module, we will need to manually add an entryComponents section which specifies this component. Otherwise, it won’t be able to be loaded during runtime.
For the purposes of explaining, we are going to create the component during the afterViewInit event. You can’t create it before this point because we are going to need to retrieve a reference to the host component. Regardless of where you create the component. The steps are the same.
- Use the factory resolver to get the component factory
- Get a reference to the host container.
- Create the component using the host reference.
To get a reference to the host container, we’ll need to inject it into our constructor. So, the resulting app component code will look like this:
If you’ve been following along, you should be able to run the code now and you will see “dynamic works!” when you view it with the browser. But, all is not right.
containerRef.createComponent(componentFactory) actually inserts the component parallel to the host container, not in it as you might expect. So, instead of the component showing up inside of
<app-root> it shows up below it.
Besides, even it it had worked, you would normally want to place this new component inside some other DOM elements. How do we fix this?
What you want to do is you want to place an ng-template tag where you want the dynamic component to go and access the viewContainerRef of off it.
You will notice that we gave the ng-template a variable which we will reference in our TypeScript file using @ViewChild(). We can remove the ViewContainerRef injection, and we’ll replace the retrieval of the viewContainerRef with the viewContainerRef from ng-template.
Here is a little trick you might miss. While the tutorial on the Angular.io site has you creating a directive, we are going to skip that part and add a second parameter to our ViewChild directive telling it to retrieve the viewContainerRef from the ng-template control instead of the control. It is a lot less code!
Passing data into your component is where things get a little cloudy. While you can set the properties directly once you have an instance. Some components we work with don’t always cooperate. So, I’m going to present several ways of passing data in. You can assume that handling events works in a similar fashion. Unfortunately, the one way you can’t pass data is by passing a parameter to the createComponent function. Someday? Maybe?
Let’s start by adding an input property to the dynamic component.
And we will display it on the screen using the template.
If you run the code now, nothing will display because we have not yet set the value.
In an ideal world, the component you are creating dynamically would not need to have the Input properties set during the creation process. In this case, all you will need to do is to grab the instance of the component you created, and set the value(s). Because of change detection this works pretty well.
To make this work in our code, we just need to grab the instance and set the property.
And, when you run the code, you see “Hello World”.
Like I said, this is the ideal. But when that doesn’t work, you still have options.
In my case, I created a component wrapper. The only reason that it exists is to wrap the component so I can send in the expected data. The component wrapper is what I create and it retrieves the data it needs from some global location so that it is available when the real component I need to create ask for it.
Global data should be something you can retrieve via injection. In my case, I use NgRX/Store and retrieve what I need via that mechanism. The other, less desirable, mechanism would be to set a static variable and retrieve it that way. But if you have multiple components you need to create, this could become problematic very quickly.
As I mentioned already, I’ve yet to find a way to pass data during the creation of the dynamic component. If you know of a way to do this, PLEASE let me know in the comments.
Other post in Angular 2
- Angular 2 – First Impressions [Compared to Angular 1] - February 25th, 2016
- Angular 2 Thoughts - October 4th, 2016
- Getting Started with Angular 2 - October 25th, 2016
- Unit Testing an Angular 2 CLI Project - November 22nd, 2016
- Adding Client Side Routing to Angular 2 - November 29th, 2016
- Angular 2 Lazy Loading - December 6th, 2016
- Reasons to use RxJS Today - December 13th, 2016
- Dissecting Angular 2 Modules - December 20th, 2016
- Awesome Angular2 Architecture Options and Opinions - December 27th, 2016
- What if Everything Was Immutable? - January 10th, 2017
- Amazing Angular2 DOM Tips, Tricks, and Warnings - January 17th, 2017
- Secrets to Styling Angular2 - January 31st, 2017
- Jedi Angular 2 Tips and Tricks - March 28th, 2017
- Unit Testing Angular(2+) with JSDOM - April 4th, 2017
- More Control with Angular Flex Layout - April 11th, 2017
- Angular(2+) Model Driven Forms Are Superior - April 18th, 2017
- Dynamically Add Components in Angular - April 25th, 2017
- Using Real World NgRX - May 9th, 2017
- Functional Reactive Angular Revealed - May 30th, 2017
- NgRX/Store Coding Sanity Epiphany - June 6th, 2017
- Real World RxJS Marble Testing Revealed - June 13th, 2017
- How to Organize an Angular Application - June 20th, 2017
- Upload an Image as a File in Angular - July 4th, 2017
- How to Implement Angular 2+ Routing - July 18th, 2017
- TypeScript Basics for Angular Developers - August 1st, 2017
- Angular Observable Secrets Revealed - August 8th, 2017
- How to Upgrade NgRX to 4.x - August 15th, 2017
- Model View Presenter, Angular, and Testing - August 29th, 2017
- This One Tweak Improved my Angular Code - September 12th, 2017
- Upgrade to Angular from... - September 26th, 2017
- Using NgRX to Cleanly Aggregate Data - October 3rd, 2017
- NgRX 4 Actions - Class vs Object Literal - October 10th, 2017
- Implementing NgRX 4 - October 24th, 2017
- What I Learned Using Angular Material - October 31st, 2017
- Angular Ionic and Angular CLI - November 14th, 2017
- How to Really Screw Up an Angular Project - December 12th, 2017
- Angular Cross Field Validation - December 19th, 2017
- Attaching an Angular Child Component's Form to a Parent - January 2nd, 2018
- Why more Angular Modules are Better than One - January 16th, 2018