Dynamically changing the template for an Angular 4 components

Kirk Grover picture Kirk Grover · May 30, 2017 · Viewed 24.1k times · Source

Using Angular 4.1, I'm trying to dynamically change a module type's template before the module rendered. Is this possible?

We are bootstrapping an unknown number of components on the page (from a known list of component types), and the page may contain multiple components of the same type. I've found out a way to give each of these components a different selector so they can be rendered separately (even if they're of the same type), but I also need to give each one a different template. The template should be the inner HTML of the selected element.

Here's the code:

import { Component, NgModule, Inject, ApplicationRef, ComponentFactoryResolver, OpaqueToken, Type } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { MyNavComponent } from './MyNav.component';
import { MyRotatorComponent } from './MyRotator.component';
import { MySignUpComponent } from './MySignUp.component';

export const BOOTSTRAP_COMPONENTS_TOKEN = new OpaqueToken('bootstrap_components');

@NgModule({
    imports: [BrowserModule],
    declarations: [
        MyNavComponent,
        MyRotatorComponent,
        MySignUpComponent
    ],
    entryComponents: [
        MyNavComponent,
        MyRotatorComponent,
        MySignUpComponent
    ],
    providers: [
        {
            provide: BOOTSTRAP_COMPONENTS_TOKEN,
            useValue: [
                { type: MyNavComponent },
                { type: MyRotatorComponent },
                { type: MySignUpComponent }
            ]
        },
    ]
})
export class AppModule {
    constructor(
        private resolver: ComponentFactoryResolver,
        @Inject(BOOTSTRAP_COMPONENTS_TOKEN) private components: [Component],
    ) { }
    ngDoBootstrap(appRef: ApplicationRef) {
        console.log(this.components);
        this.components.forEach((componentDef: { type: Type<any>, selector: string }) => {
            const factory = this.resolver.resolveComponentFactory(componentDef.type);
            let selector = factory.selector;
            let nodes = document.querySelectorAll(selector);
            for (let i = 0; i < nodes.length; i++) {
                let node = nodes[i];
                (<any>factory).factory.selector = node;

                //The next line doesn't work... how can I dynamically set the template?
                (<any>factory).factory.template = node.innerHTML;

                appRef.bootstrap(factory);
            }
        });
    }
}

As noted near the end of the above code, (<any>factory).factory.template = node.innerHTML; doesn't work. I've also tried modifying the metadata for the type, but that doesn't work, either.

Is what I'm trying to achieve possible by another means? If not, is this worth submitting as a feature request?

(Note: the above code is based in part on the code of others at https://github.com/angular/angular/issues/7136.)

Update:

I'm wondering if in a future update of Angular I'll be able to achieve the same result by setting the template to <ng-content></ng-content> to include the innerHTML of the selected element. This isn't possible now with bootstrapped components, but based on this issue on Git, I'm hopeful it will be soon.

Answer

Max Koretskyi picture Max Koretskyi · May 31, 2017

You cannot set a template for a component factory after it was created. Angular compiler parses templates when generating a factory and creates a view class for each component. After the component factory and its view class are created you cannot modify it. In your example you're using ComponentFactoryResolver

const factory = this.resolver.resolveComponentFactory(componentDef.type);

which returns already created factory.

The only option is to change the template before the compiler generates factories. But I don't think it's possible. You will probably have to take a look at the dynamic generation of components.

Read Here is what you need to know about dynamic components in Angular for more information.