Search code examples
angulartypescriptcompiler-errorsangular-cliaot

Angular 4 AOT Compilation Error while loading a Dynamic Component: Runtime compiler is not loaded


My Angular application has to deal with loading dynamic components. With the JIT ng serve or, ng build compilation, everything works fine. Even with the AOT ng serve --prod or, ng build --prod, I can still make the build successfully. Also, all the lazy loading modules in the application are working as expected. But once I initiate an event to load a dynamic component from landing page, I get following error:

vendor.fbd3ddffb0a9e35bbf55.bundle.js:1 ERROR Error: Uncaught (in promise): 
Error: Runtime compiler is not loaded
Error: Runtime compiler is not loaded
at Z (vendor.fbd3ddffb0a9e35bbf55.bundle.js:1)
at t.compileModuleAndAllComponentsAsync 
(vendor.fbd3ddffb0a9e35bbf55.bundle.js:1)
at dynamic.module.3b05550d9afcedd58855.chunk.js:1
at t.invoke (polyfills.a58272fc2ef255219061.bundle.js:1)
at Object.onInvoke (vendor.fbd3ddffb0a9e35bbf55.bundle.js:1)
at t.invoke (polyfills.a58272fc2ef255219061.bundle.js:1)
at r.run (polyfills.a58272fc2ef255219061.bundle.js:1)
at polyfills.a58272fc2ef255219061.bundle.js:1
at t.invokeTask (polyfills.a58272fc2ef255219061.bundle.js:1)
at Object.onInvokeTask (vendor.fbd3ddffb0a9e35bbf55.bundle.js:1)
at Z (vendor.fbd3ddffb0a9e35bbf55.bundle.js:1)
at t.compileModuleAndAllComponentsAsync 
(vendor.fbd3ddffb0a9e35bbf55.bundle.js:1)
at dynamic.module.3b05550d9afcedd58855.chunk.js:1
at t.invoke (polyfills.a58272fc2ef255219061.bundle.js:1)
at Object.onInvoke (vendor.fbd3ddffb0a9e35bbf55.bundle.js:1)
at t.invoke (polyfills.a58272fc2ef255219061.bundle.js:1)
at r.run (polyfills.a58272fc2ef255219061.bundle.js:1)
at polyfills.a58272fc2ef255219061.bundle.js:1
at t.invokeTask (polyfills.a58272fc2ef255219061.bundle.js:1)
at Object.onInvokeTask (vendor.fbd3ddffb0a9e35bbf55.bundle.js:1)
at c (polyfills.a58272fc2ef255219061.bundle.js:1)
at polyfills.a58272fc2ef255219061.bundle.js:1
at t.invokeTask (polyfills.a58272fc2ef255219061.bundle.js:1)
at Object.onInvokeTask (vendor.fbd3ddffb0a9e35bbf55.bundle.js:1)
at t.invokeTask (polyfills.a58272fc2ef255219061.bundle.js:1)
at r.runTask (polyfills.a58272fc2ef255219061.bundle.js:1)
at o (polyfills.a58272fc2ef255219061.bundle.js:1)
at <anonymous>`

I got rid of above error by applying injector in the dynamic component like this:

export class DynamicComponent {
private injector: Injector;
private compiler: Compiler;
constructor(injector: Injector) {
    this.injector = ReflectiveInjector.resolveAndCreate(COMPILER_PROVIDERS,
        injector);
    this.compiler = this.injector.get(Compiler);
}
}

Once the above error gone away, I now getting the following error:

vendor.fbd3ddffb0a9e35bbf55.bundle.js:1 ERROR Error: Uncaught (in promise): 
Error: No NgModule metadata found for 'l'.
Error: No NgModule metadata found for 'l'.
at t.FDXN.t.resolve (dynamic.module.c493eec4648091b2c9b3.chunk.js:1)
at t.FDXN.t.getNgModuleMetadata (dynamic.module.c493eec4648091b2c9b3.chunk.js:1)
at t.FDXN.t._loadModules (dynamic.module.c493eec4648091b2c9b3.chunk.js:1)
at t.FDXN.t._compileModuleAndAllComponents (dynamic.module.c493eec4648091b2c9b3.chunk.js:1)
at t.FDXN.t.compileModuleAndAllComponentsAsync (dynamic.module.c493eec4648091b2c9b3.chunk.js:1)
at dynamic.module.c493eec4648091b2c9b3.chunk.js:1
at t.invoke (polyfills.a58272fc2ef255219061.bundle.js:1)
at Object.onInvoke (vendor.fbd3ddffb0a9e35bbf55.bundle.js:1)
at t.invoke (polyfills.a58272fc2ef255219061.bundle.js:1)
at r.run (polyfills.a58272fc2ef255219061.bundle.js:1)
at t.FDXN.t.resolve (dynamic.module.c493eec4648091b2c9b3.chunk.js:1)
at t.FDXN.t.getNgModuleMetadata (dynamic.module.c493eec4648091b2c9b3.chunk.js:1)
at t.FDXN.t._loadModules (dynamic.module.c493eec4648091b2c9b3.chunk.js:1)
at t.FDXN.t._compileModuleAndAllComponents (dynamic.module.c493eec4648091b2c9b3.chunk.js:1)
at t.FDXN.t.compileModuleAndAllComponentsAsync (dynamic.module.c493eec4648091b2c9b3.chunk.js:1)
at dynamic.module.c493eec4648091b2c9b3.chunk.js:1
at t.invoke (polyfills.a58272fc2ef255219061.bundle.js:1)
at Object.onInvoke (vendor.fbd3ddffb0a9e35bbf55.bundle.js:1)
at t.invoke (polyfills.a58272fc2ef255219061.bundle.js:1)
at r.run (polyfills.a58272fc2ef255219061.bundle.js:1)
at c (polyfills.a58272fc2ef255219061.bundle.js:1)
at polyfills.a58272fc2ef255219061.bundle.js:1
at t.invokeTask (polyfills.a58272fc2ef255219061.bundle.js:1)
at Object.onInvokeTask (vendor.fbd3ddffb0a9e35bbf55.bundle.js:1)
at t.invokeTask (polyfills.a58272fc2ef255219061.bundle.js:1)
at r.runTask (polyfills.a58272fc2ef255219061.bundle.js:1)
at o (polyfills.a58272fc2ef255219061.bundle.js:1)
at <anonymous>

I found lots of posts related to this issue in the internet including in stack overflow, but none of them happen to work for me.

My dynamic component looks like this:

import { Component, Input, Output, ViewContainerRef, Compiler, ComponentFactory, ComponentFactoryResolver, ModuleWithComponentFactories, ComponentRef, ReflectiveInjector, SystemJsNgModuleLoader, OnInit, OnDestroy, ViewChild, Injector } from '@angular/core';
import { Http } from '@angular/http';
import { Observable, Subscription } from 'rxjs/Rx';
import { JitCompiler } from '@angular/compiler';
import { COMPILER_PROVIDERS } from '@angular/compiler';
import * as _ from 'lodash';
import { Page } from './interfaces/page';
import { PageService } from './services/page.service';
import { ActivePageService } from './services/active-page.service';

export class ModuleNode { modulePath: string; componentName: string; }
@Component({
selector: 'app-dynamic-viewer',
host: { 'class': 'template-wrapper dynamicviewer' },
templateUrl: './dynamic.component.html',
styleUrls: ['./dynamic.component.less'],
providers: [PageService, ActivePageService]
})
export class DynamicComponent implements OnInit, OnDestroy {
module: ModuleNode;
componentRef: ComponentRef<any>;
@ViewChild('pageContainer', { read: ViewContainerRef }) public pageHost;
public activePagesData: string[];
public pageData: Page[];
public currentPage: Page;
private injector: Injector;
private compiler: Compiler;

constructor(
    injector: Injector,
    private activePageService: activePageService,
    private pageService: pageService,
    private viewContainerRef: ViewContainerRef,
    private componentFactoryResolver: ComponentFactoryResolver,
    private systemJsNgModuleLoader: SystemJsNgModuleLoader
) {
    this.injector = ReflectiveInjector.resolveAndCreate(COMPILER_PROVIDERS, 
injector);
    this.compiler = this.injector.get(Compiler);
}

ngOnInit() {
    this.getActivePageData();
}

openWebApp(currentModule: any) {
    this.destoryComponent();
    this.systemJsNgModuleLoader.load(currentModule.modulePath)  
        .then((moduleFactory) => {
            this.compiler.compileModuleAndAllComponentsAsync<any>(moduleFactory.moduleType)
                .then((factory: ModuleWithComponentFactories<any>) => {
                    return factory.componentFactories.find(x => x.componentType.name === currentModule.componentName);
                })
                .then(componentFactory => {
                    const moduleRef = moduleFactory.create(this.pageHost.parentInjector);
                    this.componentRef = this.pageHost.createComponent(componentFactory, 0, moduleRef.injector);
                });
        });
}

destoryComponent() {
    if (this.componentRef) {
        this.componentRef.destroy();
    }
}

ngOnDestroy() {
    this.destoryComponent();
}

getPageData() {
    this.pageService.getPageData().subscribe(res => {
        for (const page of res) {
            page.active = (this.activePagesData.indexOf(page.name) !== -1)
        }
        this.pageData = res;

    });
}


getActivePageData() {
    this.activePageService.getActivePageData().subscribe(res => {
        this.activePagesData = res;
        this.getPageData();
    });
}

selectPage(page: Page) {
    if (page.active) {
        this.currentPage = page;
        if (this.componentRef) {
            this.componentRef.destroy();
        }
        this.module = new ModuleNode();
        this.module.modulePath = this.currentPage.modulePath;
        this.module.componentName = this.currentPage.componentName;
        this.openWebApp(this.module);
    }
}
}

Application environment:

@angular/cli: 1.3.0-beta.1
node: 6.9.4
os: win32 x64
@angular version: 4.3.6

Is there any workaround? Any input will be appreciated.


Solution

  • I have also gone through the same problem. This is how I made it. Let's say, UserComponent needs to be loaded dynamically with AOT compilation from your landing page.

    user.module.ts

    @NgModule({
      ...
      entryComponents: [UserComponent]
    })
    export class UserModule {
      static entry = UserComponent
    }
    

    Your basic landing page can be like:

    dynamic.component.htlm

    <div>
        <ng-template #pageContainer></ng-template>
    </div>    
    <div *ngFor="let page of pageData">
        <div (click)="selectPage(page)">
            <div>{{page.title}}</div>
        </div>
    </div>
    

    When you select a page (i.e. try to load component dynamically) from your landing page, provide absolute path of respective module. So, the value of the currentPage.modulePathfor the user page might be something like app/modules/pages/user/user.module#UserModule. Finally, you can load your entry component like this:

    openWebApp(path: string) {
            this.systemJsNgModuleLoader.load(path)
                .then((moduleFactory: NgModuleFactory<any>) => {
                    const entryComponent = (<any>moduleFactory.moduleType).entry;
                    const moduleRef = moduleFactory.create(this.injector);    
                    const compFactory = moduleRef.componentFactoryResolver.resolveComponentFactory(entryComponent);
                    this.componentRef = this.pageHost.createComponent(compFactory);
                });
        }
    

    So, your dynamic component looks like this:

    dynamic.component.ts

    import { Component, Input, Output,   NgModuleFactory, ViewContainerRef, Compiler, ComponentFactory, ComponentFactoryResolver, ModuleWithComponentFactories, ComponentRef, ReflectiveInjector, SystemJsNgModuleLoader, OnInit, OnDestroy, ViewChild, Injector } from '@angular/core';
    import { Http } from '@angular/http';
    import { Observable, Subscription } from 'rxjs/Rx';    
    import { COMPILER_PROVIDERS } from '@angular/compiler';
    import * as _ from 'lodash';    
    import { Page } from './interfaces/page';
    import { PageService } from './services/page.service';
    import { ActivePageService } from './services/active-page.service';
    
    @Component({
        selector: 'app-dynamic-viewer',
        host: { 'class': 'template-wrapper dynamicviewer' },
        templateUrl: './dynamic.component.html',
        styleUrls: ['./dynamic.component.less'],
        providers: [PageService, ActivePageService]
    })
    export class DynamicComponent implements OnInit, OnDestroy {
        componentRef: ComponentRef<any>;
        @ViewChild('pageContainer', { read: ViewContainerRef }) public pageHost;
        public activePagesData: string[];
        public pageData: Page[];
        public currentPage: Page;
        private injector: Injector;
        private compiler: Compiler;
    
        constructor(
            injector: Injector,
            private activePageService: ActivePageService,
            private pageService: PageService,
            private viewContainerRef: ViewContainerRef,
            private componentFactoryResolver: ComponentFactoryResolver,
            private systemJsNgModuleLoader: SystemJsNgModuleLoader
        ) {
            this.injector = ReflectiveInjector.resolveAndCreate(COMPILER_PROVIDERS, injector);
            this.compiler = this.injector.get(Compiler);
        }
    
        ngOnInit() {
            this.getActivePageData();
        }
    
        selectPage(page: Page) {
            if (page.active) {
                this.currentPage = page;
                if (this.componentRef) {
                    this.componentRef.destroy();
                }    
                this.openWebApp(this.currentPage.modulePath);
            }
        }
    
        openWebApp(path: string) {
            this.destoryComponent();
            this.systemJsNgModuleLoader.load(path)
                .then((moduleFactory: NgModuleFactory<any>) => {
                    const entryComponent = (<any>moduleFactory.moduleType).entry;
                    const moduleRef = moduleFactory.create(this.injector);    
                    const compFactory = moduleRef.componentFactoryResolver.resolveComponentFactory(entryComponent);
                    this.componentRef = this.pageHost.createComponent(compFactory);
                });
        }
    
        destoryComponent() {
            if (this.componentRef) {
                this.componentRef.destroy();
            }
        }
    
        ngOnDestroy() {
            this.destoryComponent();
        }
    
        getPageData() {
            this.pageService.getPageData().subscribe(res => {
                for (const page of res) {
                    page.active = (this.activePagesData.indexOf(page.name) !== -1)
                }    
                this.pageData = res;
            });
        }
    
    
        getActivePageData() {
            this.activePageService.getActivePageData().subscribe(res => {
                this.activePagesData = res;
                this.getPageData();
            });
        }
    }