Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 6/7 AOT: Dynamic template render - load JitCompiler for module

I have a problem with building templates "on the fly" from API response but only in AoT build.

I received from backend this kind of response:

<h1>Title...</h1> 
<some-component></some-componen> 
<p>other content</p>

And I want to parse this like regular Angular template.

The simplified code of my component looks like this:


        import {
          Compiler,
          Component,
          ComponentFactory,
          ComponentRef,
          Injector,
          Input,
          NgModule,
          OnChanges,
          OnDestroy,
          OnInit,
          ViewContainerRef
        } from '@angular/core';
        import { CommonModule } from '@angular/common';
        import { RouterModule } from '@angular/router';

        export async function createComponentFactory(compiler: Compiler, metadata: Component): Promise> {
          const cmpClass = class DynamicComponent {
          };
          const decoratedCmp = Component(metadata)(cmpClass);

          // IMPORT ALL MODULES HERE!!!
          @NgModule({imports: [CommonModule, RouterModule], declarations: [decoratedCmp]})
          class DynamicHtmlModule {
          }

          const moduleWithComponentFactory = await compiler.compileModuleAndAllComponentsAsync(DynamicHtmlModule);
          return moduleWithComponentFactory.componentFactories.find(x => x.componentType === decoratedCmp);
        }

        @Component({
          selector: 'html-renderer',
          templateUrl: './html-renderer.component.html',
          styleUrls: ['./html-renderer.component.scss']
        })
        export class HtmlRendererComponent implements OnInit, OnChanges, OnDestroy {

          @Input() content: string; 
          cmpRef: ComponentRef;

          constructor(private vcRef: ViewContainerRef, private compiler: Compiler) { }

          ngOnInit(): void {
            console.log('init...')
            console.log(this.compiler)
          }

          ngOnDestroy() {
            if (this.cmpRef) {
              this.cmpRef.destroy();
            }
          }

          ngOnChanges() {
            const html = this.content;
            if (!html) { return; }

            if (this.cmpRef) {
              this.cmpRef.destroy();
            }

            const compMetadata = new Component({
              selector: 'dynamic-selector',
              template: this.content,
            });

            createComponentFactory(this.compiler, compMetadata)
              .then(factory => {
                const injector = Injector.create({providers: [], parent: this.vcRef.injector});
                this.cmpRef = this.vcRef.createComponent(factory, 0, injector, []);
              });
          }


        }

So, I passing whole data in content input, then compiling all components via compileModuleAndAllComponentsAsync method ( https://angular.io/api/core/Compiler#compilemoduleandallcomponentssync ) and all works in JIT build.

I want to get this work in AoT compilation because now I getting an error: Runtime Compiler is not loaded when building with AoT on the example code

I also tried to provide compiler in app.module.ts in providers[] like this bu it doesn't work too:

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

    {provide: COMPILER_OPTIONS, useValue: {}, multi: true},
    {provide: CompilerFactory, useClass: JitCompilerFactory, deps: [COMPILER_OPTIONS]},
    {provide: Compiler, useFactory: createCompiler, deps: [CompilerFactory]},

My question: is there any way to include the lazy loaded module with JIT compiler to access its methods?

I found some related questions but there is no answer:

Error while using @angular compiler in Angular 5 and AOT-Build

EDIT 15.01.2019 Here is a working JIT example on stackblitz.com with interpolation and databindings test: https://stackblitz.com/github/lyczos/angular-dynamic-html-renderer

EDIT 05.01.2020 Currently, I've started using builder.io Steve (author of this project) is using web components to make it work.

like image 352
Lyczos Avatar asked Dec 20 '18 13:12

Lyczos


1 Answers

First off, I apologize for writing this as an answer, but it was too long to right as a comment.

It is possible to do what you're asking. In fact, I actually brought up this exact question at ng-conf this year. I spoke to Max Koretskyi (aka the "ng-wizard" author of angularindepth.com) after one of his sessions on this very subject.

I must warn you though the solution he provided was very convoluted, hacky, and you couldn't rely on it not breaking in future Angular releases, because what you're trying to accomplish goes against the Angular framework and is exactly what the Angular team is trying to prevent people from doing. Really it would just be a nightmare to try to maintain and any new Devs would probably pop their top trying to make sense of what I did. Heck I probably wouldn't even know what I did if I looked back on it a year+ later.

Ultimately, I settled on giving up on AOT and deployed my app using JIT and I have not regretted my decision since. If you decide you really want to pursue this further I would suggest reaching out to Max. From what I gathered at ng-conf he is a pretty friendly guy and he openly invites people to reach out to him if they have questions. Hope that helps, best of luck! :)

like image 92
Narm Avatar answered Sep 24 '22 04:09

Narm