angular: [4.0.0] bug(compiler-cli): Cannot use JitCompiler with AoT

I’m submitting a … (check one with “x”)

[x] bug report => search github for a similar issue or PR before submitting
[ ] feature request
[ ] support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Current behavior Since 4.0.0, JitCompiler cannot be used with AoT compilation. It’s exported from @angular/compiler but its compiled import path is @angular/compiler/src/jit/compiler

app.module.ts

import { NgModule, Compiler } from '@angular/core';
import { JitCompiler } from '@angular/compiler';

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

@NgModule({
    declarations: [AppComponent],
    bootstrap: [AppComponent],
    providers: [
        {
            provide: Compiler, useExisting: JitCompiler,
        },
    ]
})
export class AppModule {
}

app.module.ngfactory.ts(summary)

import * as import0 from '@angular/core';
import * as import1 from './app.module';
import * as import2 from './app.component.ngfactory';
import * as import3 from '@angular/compiler/src/jit/compiler';
class AppModuleInjector extends import0.ɵNgModuleInjector<import1.AppModule> {
  _AppModule_0:import1.AppModule;
  __Compiler_1:any;
  constructor(parent:import0.Injector) {
    super(parent,[import2.AppComponentNgFactory],[import2.AppComponentNgFactory]);
  }
  get _Compiler_1():any {
    if ((this.__Compiler_1 == null)) { (this.__Compiler_1 = this.parent.get(import3.JitCompiler)); }
    return this.__Compiler_1;
  }

@angular/compiler/src/jit/compiler is not existing JS file (because of 4.0.0 FESM). webpack bundling crashes by this problem.

Expected behavior

Resolve JitCompiler from @angular/compiler

Minimal reproduction of the problem with instructions

https://github.com/laco0416/ngrepro-0001

  1. git clone
  2. npm install
  3. npm run ngc
  4. see the generated files

What is the motivation / use case for changing the behavior?

Please tell us about your environment: OSX

  • Angular version: 4.0.0
  • Language: TypeScript 2.2

  • Node (for AoT issues): node --version = 6.9.5

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 23
  • Comments: 99 (11 by maintainers)

Most upvoted comments

@IgorMinar @tbosch Any news about this issue? We are really in trouble because we have an application with a huge dynamic part that use JIT Compiler but have a startup delay (15 secs.). We need AOT to speedup the initial loading but we need also JIT Compiler!!! Please don’t leave us alone… in the dark!

@maxbellec : I can provide you a ZIP that you can try ourself (see attachment). To run:

  • If you want to run it in dev mode (which uses JIT all the time): npm run server:dev (if you clear the ‘output’ folder, you have to re-created the DLLs by running npm run build:dll)
  • If you want to run it in prod mode (which uses AOT): npm run server:prod

I suggest you start with looking at the app.component.html which includes the same component twice: once ‘normally’ and once using the dynamic-ui component which gets a string (here with a component called ‘some-component’, but it could also be just HTML or both) that is then compiled using JIT and inserted into the page.

Just to not get confused: yes, ‘some-component’ is pre-compiled by AOT. But the string passed to the dynamic-ui component needs to be compiled and interpreted by Angular - which is the step where the JIT compiler is needed.

One more thing: I never said it can be done using the angular CLI. We created our own build based on Webpack 2 and I’m not saying that it’s perfect. However, it manages the complexity of switching between JIT and AOT for dev and prod mode. Also note that among others, the entry points are different: main.browser.ts and main.browser.aot.ts.

I’m sure there is much to be improved in the given example, but for sure: it shows how to dynamically create a UI using JIT within an AOT compiled application. Let me know what you think.

angular4-aot-jit.zip

Here’s the workaround using the JitCompilerFactory:

import {JitCompilerFactory} from '@angular/compiler';
...
// Need an exported function to make it work with AOT:
export function createJitCompiler () {
    return new JitCompilerFactory([{useDebug: false, useJit: true}]).createCompiler();
}
...
@NgModule({
    providers: [
       { provide: Compiler, useFactory:  createJitCompiler},
    ],
    ...
})

Just to add to this, you can reproduce it with the CLI aswell. ng new myproj cd myproj add to app.module.ts

  providers: [ { provide: Compiler, useExisting: JitCompiler, }, ],

run ng serve --aot ng serve works fine however and you should get an error similar to Error: Can't resolve '@angular/compiler/src/jit/compiler' in '...\myproj\src\$$_gendir\app' which indicates the same deep import as @laco0416 pointed out in the ngc build

Hi all,

Indeed, JIT and AOT in Angular with the View Engine runtime were independent - it wasn’t really possible to mix and match the two. This was a systemic constraint, not a bug in the implementation.

The good news is that a major design goal for Angular Ivy was to support JIT/AOT interop. In Angular v9+ it’s possible to build applications which are AOT compiled but consume dynamic, JIT-compiled components, or even the other way around if desired. There are still a few hiccups (#37216 for example) but overall the support is there.

@Paladinium YOU ARE THE TOTAL MASTER OF THE UNIVERSE!!!

I DID IT! I AM USING THE FACTORIES, ALL!!! BUT WHEN I NEED JIT, I GOT IT!!!

I AM YOUR SLAVE!!!

@patrikx3 : whether or not it is JIT or AOT at runtime is determined by the selection of the appropriate main.browser.ts vs. main.browser.aot.ts which is done by the build. Check out this documentation an compare those files with the ones provided: https://angular.io/docs/ts/latest/cookbook/aot-compiler.html#!#bootstrap

When we switched to AOT a couple of months ago, we could also observe the massive performance gain.

I hope I could help you - but I’m not going to continue this conversation since a working example has been provided which is using AOT.

Same error for me.

Guys, the CLI is removing the meta information that the JIT compiler needs. If you want this to be changed, I recommend that you upvote this CLI issue: https://github.com/angular/angular-cli/issues/9306

@Jackclarify
Need to be angular cli, added a flag

@alexzuza, Thanks for your posting. But still, I need to add my dynamic components in the shared.module.ts - how would I be able to change this during runtime ? For instance compiling components from an external resource ?

The compiler AOT has enableSummariesForJit What does it mean?

I get the same error No NgModule metadata found for RuntimeComponentModule, do you have a full example? The GenericUiComponent, GenericTypeCreator app and the module? I am thinking just some import or something setting from the module or the app. Please help me out. I get always the same error.

Looks like the component is decorated, but the module is missing something somewhere. I get the same code.

This is the code that creates a factory for creating a component:

@Injectable
export class GenericTypeCreator {
    private _cacheOfFactories: {[templateKey: string]: ComponentFactory<GenericContextData>} = {};

    constructor(protected compiler: Compiler) {} // This is the JIT compiler

    createComponentFactory(template: string): Promise<ComponentFactory<GenericContextData>> {

        let factory = this._cacheOfFactories[template];

        if (factory) {
            console.log('Module and Type are returned from cache');

            return new Promise((resolve) => {
                resolve(factory);
            });
        }

        // unknown template ... let's create a Type for it
        let type   = this.createNewComponent(template);
        let module = this.createComponentModule(type);

        return new Promise((resolve) => {
            this.compiler
                .compileModuleAndAllComponentsAsync(module)
                .then((moduleWithFactories) => {
                    factory = moduleWithFactories.componentFactories.find(component => component.componentType === type);
                    this._cacheOfFactories[template] = factory;
                    resolve(factory);
                })
                .catch(error => {
                    console.log(error);
                });
        });
    }

    private createNewComponent (tmpl:string) {
        @Component({
            selector: 'dynamic-component',
            template: tmpl,
        })
        class CustomDynamicComponent implements GenericContextData {
             // In our case, this component gets a context set. But you can do with that component whatever you want.
      }
      return CustomDynamicComponent;
  }

    private createComponentModule (componentType: any) {
        @NgModule({
            imports: [
                // whatever modules you need as a dependency,
                CommonModule,
                ReactiveFormsModule
            ],
            declarations: [
                componentType
            ]
        })
        class RuntimeComponentModule { }
        // a module for just this Type
        return RuntimeComponentModule;
    }

}

And here’s the caller:

@Component({
    selector: 'generic-ui',
    template: '<div #genericUiContentPlaceHolder></div>',
    providers: [SchemaToUiService, GenericDataService]
})
export class GenericUiComponent implements ... {

    @Input
    templateString: string;

    @ViewChild('genericUiContentPlaceHolder', {read: ViewContainerRef})
    public genericUiComponentTarget: ViewContainerRef;
    protected componentRef: ComponentRef<GenericContextData>;

    constructor(private genericTypeCreator: GenericTypeCreator) { }

   // Note: this component implements a couple of lifecycle methods to trigger the creation of the UI

    private buildUi () {
        if (this.componentRef) {
            this.componentRef.destroy();
        }

        console.log('The generic template is: ' + this.templateString);

        // here we get Factory (just compiled or from cache)
        this.genericTypeCreator
            .createComponentFactory(this.templateString) // <- this is what you pass in to be compiled by JIT
            .then((factory: ComponentFactory<GenericContextData>) => {
                // Target will instantiate and inject component (we'll keep reference to it)
                this.componentRef = this.genericUiComponentTarget.createComponent(factory);
                let component = this.componentRef.instance;
               // Here we pass data to the component mentioned above
                component.context = this.ui.context;
            });
    }
}

And the usage could be:

<generic-ui [templateString]='whatever string you want to be compiled'></generic-ui>

@alexzuza thanks for sharing. This is example of dynamically injecting the already build component. I am looking for the solution for rendering dynamic component which template we will decide on runtime and so we also require runtimecompiler along with AOT. The provided solution is not the example of runtimeCompiler along with AOT.

document.getElementById('someidentifier').innerHTML = html; Equivalently [innerHtml]

<div [innerHTML]="html">

@Alekcei I tried with

export function CustomComponent(annotation: any) {
  return function (target: Function) {
    const metaData = new Component(annotation)
    Component(metaData)(target)
  }
}
export function CustomNgModule(annotation: any) {
  return function (target: Function) {
    const metaData = new NgModule(annotation)
    NgModule(metaData)(target)
  }
}

and now getting Error: Unexpected value 'e' imported by the module 'e'. Please add a @NgModule annotation.