9

On angular5, i try to have on same project AOT compilation for most of my module/component... but i have one part who need to be JIT compiled.

For this second part, HTML come from Ajax request and contain some component tag who must be compiled by angular. To Manage this part i use directive who looks like :

export class ArticleLiveDirective implements OnInit, OnChanges, OnDestroy {

    // [...]    

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

    // [...]

    private addHtmlComponent(template: string, properties: any = {}) {
        this.container.clear();
        //Force always rootDomElement.
        const divTag = document.createElement('div');
        divTag.setAttribute('id',this.currentId);
        divTag.innerHTML = template;
        template = divTag.outerHTML;

        // We create dynamic component with injected template
        @Component({ template })
        class ArticleLIveComponent implements OnInit, OnChanges, OnDestroy {
            constructor(
                private articleService: ArticleService
            ) {}
            ngOnInit() {}
            ngOnChanges(changes: SimpleChanges) {}
            ngOnDestroy() {}
            goToPage($event: Event, pagination: string) {
                this.articleService.askToChangeArticle(pagination);
                //Stop propagation
                $event.stopPropagation();
                return false;
            }

        }

        // we declare module with all dependencies
        @NgModule({
            declarations: [
                ArticleLIveComponent
            ],
            imports: [
                BrowserModule,
                MatTabsModule
            ],
            providers: []
        })
        class ArticleLiveModule {}

        // we compile it
        const mod = this.compiler.compileModuleAndAllComponentsSync(ArticleLiveModule);
        const factory = mod.componentFactories.find((comp) =>
            comp.componentType === ArticleLIveComponent
        );
        // fetch instance of fresh crafted component
        const component = this.container.createComponent(factory);
        // we inject parameter.
        Object.assign(component.instance, properties);
    }
}

As you can see i can call addHtmlComponent method to compile new component on runtime with custom HTML as template.

My template looks like :

<div>
<h2>Foo bar</h2>
<mat-tab-group>
  <mat-tab label="Tab 1">Content 1</mat-tab>
  <mat-tab label="Tab 2">Content 2</mat-tab>
</mat-tab-group>
<p>Other content</p>

everything work perfectly until i switch to AOT compilation (fyi i use : https://github.com/angular/angular-cli/tree/master/packages/%40ngtools/webpack)

Possible reason : Main reason i guess is because AOT compilation delete "compiler" part of Angular from output compiled bundle. What i have try - I have try to require it directly on my code but still is not present. - I have try to check how website like angular (or angular material) deal with it. But is not fit with my case. In fact, both already have compiled version of all examples in AOT version. Dynamic part is "just" content around sample.

If you want to check how angular material do it : All Website examples for each component : https://github.com/angular/material2/tree/master/src/material-examples

Then they have loader : https://github.com/angular/material.angular.io/blob/master/src/app/shared/doc-viewer/doc-viewer.ts#L85

Is may be right way to do it but i am don't know how to adapt it to manage, dynamic Tab content.


EDIT : i have add sample here : https://github.com/yanis-git/aot-jit-angular (branch Master)

As you will see, AOT compilation remove wall compiler from bundle, this result :

Module not found: Error: Can't resolve '@angular/compiler/src/config'

I have try to force compilater Factory on AppModule, but still no result.

I have another sample on same repo, but on branch "lazy-jit", now i have Compiler embed on the outputed bundle, but new error come to me :

ERROR Error: No NgModule metadata found for 'ArticleLiveModule'.

Who looks to be exactly same than this issue : https://github.com/angular/angular/issues/16033

Yanis-git
  • 7,737
  • 3
  • 24
  • 41
  • Angular material doesn't compile template at runtime. They use entryComponents https://github.com/angular/material2/blob/e7af740471d257a4d682f3f2f2630dff524d2608/tools/gulp/tasks/example-module.ts#L118 – yurzui Mar 20 '18 at 10:14
  • Yes, is what i have try to explain. I can't use same way of material website because they use pre-compiled component sample. – Yanis-git Mar 20 '18 at 13:25
  • https://github.com/angular/angular/issues/20639#issuecomment-347149868 – yurzui Mar 20 '18 at 13:28
  • Thx yurzui for this ressource, i have already try it, but unfortunatly. Result compilation without compiler part of Angular. – Yanis-git Mar 20 '18 at 13:31
  • Can you create simple application to reproduce it? – yurzui Mar 20 '18 at 13:39
  • I just edit my original topic to add github repository, as you will see, what ever i configure on AppModule to force to load JitCompiler, is still not present on the output bundle. – Yanis-git Mar 21 '18 at 06:33
  • @Yanis-git, what are you trying to achive aot and jit? I saw your sample, the only thing i see is dynamic template. – hendrathings Mar 21 '18 at 12:13
  • @hendrathings Yes i try to compile dynamic template with embed other components call (like MatTabs on my sample and also custom function embed like onClick, onScroll) – Yanis-git Mar 21 '18 at 17:08

2 Answers2

7

Try this:

    import { Compiler, COMPILER_OPTIONS, CompilerFactory, NgModule } from '@angular/core';
    import { BrowserModule, } from '@angular/platform-browser';
    import { FormsModule } from '@angular/forms';

    import { AppComponent } from './app.component';
    import { HelloComponent } from './hello.component';


    import { JitCompilerFactory } from '@angular/platform-browser-dynamic';

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


    @NgModule({
      providers: [
        { provide: COMPILER_OPTIONS, useValue: {}, multi: true },
        { provide: CompilerFactory, useClass: JitCompilerFactory, deps: [COMPILER_OPTIONS] },
        { provide: Compiler, useFactory: createCompiler, deps: [CompilerFactory] }
      ],
      imports: [BrowserModule, FormsModule],
      declarations: [AppComponent, HelloComponent],
      bootstrap: [AppComponent]
    })
    export class AppModule { }

CODE EXAMPLE

But JitCompiler still not able to create Dependency Injection tree. I suspect @Injectable to be remove from AOT part. But i can't do your trick.

In code example above, there is no decorators for NgModule and Component. So, it means there is no @Injectable too and they can't inject providers. So why we don't write for @NgModule and @Component @Injectable decorator and only write it to Services? Because, they have a decorators(@NgModule/@Components), Services not. And their decorators is sufficient for Angular to know that their are injectable.

CODE EXAMPLE with DI.

UPDATE: Created custom wrapper CustomNgModule, CustomComponent and CustomInjectable decorators:

export function CustomComponent(annotation: any) {
    return function (target: Function) {
        const component = new Component(annotation);
        Component(component)(target);

    };
}

export function CustomNgModule(annotation: any) {
    return function (target: Function) {
        const ngModule = new NgModule(annotation);
        NgModule(ngModule)(target);
    };
}


export function CustomInjectable() {
  return function (target: Function) {
      const injectable = new Injectable();
      Injectable()(target);
  };
}

When building with AOT flag, Angular-CLI looks like cleans bundle from native decorators from parts of code which need to be compiled dynamically.

And where you want dynamically compile modules with components in AOT with DI functionality, replace native decorators (NgModule/Injectable...) with custom one to preserve decorators in AOT compilation mode:

lazy.module.ts:

@CustomComponent({
  selector: 'lazy-component',
  template: 'Lazy-loaded component. name:  {{name}}.Service 
           {{service.foo()}}!',
  //providers: [SampleService]
})
export class LazyComponent {
  name;
  constructor(public service: SampleService) {
    console.log(service);
    console.log(service.foo());
  }
}

@CustomNgModule({
  declarations: [LazyComponent],
  providers: [SampleService]
})
export class LazyModule {
}

app.component.ts:

...
 ngAfterViewInit() {

    this.compiler.compileModuleAndAllComponentsAsync(LazyModule)
      .then((factories) => {
        const f = factories.componentFactories[0];    
        const cmpRef = this.vc.createComponent(f);    
        cmpRef.instance.name = 'dynamic';
      });
  }
...

CODE EXAMPLE 3

Yerkon
  • 4,548
  • 1
  • 18
  • 30
  • Hello Yerkon, I have already try it on my attached code sample : https://github.com/yanis-git/aot-jit-angular/blob/master/src/app/app.module.ts ------ This result : ERROR Error: No NgModule metadata found for 'ArticleLiveModule'. You sample work because stackblitz doesn't compile in AoT – Yanis-git Mar 24 '18 at 06:18
  • @Yanis-git, download the source code(export button). I tested it locally with: `ng build --prod`. – Yerkon Mar 24 '18 at 06:20
  • Is true, your solution import right JitCompiler and keep meta from component / module. But JitCompiler still not able to create Dependency Injection tree. I suspect @Injectable to be remove from AOT part. But i can't do your trick. Sample from your original one : https://stackblitz.com/edit/angular-jit-9jhqx3?file=app%2Fapp.component.ts Please check console log for more detail. – Yanis-git Mar 24 '18 at 08:47
  • Thx for your help, so if i understand well, is impossible to both have "dynamic template definition" and, Module/Service injection on this component ? – Yanis-git Mar 24 '18 at 10:50
  • @Yanis-git, no it's possible. Later I will post code example, where with aot flag dynamically create component/module with dI – Yerkon Mar 24 '18 at 11:39
  • Awsome, thanks for your great help, i will validate your answer when you will post your new code example. – Yanis-git Mar 24 '18 at 11:51
  • This work perfectly, following link show how to deal with your solution and component template as variable : https://stackblitz.com/edit/angular-aot-dynamic-compile-module-n6scvj?file=app%2Fapp.component.ts ----- Thank you for your help !!! – Yanis-git Mar 24 '18 at 16:56
  • you will never believe me, is like each little step is stoking for me. Please pay attention to this link : https://stackblitz.com/edit/angular-aot-dynamic-compile-module-n6scvj Now if i try to import 3th party library... Compilation work well, no error, but i can't see HTML on DOM and Component constructor is never run. – Yanis-git Mar 24 '18 at 17:08
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/167475/discussion-between-yerkon-and-yanis-git). – Yerkon Mar 24 '18 at 17:18
  • @Yerkon, I was badly looking for this solution, Thanks for such a wonderful solution. Is this still works fine. Because i am facing some issue main.7b86a16abd15c99f3b38.bundle.js:1 ERROR Error: Can't resolve all parameters for n: (?). at H (vendor.89a9af6ae45024cb1c82.bundle.js:1) at t._getDependenciesMetadata (vendor.89a9af6ae45024cb1c82.bundle.js:1) at t._getTypeMetadata (vendor.89a9af6ae45024cb1c82.bundle.js:1) – Francis Stalin May 14 '18 at 06:04
  • i confirm this still work fine but is actually this solution is hack who can handle nested dependency.. In order to counter this limit, i recommand you to use [Angular Custom Web Element](https://medium.com/@tomsu/building-web-components-with-angular-elements-746cd2a38d5b) for your JIT part – Yanis-git Nov 15 '18 at 05:18
  • @Yerkon, I tried your first example and it worked when I build my angular 11 app without using aot. when I tried `ng build --prod`, in the browser, the on-fly content did not appear, there was an error message in browser `Angular JIT compilation failed: '@angular/compiler' not loaded!`. Anything I need to do to use with aot compile? – Redplane Dec 19 '21 at 09:06
0

I've faced with same issue recently and gave up trying to solve for now.

Afaik this can not be done (for now) because angular removes metadata on production build.

We should try again after issue 8896 closed.

Halil İbrahim Karaalp
  • 1,290
  • 1
  • 13
  • 24
  • Yes i have noticed same thing, is not angular who remove but ng-cli. I have also try this trick : https://gist.github.com/p3x-robot/e12ed76acb7033638b4179149546bb73, Output is not as espected. JitCompiler will work in AoT and all code looks like working. But Angular throw many error on console log about missed meta. – Yanis-git Mar 22 '18 at 09:31
  • Unfortunately it seems better not to go hacky ways and wait for an official solution – Halil İbrahim Karaalp Mar 22 '18 at 10:32