angular AOT and JIT on same project

2019-04-06 07:15发布

问题:

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

回答1:

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



回答2:

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.