5

I'm creating Angular code at runtime, in particular, I use a SVG library in order to create a vector graphic that contains Angular code directives like (click)='myMethod()', which, in turn, call methods I've statically defined in the SVG-enclosing component. Being generated at runtime I need to compile the created template and add it to a component. I implemented such code back then with Angular 3 with the help of this post which was quite cumbersome. I tried to the copy the old code in an Angular 8 app:

private addComponent(template: string) {

    @Component({template: template + ' <div #target></div>'})
    class TemplateComponent {

      @ViewChild('target', {static: false, read: ViewContainerRef}) public target;

      constructor() {
      }

      public myMethod() {
        // do something     
      }
    }

    @NgModule({declarations: [TemplateComponent]})
    class TemplateModule {
      @ViewChild('target', {static: false, read: ViewContainerRef}) public target;
    }

    // ERROR in next line:
    const mod = this.compiler.compileModuleAndAllComponentsSync(TemplateModule);
    const factory = mod.componentFactories.find((comp) =>
        comp.componentType === TemplateComponent
    );
    this.container.createComponent(factory);
}

which now fails with

ERROR Error: Runtime compiler is not loaded at Compiler._throwError (core.js:38932)

On the one hand, I have no clue why that error occurs. Everything I found on the internet was about critical function syntax in lazy module loading. On the other hand, I wonder, if five Angular major versions later, there is an other way to do it. I read about Portals but it seems to be about loading static templates dynamically, but not uncompiled templates generated on the fly. Looking forward to someone pointing me in the right direction.

Post Scriptum: Adding a Bounty for the one that can provide a basic running code snippet targetting Angular v9 in AoT mode. It has to contain a runtime-compiled template (e.g. from a string variable) containing a method call in the associated component.

5
  • 2
    You implemented that kind of code with Angular 3? :-) Commented May 17, 2020 at 0:21
  • 1
    The easy fix is to disable aot, but I doubt that this is a solution for you ? Commented May 17, 2020 at 7:32
  • @David, yes, very good hint. I should work in AoT. I added it to the post. Commented May 17, 2020 at 10:31
  • can you show your routing module ? Commented May 17, 2020 at 10:35
  • @nafeo module is loaded like this {path: 'user', loadChildren: () => import('./pages/user/user.module').then(m => m.UserModule), data: {breadcrumb: 'User'}} Commented May 17, 2020 at 11:25

1 Answer 1

9
+300

The JIT compiler is now excluded by default from AOT bundles so you have to include it manually. You need to install the package @angular/platform-browser-dynamic and add the Compiler providers to your application module. For example:

import { NgModule, COMPILER_OPTIONS, CompilerFactory, Compiler } from '@angular/core';
import { JitCompilerFactory } from '@angular/platform-browser-dynamic';

@NgModule({
  providers: [
    { provide: COMPILER_OPTIONS, useValue: {}, multi: true },
    { provide: CompilerFactory, useClass: JitCompilerFactory, deps: [COMPILER_OPTIONS] },
    { provide: Compiler, useFactory: createCompiler, deps: [CompilerFactory] }
  ]
})
export class AppModule { }

export function createCompiler(compilerFactory: CompilerFactory) {
  return compilerFactory.createCompiler();
}

Then you have to also make some small changes to your code because you cannot set the template to be a variable using the @Component decorator it gives a compile error. The decorator must be run dynamically in code.

Here is your method updated with the changes:

private addComponent(template: string) {
  class TemplateComponent {

    @ViewChild('target', {static: false, read: ViewContainerRef}) public target;

    constructor() {
    }

    public myMethod() {
      // do something     
    }
  }

  class TemplateModule {
    @ViewChild('target', {static: false, read: ViewContainerRef}) public target;
  }

  const componentType = Component({template: template + '<div #target></div>'})(TemplateComponent)

  const componentModuleType = NgModule({declarations: [componentType]})(TemplateModule)

  const mod = this.compiler.compileModuleAndAllComponentsSync(componentModuleType);
  const factory = mod.componentFactories.find((comp) =>
      comp.componentType === componentType
  );
  this.container.createComponent(factory);
}

I have created also a StackBlitz sample where you can see it working.

For a more detailed sample on how to do this check this Angular AOT Dynamic Components GitHub repository with a full sample.

I found this in the Angular GitHub issue about dynamically loading component templates. Since you use this it is probably good you follow the issue to be up to date on the developments.

Sign up to request clarification or add additional context in comments.

3 Comments

You probably need to set buildOptimizer : false in angular.json, or it will not work when built in prod mode
@David, thanks for the hint. Will keep that in mind when we put the code in production.
Please also consider that dynamic module imports (e.g. CommonModule) would work only with aot: false in angular.json.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.