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

2

8

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.

Indonesian answered 20/12, 2018 at 13:11 Comment(8)
Your question was marked for triage. Off-topic for me > requires editing. Please examen your question again to see what should be improved. (this comment may be deleted after you've done the update). So-far it looks like a legit question ;-)Hebephrenia
Actually I think it's a good question. Looks OK for me! :)Counterblast
Let me rephrase: you want to use the compiler without loading the compiler. I'm sorry to say, but there is a logical problem here... You either use JIT (with the compiler) or AOT (without the compiler). @Hatef How could this be a good question?Uttica
@Uttica right, but is there any way to attach the compiler (on the fly) to the lazy loaded module to not increase initial app load time? in e.x. i saw this here but it does not work with Angular 6/7 github.com/alexzuza/angular2-build-examples/tree/master/…Indonesian
this is a (very) dangerous way to go. This is never claimed to be supported...Uttica
@Uttica ok, got it. Did you see any other possible solution/idea to build template 'on the fly'?Indonesian
@Uttica Maybe the solution is to create two separate applications - first without the compiler and second (for the part of parsing) - embedded - with the compiler.Indonesian
@Uttica it's well formatted, has included the tried code, added the link to relevant questions, definitely on topic & their question is clear - if the logic of the OP / what they are trying to do doesn't make sense to you/us, it doesn't make it a bad question.Counterblast
P
3

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! :)

Peekaboo answered 20/12, 2018 at 15:37 Comment(1)
I've working solution :) Check it out @Peekaboo : builder.ioIndonesian
C
0

I ran into the same problem last year, and was able to find a fix. I was using dynamically generated angular components in my style-guide. Here's a working example, which works with AOT compilation:

https://github.com/johncrim/angular-dynamic-styleguide

Adding import 'core-js/es7/reflect'; to polyfills.ts was the critical nonobvious trick.

Running dynamically compiled components with ng build --prod also requires

 "buildOptimizer": false,

in the production configuration in angular.json. Note that turning off the buildOptimizer will likely increase your bundle size, but at least you'll get the benefit of pre-compiling most of your code.

Cliffordclift answered 22/3, 2019 at 17:59 Comment(1)
1. Is it possible to lazyload the jit compiler in aot :D and 2. How do we execute js/ts (decorated component class) that is connected to the templateHTML "on the fly"?Cheeks

© 2022 - 2024 — McMap. All rights reserved.