Angular-cli: V8.0.0 : loadChildren library module failed : "Error: Runtime compiler is not loaded" on AOT mode

Created on 7 Jun 2019  路  23Comments  路  Source: angular/angular-cli

馃悶 bug report

Affected Package

The issue is caused by package @angular/compiler and/or @angular/core and/or @angular/router

Is this a regression?

Yes, the previous version in which this bug was not present was: 7.2.1

Description

I try to load a project library (named shell) as a route "loadChildren", with 2 diff茅rents option :

  • loadChildren: () => import('shell').then(m => m.ProfileModule)
  • loadChildren: () => ProfileModule

This 2 ways work correctly on dev mode, but failed on AOT builded mode with an error message. And the route is not loaded.

馃敩 Minimal Reproduction

ProfileModule (as an angular library named "shell" on the CLI project) :

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatIconModule } from '@angular/material/icon';
import { MatListModule } from '@angular/material/list';
import { FlexLayoutModule } from '@angular/flex-layout';

import { ProfileComponent } from './profile/profile.component';
import { TranslateModule } from '../translate/translate.module';
import { GhostModule } from '../ghost/ghost.module';
import { DataModule } from '../data/data.module';
import { ProfileRoutingModule } from './profile-routing.module';
import { ProfileCardComponent } from './profile-card/profile-card.component';

@NgModule({
  declarations: [
    ProfileComponent,
    ProfileCardComponent
  ],
  imports: [
    CommonModule,
    MatCardModule,
    MatListModule,
    MatButtonModule,
    MatIconModule,
    TranslateModule,
    FlexLayoutModule,
    GhostModule,
    DataModule,
    ProfileRoutingModule
  ],
  exports: [
    ProfileCardComponent
  ]
})
export class ProfileModule { }

AppRoutingModule (as an angular project from the same CLI project)

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { SecureGuard } from 'shell';

import { HomeComponent } from './home/home.component';

export function loadProfile() {
  return import('shell').then(m => m.ProfileModule);
}

const routes: Routes = [{
  path: '',
  canActivate: [SecureGuard],
  children: [{
    path: '',
    component: HomeComponent
  }, {
    path: 'profile',
    loadChildren: loadProfile // Here the error at runtime, the error message appear when I go to /profile
  }, {
    path: '**',
    component: HomeComponent
  }]
}];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

馃敟 Exception or Error


Uncaught Error: Uncaught (in promise): Error: Runtime compiler is not loaded
Error: Runtime compiler is not loaded
    at e.Jr (main.41ff3398f5c3f189c836.js:1)
    at t.project (main.41ff3398f5c3f189c836.js:1)
    at t._tryNext (main.41ff3398f5c3f189c836.js:1)
    at t._next (main.41ff3398f5c3f189c836.js:1)
    at t.next (main.41ff3398f5c3f189c836.js:1)
    at e._subscribe (main.41ff3398f5c3f189c836.js:1)
    at e._trySubscribe (main.41ff3398f5c3f189c836.js:1)
    at e.subscribe (main.41ff3398f5c3f189c836.js:1)
    at e.call (main.41ff3398f5c3f189c836.js:1)
    at e.subscribe (main.41ff3398f5c3f189c836.js:1)
    at Z (polyfills.06b93379260cc1d8e708.js:1)
    at Z (polyfills.06b93379260cc1d8e708.js:1)
    at polyfills.06b93379260cc1d8e708.js:1
    at e.invokeTask (polyfills.06b93379260cc1d8e708.js:1)
    at Object.onInvokeTask (main.41ff3398f5c3f189c836.js:1)
    at e.invokeTask (polyfills.06b93379260cc1d8e708.js:1)
    at t.runTask (polyfills.06b93379260cc1d8e708.js:1)
    at g (polyfills.06b93379260cc1d8e708.js:1)
    at t.invokeTask [as invoke] (polyfills.06b93379260cc1d8e708.js:1)
    at m (polyfills.06b93379260cc1d8e708.js:1)

馃實 Your Environment

Angular Version:


Angular CLI: 8.0.1
Node: 12.4.0
OS: darwin x64
Angular: 8.0.0
... animations, cdk, common, compiler, compiler-cli, core, forms
... language-service, material, material-moment-adapter
... platform-browser, platform-browser-dynamic, router
... service-worker

Package                            Version
------------------------------------------------------------
@angular-devkit/architect          0.800.1
@angular-devkit/build-angular      0.800.1
@angular-devkit/build-ng-packagr   0.800.1
@angular-devkit/build-optimizer    0.800.1
@angular-devkit/build-webpack      0.800.1
@angular-devkit/core               8.0.1
@angular-devkit/schematics         8.0.1
@angular/cli                       8.0.1
@angular/flex-layout               8.0.0-beta.26
@angular/pwa                       0.800.1
@ngtools/json-schema               1.1.0
@ngtools/webpack                   8.0.1
@schematics/angular                8.0.1
@schematics/update                 0.800.1
ng-packagr                         5.2.0
rxjs                               6.5.2
typescript                         3.4.5
webpack                            4.30.0

Anything else relevant?
I try to use on different CLI projects a Profile module from a CLI library (all on the same CLI project). This is not possible since angular 8.0.0

Most helpful comment

Yes that works ! (it was not working on AOT when it is not proxyfied).
To conclude, the solution to load a library module is :

  • Proxify the library module inside a project modul
  • Use the import notation of the module
  • Don't use an exported function (like AOT service configuration), but directly an arrow function
loadChildren: () => import('./shared/profile-wrapper/profile-wrapper.module')
    .then(m => m.ProfileWrapperModule) 
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import { ProfileModule } from 'shell'; // From Angular project library

@NgModule({
  declarations: [],
  imports: [
    CommonModule,
    ProfileModule
  ]
})
export class ProfileWrapperModule { }

Thanks for support !

All 23 comments

It's a known limitation:

Declaration syntax: It's important to follow the route declaration syntax loadChildren: () => import('...').then(m => m.ModuleName) to allow ngc to discover the lazy-loaded module and the associated NgModule. You can find the complete list of allowed syntax constructs here. These restrictions will be relaxed with the release of Ivy since it'll no longer use NgFactories.

See https://angular.io/guide/deprecations#loadchildren-string-syntax for more information.

If you believe this is worthing implementing please consider open a feature request to Angular CLI.

@trotyl thank you for quick answer,

I already use this kind of declaration :

export function loadProfile() {
  return import('shell').then(m => m.ProfileModule);
}

const routes: Routes = [{
  path: '',
  canActivate: [SecureGuard],
  children: [{
    path: '',
    component: HomeComponent
  }, {
    path: 'profile',
    loadChildren: loadProfile
  }, {
    path: '**',
    component: HomeComponent
  }]
}];

If I declare it like that, I have a build issue with ng factory don't exist (I cannot use the Ivy build system, they are issue with the library build

const routes: Routes = [{
  path: '',
  canActivate: [SecureGuard],
  children: [{
    path: '',
    component: HomeComponent
  }, {
    path: 'profile',
    loadChildren: import('shell').then(m => m.ProfileModule)
  }, {
    path: '**',
    component: HomeComponent
  }]
}];

It is an issue with the CLI ? I have to add a bug on the cli project ?

loadChildren: import('shell').then(m => m.ProfileModule)

This is not lazy loading at all, you need to wrap an arrow function.

Sure, it is a bad copy paste @trotyl

const routes: Routes = [{
  path: '',
  canActivate: [SecureGuard],
  children: [{
    path: '',
    component: HomeComponent
  }, {
    path: 'profile',
    loadChildren: () => import('shell').then(m => m.ProfileModule)
  }, {
    path: '**',
    component: HomeComponent
  }]
}];

If I don't declare it as an exported fonction, the build failed, because there is no ng factory for the module

I create same issue on CLI project : https://github.com/angular/angular-cli/issues/14700

Is shell also a copy-paste issue? You cannot load an NgModule from node_modules, it must belong to your project.

@trotyl, shell is a library from the same project, here the tsconfig file, so it is not a node_module, but an angular library :

{
  "compileOnSave": false,
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "sourceMap": true,
    "declaration": false,
    "module": "esNext",
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "importHelpers": true,
    "target": "es5",
    "typeRoots": [
      "node_modules/@types"
    ],
    "lib": [
      "es2018",
      "dom"
    ],
    "paths": {
      "shell": [
        "dist/shell"
      ],
      "shell/*": [
        "dist/shell/*"
      ]
    }
  }
}

and the piece of angular.json about the shell library :

"shell": {
      "root": "projects/shell",
      "sourceRoot": "projects/shell/src",
      "projectType": "library",
      "prefix": "lib",
      "schematics": {
        "@schematics/angular:component": {
          "styleext": "scss",
          "spec": false
        },
        "@schematics/angular:directive": {
          "spec": false
        },
        "@schematics/angular:modules": {
          "spec": false
        },
        "@schematics/angular:pipe": {
          "spec": false
        },
        "@schematics/angular:service": {
          "spec": false
        },
        "@schematics/angular:class": {
          "spec": false
        }
      },
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-ng-packagr:build",
          "options": {
            "tsConfig": "projects/shell/tsconfig.lib.json",
            "project": "projects/shell/ng-package.json"
          }
        },
        "test": {
          "builder": "@angular-devkit/build-angular:karma",
          "options": {
            "main": "projects/shell/src/test.ts",
            "tsConfig": "projects/shell/tsconfig.spec.json",
            "karmaConfig": "projects/shell/karma.conf.js"
          }
        },
        "lint": {
          "builder": "@angular-devkit/build-angular:tslint",
          "options": {
            "tsConfig": [
              "projects/shell/tsconfig.lib.json",
              "projects/shell/tsconfig.spec.json"
            ],
            "exclude": [
              "**/node_modules/**"
            ]
          }
        }
      }
    }

Sorry for not making it clear, whether it's from node_modules doesn't matter, from your configuration:

"shell": [
   "dist/shell"
],

It's not the same project, but a separately built library. (Where the source code resides doesn't make any difference)

ok @trotyl and @alan-agius4 , I already try it yesterday @alan-agius4 :

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { SecureGuard } from 'shell';

import { HomeComponent } from './home/home.component';
import { ProfileWrapperModule } from './shared/profile-wrapper/profile-wrapper.module';

const routes: Routes = [{
  path: '',
  canActivate: [SecureGuard],
  children: [{
    path: '',
    component: HomeComponent
  }, {
    path: 'profile',
    loadChildren: () => ProfileWrapperModule // For this feature I don't care about lazy loading, I just reuse module for all my portal apps
  }, {
    path: '**',
    component: HomeComponent
  }]
}];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import { ProfileModule } from 'shell';

@NgModule({
  declarations: [],
  imports: [
    CommonModule,
    ProfileModule
  ]
})
export class ProfileWrapperModule { }

And it produce the same error

@alan-agius4 , you can close other issue if you want

For this 2 ways, that works on dev mode, build AOT is ok, but it is broke on runtime when I try to load the module

HI, the loadChildren syntax is your example above is not correct.

It should be:

 loadChildren: () => import('./shared/profile-wrapper/profile-wrapper.module').then(m => m.ProfileWrapperModule) 

@alan-agius4 , I have the same error than the 2 others ways :

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import { ProfileModule } from 'shell';

@NgModule({
  declarations: [],
  imports: [
    CommonModule,
    ProfileModule
  ]
})
export class ProfileWrapperModule { }

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { SecureGuard } from 'shell';

import { HomeComponent } from './home/home.component';

export function loadProfile() {
  return import('./shared/profile-wrapper.module').then(m => m.ProfileWrapperModule);
}

const routes: Routes = [{
  path: '',
  canActivate: [SecureGuard],
  children: [{
    path: '',
    component: HomeComponent
  }, {
    path: 'profile',
    loadChildren: loadProfile
  }, {
    path: '**',
    component: HomeComponent
  }]
}];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Always good on local env, but not on AOT env.
When a click on the menu to go to my lazy loaded route, the lazy script .js is loaded but it seems to be empty :

(window.webpackJsonp=window.webpackJsonp||[]).push([[5],{Lz8n:function(n,o,r){"use strict";r.r(o),r.d(o,"ProfileWrapperModule",function(){return u});var u=function(){return function(){}}()}}]);

And an error appear on the console :

Uncaught Error: Uncaught (in promise): Error: Runtime compiler is not loaded
Error: Runtime compiler is not loaded
    at e.Jr (main.b3a8916c121eaf6af8dc.js:1)
    at t.project (main.b3a8916c121eaf6af8dc.js:1)
    at t._tryNext (main.b3a8916c121eaf6af8dc.js:1)
    at t._next (main.b3a8916c121eaf6af8dc.js:1)
    at t.next (main.b3a8916c121eaf6af8dc.js:1)
    at main.b3a8916c121eaf6af8dc.js:1
    at e.invoke (polyfills.06b93379260cc1d8e708.js:1)
    at Object.onInvoke (main.b3a8916c121eaf6af8dc.js:1)
    at e.invoke (polyfills.06b93379260cc1d8e708.js:1)
    at t.run (polyfills.06b93379260cc1d8e708.js:1)
    at Z (polyfills.06b93379260cc1d8e708.js:1)
    at Z (polyfills.06b93379260cc1d8e708.js:1)
    at polyfills.06b93379260cc1d8e708.js:1
    at e.invokeTask (polyfills.06b93379260cc1d8e708.js:1)
    at Object.onInvokeTask (main.b3a8916c121eaf6af8dc.js:1)
    at e.invokeTask (polyfills.06b93379260cc1d8e708.js:1)
    at t.runTask (polyfills.06b93379260cc1d8e708.js:1)
    at g (polyfills.06b93379260cc1d8e708.js:1)

Can you try to see if it works if you remove the loadProfile and pass an arrow function directly to loadChildren as per my previous example?

Not possible to build in AOT mode if I pass it directly as an arrow function.

@robinComa, the following syntax is supported by the AOT compiled

loadChildren: () => import('./shared/profile-wrapper/profile-wrapper.module')
    .then(m => m.ProfileWrapperModule) 

Check the lazy loading docs here: https://angular.io/guide/lazy-loading-ngmodules

Yes that works ! (it was not working on AOT when it is not proxyfied).
To conclude, the solution to load a library module is :

  • Proxify the library module inside a project modul
  • Use the import notation of the module
  • Don't use an exported function (like AOT service configuration), but directly an arrow function
loadChildren: () => import('./shared/profile-wrapper/profile-wrapper.module')
    .then(m => m.ProfileWrapperModule) 
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import { ProfileModule } from 'shell'; // From Angular project library

@NgModule({
  declarations: [],
  imports: [
    CommonModule,
    ProfileModule
  ]
})
export class ProfileWrapperModule { }

Thanks for support !

@alan-agius4 Is support for other import variants planned? I didn't find any issue stating support for different kinds of imports for lazy children.

It seems lazy children are limited to the application type and do not support libraries, right?
We have our router module in one library, because the setup is always the same and we don't want to duplicate it for every application.

Hi @CSchulz, that is correct, lazy loading routes are not supported in libraries, and at the moment there are no concrete plans yet to support this feature.

There is a greater discussion around this here: https://github.com/angular/angular-cli/issues/6373#issuecomment-453006158

@alan-agius4 Thanks for your fast reply. I have seen that discussion.

To support libraries the libraries needs to provide the factories itself? I have tried to investigate what is missing for supporting it.

I know it's closed but, M I the only one that are is not able to let it work on AOT or PROD ?? proxyfied or not, inside the project or from node_modules AOT (or PROD) compilation always give me "Error: Runtime compiler is not loaded".

Thanks in advance

I am also getting same error when adding Dynamic Component. What is the resolution for this?

Anyone still having this problem, you might be having the same problem as me and I found the answer here:

In summary, I was using backticks rather than single quotes when giving the path to the module I wanted to load.

@BerBevans It works like a charm. Thanks for pointing this out.

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

_This action has been performed automatically by a bot._

Was this page helpful?
0 / 5 - 0 ratings

Related issues

MateenKadwaikar picture MateenKadwaikar  路  3Comments

NCC1701M picture NCC1701M  路  3Comments

jmurphzyo picture jmurphzyo  路  3Comments

donaldallen picture donaldallen  路  3Comments

rajjejosefsson picture rajjejosefsson  路  3Comments