If You Think You Need the Angular 2 Runtime (JIT) Compiler

Isaac Mann
5 min readSep 26, 2016

--

You probably don’t.

I thought I did too. But after going through several iterations of confusion followed by revelation, I’ve realized that I can dynamically insert components in lots of different ways and still use the AoT compiler.

TL;DR; Use <template> tags (example 6), they’re awesome.

I’m going to walk through six different ways of dynamically inserting components (with working plunkers), one of which is using the runtime compiler. I’m confident that after reading this article, you’ll be convinced, as I am, that you can satisfy whatever requirements your app has while still choosing an implementation that can benefit from the goodness that is the AoT compiler.

1. Use the runtime compiler

I’m starting with the runtime compiler example (plunker), to show that I’ve tried it and it does work. If none of the other examples meets your needs, you can always fall back to this.

My first indication that this was probably not the right solution was this code:

private createDynamicComponent(): Type<any> {
@Component({
template: ‘<p>This was inserted!</p>’
})
class InsertedComponent { }

return InsertedComponent;
}

For some reason, declaring a class inside a function sets off alarm bells in my brain.

Most of the work happens in the AdHocComponentFactoryCreator service. Here we:

  1. Create a new NgModule
  2. Register the component
  3. Use the Compiler to (at runtime) create a factory for the component
  4. Find and return the component factory
_createAdHocComponentFactory(component: any): ComponentFactory<any> {
@NgModule({
declarations: [component],
entryComponents: [component],
imports: [CommonModule],
})
class AdHocModule {}
let factory = this.compiler.compileModuleAndAllComponentsSync(AdHocModule).componentFactories
.find(factory => factory.componentType === component);
this.factories.push(factory);
return factory;
}

That’s quite a lot of work and I had the nagging suspicion that I was working against the platform instead of with it. All the rest of the examples are AoT compiler compatible. Let’s find one that will work for you.

2. Use *ngIf or *ngSwitch

This was the next solution I attempted (plunker).

<div *ngIf="toggle">
<my-first-inserted-component></my-first-inserted-component>
</div>
<div *ngIf="!toggle">
<my-second-inserted-component></my-second-inserted-component>
</div>

It leaves quite a bit to be desired. It’s only dynamic in the sense that you choose from a pre-determined list. The parent component has to reference explicitly all the possible inserted components.

3. Insert a component into the primary ViewContainerRef

Here (plunker) the SwitcherComponent has no knowledge of which components are going to be inserted.

The AppComponent passes the inserted component as an input to the SwitcherComponent.

<my-switcher-component [insertedComponent]="insertedComponent"></my-switcher-component>

The meat of the logic happens in SwitcherComponent.renderComponent()

renderComponent() {
let componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.insertedComponent);
this.viewContainerRef.clear();
let componentRef = this.viewContainerRef.createComponent(componentFactory);
componentRef.changeDetectorRef.detectChanges();
}

One problem with this method is that you are always inserting the component at the root level of the SwitcherComponent template.

Note: For this example and the next two, FirstInsertedComponent and SecondInsertedComponent need to be declared as entryComponents in the AppModule.

4. Insert a component into the view template

This method (plunker) allows you to use any location in the view template as the insertion point.

All the changes are in SwitcherComponent. Somewhere in the template, include an #insertionPoint reference:

<div><ul><li><span #insertionPoint></span></li></ul></div>

And load the ViewContainerRef of that element like this:

@ViewChild('insertionPoint', { read: ViewContainerRef }) public insertionPoint: ViewContainerRef;

Then use this.insertionPoint in place of this.viewContainerRef.

5. Insert a component into the content

You can even use a location in the content of the component as the insertion point. (plunker) All you do is (1) update the AppComponent template to include a reference to #insertionPoint inside of <my-switcher-component>.

<my-switcher-component [insertedComponent]="insertedComponent">
Content: <i><span #insertionPoint></span></i> (Cool, huh?)
</my-switcher-component>

(2) Add <ng-content></ng-content> somewhere in the SwitcherComponent template and (3) switch @ViewChild to @ContentChild.

@ContentChild('insertionPoint', { read: ViewContainerRef }) public insertionPoint: ViewContainerRef;

Using the ComponentFactoryResolver seems like a lot of overhead, when all I want to do is be able to pass in arbitrary html that will be interpreted correctly as an Angular 2 template. So…

6. Use templates!

Enter <template>. (plunker — updated thanks to Matt Williams) Templates are an amazing tool, but it took me six months of Angular 2 development to discover them. What took thirty lines of convoluted code in Angular 1 is now accomplished in a few straight forward lines. With templates, you can write the html structure in the outer context and then have the component inject values from its own context into the template and render it somewhere inside the component. Pretty much exactly what I was looking for this whole time. You can even include your own components in the template and use values from the outer context.

Basically, all the power of the ComponentFactoryResolver with the simplicity of *ngIf.

The AppComponent template defines the <template> tag in the content of my-switcher-component.

<my-switcher-component>
<template #itemTemplate let-item>
<b>{{ item.label }}:</b> {{item.value}}<br/>
<div *ngIf="outsideValue === 'on'">
<my-first-inserted-component></my-first-inserted-component>
</div>
<div *ngIf="outsideValue !== 'on'">
<my-second-inserted-component></my-second-inserted-component>
</div>
{{ outsideValue }}
</template>
</my-switcher-component>`

We put #itemTemplate on the tag so the SwitcherComponent can retrieve it with @ContentChild. let-item tells Angular that whatever is injected with the key word $implicit will be bound to the item property in the context of this template. Now you can do everything inside the <template> tag that you can do elsewhere in the view template. You can use components and reference the item property or properties defined in AppComponent itself.

Looking at the SwitcherComponent, most of complexity has been removed. There’s a @ContentChild reference to the itemTemplate and an outlet <template> tag in the template.

<template [ngTemplateOutlet]="itemTemplate"
[ngOutletContext]="{ $implicit: item }"></template>

ngTemplateOutlet tells Angular which TemplateRef to insert. ngOutletContext tells Angular what properties to bind to the template context.

Simple.

Powerful.

Compatible with the AoT compiler.

Angular 2 is awesome.

Summary

I work at MVP Systems, making an Angular 2 web client for the JAMS enterprise job scheduler. The more I learn about Angular 2, the more I like it and the more excited I am to build things with it.

Here are six different ways of inserting a component into another component. I’m sure I’ve missed a few, so let me know in the comments. Also, if you still think there’s a use case that would require the use of the runtime compiler, I’d like to hear it.

[Edit] Dynamically editing component metadata can only be done with the runtime compiler, but I posted a response below explaining why I don’t think that would ever be useful.

[Addendum] Mikulas Hayek found another method to take an existing DOM element and turn it into a component. See his response for details.

--

--