Solvedangular Router: AoT compilation fails when using a function with loadChildren

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

In order to enable lazy loading with Webpack 1, a custom NgModuleFactoryLoader must be implemented to lazy load routes with loadChildren. I've implemented an example loader here.

https://github.com/brandonroberts/router-cli-aot/blob/master/src/app/shared/async-ng-module-loader.ts

The custom load function is being used here: https://github.com/brandonroberts/router-cli-aot/blob/master/src/app/app.routing.ts#L12-L25

When running ngc over the provided repo, an error occurs:

Error: Error encountered resolving symbol values statically. Function calls are not supported. Consider replacing the function or lambda with a reference to an exported function (position 23:24 in the original .ts file), resolving symbol crisisCenterRoutes in /router-cli-aot/src/app/app.routing.ts, resolving symbol appRoutes in /router-cli-aot/src/app/app.routing.ts, resolving symbol routing in /router-cli-aot/src/app/app.routing.ts, resolving symbol AppModule in /router-cli-aot/src/app/app.module.ts, resolving symbol AppModule in /router-cli-aot/src/app/app.module.ts

I can comment out the loadChildren line and ngc completes successfully. If I uncomment the line after running ngc and bundle with webpack, the app works as desired with lazy loading.

While #10705 adds support for using a callback function with loadChildren such as loadChildren: () => System.import('lazy.module#LazyModule'), this will cause AoT compilation to fail also with the same error.

Expected/desired behavior

Providing a callback function to loadChildren does not cause AoT compilation to fail when used
with vanilla webpack or some other bundling tool.

Reproduction of the problem
If the current behavior is a bug or you can illustrate your feature request better with an example, please provide the steps to reproduce and if possible a minimal demo of the problem via https://plnkr.co or similar (you can use this template as a starting point: http://plnkr.co/edit/tpl:AvJOMERrnz94ekVua0u5).

What is the expected behavior?

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

Webpack 1/2 use a callback function to provide lazy loading through returning a promise.

Please tell us about your environment:

  • Angular version: 2.0.0-rc.5
  • Browser: [all | Chrome XX | Firefox XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView ]
  • Language: [all | TypeScript X.X | ES6/7 | ES5]
34 Answers

✔️Accepted Answer

Calling functions or calling new is not supported in metadata when using AoT. This includes things like all the examples of above.

The work-around is to introduce an exported function to call such as changing the first example to,

export function loadCrisisCenter() {
  return require('es6-promise!./crisis-center/crisis-center.module')('CrisisCenterModule'));
}

export const crisisCenterRoutes: Routes = [
  {
    path: '',
    redirectTo: '/heroes',
    pathMatch: 'full'
  },
  {
    path: 'crisis-center',
    loadChildren: loadCrisisCenter
  }
];

@Richard87's example can be transformed into:

export function jwtHelperFactory() {
  return new AuthConfig({
    tokenName: "auth_token",
    headerName: 'Authorization',
    headerPrefix: 'Bearer',
    globalHeaders: [{'Content-Type':'application/json'}],
    noJwtError: true,
    noTokenScheme: true
  });
}

@NgModule({
    declarations: [AppComponent],
    imports:      [
        BrowserModule,
        HttpModule,
        NgbModule,
        FormsModule,
        DashboardModule,
        RouterModule.forRoot(routes)
    ],
    bootstrap:    [AppComponent],
    providers:    [
        LoggedInGuard,
        AUTH_PROVIDERS,
        JwtHelper,
        {
            provide: AuthConfig,
            useFactory: jwtHelperFactory
        },
        AuthHttp,
        AuthService,
    ],
})
export class AppModule {}

@lpenet's example can be transformed into:

export function authenticationFactory(backend: XHRBackend, defaultOptions: RequestOptions) {
 return new AuthenticatedHttp(backend, defaultOptions);
}

@NgModule({
    declarations: [AppComponent,DelaisSchedule],
    imports:      [BrowserModule,
                   routing,
                   FormsModule,
                   ReactiveFormsModule,
                   HttpModule,
                   ButtonModule, CalendarModule, DataTableModule, DialogModule, GrowlModule, InputTextModule, TabViewModule, MenuModule],
    providers:    [{provider: AuthenticatedHttp, 
                        useFactory: authenticationFactor,
                        deps: [XHRBackend, RequestOptions]
                    },
                    APP_ROUTER_PROVIDERS,
                    DelaisLimitesService,
                    DroitsAccesService,
                    SyncService],
    bootstrap:    [AppComponent]
})

The reason for this limitation is that the AoT compiler needs to generate the code that calls the factory and there is no way to import a lambda from a module, you can only import an exported symbol.

Other Answers:

Please start another issue as this is the same error arrived at in a different scenario.

It is confusing to have an issue whose title and initial description is about using the router with AoT turn into an issue discussing using the UpgradeAdapter with AoT. If this gets resolved, which issue was resolved?

@samiheikki you cannot call functions in metadata. your code should be statically analizable

re chuckjaz's suggestion I can say that the solution shouldn't be updating the source code to get it to work with AOT, because this defeats the statement made by Angular team in link
One of the benefits of the introduction of NgModules is that your code does not have to change based on whether you’re running in AoT or just in time (JiT) mode

do this:
npm install typescript@next --save
typescript@next===typescript 2.1.0
and then change your gulp file to this:
let tsProject = ts.createProject('tsconfig.json', { typescript: require('typescript')});
and then update/create your tsconfig.json:
{
"compilerOptions": {
"target": "es5",
"module": "es2015",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": false,
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true
},
"files": [
"app/app.module.ts",
"app/main.ts",
"./typings/index.d.ts"
],
"angularCompilerOptions": {
"genDir": "aot",
"skipMetadataEmit": false
}
}
and try running your ngc -p "path to the tsconfig"

More Issues: