The last couple of months I have been on and off trying to get carte blanche working with Angular 2 by making a Carte-blanche-angular2 plugin. This has provided some interesting challenges especially on how to render dynamically components at runtime using the Angular runtime compiler. The approach I took went through several changes during RC periods but I will focus on the last one which is the one compatible with the final release of Angular2 using NgModules.
The challenge is simple, create at runtime a component and render it in a page. Maybe you have something that comes from a server, or in a build step get some info and need to dynamically at runtime use that style / template or inputs and create a component and render it. This is one of the most flexible ways to do it.
An overview of the solution
Before jumping into the technical aspects and code for the solution I think it’s good to have an overview of how it works briefly. Angular works with NgModules since RC5 and that applies to dynamically generated components as well, every component needs to be registered in a module.
The solution has 4 essential steps to it :
- 1 ) Create a dynamic component outlet / wrapper(call it dynamic-outlet
- 2 ) Get the necessary parts of what makes a component (style, template, inputs etc..)
- 3 ) Dynamically generate the Component Factory with the Angular runtime compiler
- 4 ) Inject the component factory into the dynamic outlet component template using viewContainerRef
So let’s start by the first step, creating a simple component to inject the dynamically created component in. Because it needs to go somewhere right? This could totally be you root angular app or any other component where you need to place this dynamic component in.
1 & 2 - Create a dynamic component outlet and get dynamic data
This part is the simple part. You just create a component just like any other component, with only one particular thing. It will have sort of a placeholder to inject the component in. There are different ways to accomplish this injecting part using @ViewChild or using ReflectiveInjector, for this case we will use the ReflectiveInjector service.
Step 2 here is hardcoded, but you could here use your service call via http or something that would make these things dynamic like the styles or the name of the component to be rendered. Essencially provide these as an object (simillar to how its used when using the @component decorator).
Step 3 - Dynamically generate the Component Factory
In this step we will create the service used previously to generate the component factory by at runtime registering a new module and attaching the newly created component to that module.
Let’s analyze what was done in this service for a moment so we can better understand the concept behind it.
We start by calling createComponentFactory, which is a function that takes what we call the decorator data simulating what we normally send to a @component decorator :
This could have more things like change detection strategy and any other @component decorator properties but for simplicity we have these which are the most common ones being used.
The second step is to create the component with the metadata and the class (an empty class in this case for simplicity reasons) and register it in a module and compiling with the @NgModule decorator.
Finally we compile it and return the factory. The trick here is because a module contains multiple components (normally) we filter this by the one that has the class that we just created in order to return only the component we need.
Step 4 - Inject the component into the dynamic outlet component template using viewContainerRef
On step one after we get the component factory from our service’s createComponentFactory method we have to inject it into our view. Lets take a look at that.
As we can see here we use the ReflectiveInjector to inject our component into the view and this is pretty straightforward. We can then also inject dynamically inputs and other things into this component by accessing the component reference instance.
That’s it. We have a component that was created entirely at runtime, injecting inputs into it that are also dynamic and could be fetched from a server, could also be changed by some observable response for example etc.. This is a great feature of angular2 that allows for dynamic creation of components whose templates, styles and / or inputs might be delivered at runtime for whatever reason.
I haven’t explored AOT, or how that would play out in this scenario at all, if that would even be something doable or not since this is a runtime thing. At the moment this is all done in JIT mode using the RuntimeCompiler form @angular/compiler module.