Solvedangular Creating ROUTES with custom factory function ends up with "Cannot read property 'loadChildren' of undefined" error

I'm submitting a...

[ ] Regression (a behavior that used to work and stopped working in a new release)
[x] Bug report  <!-- Please search GitHub for a similar issue or PR before submitting -->
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Current behavior

When angular compiler finds a provider that defines ROUTES, but which doesn't have a useValue property, (because it has f.e. useFactory property instead) it fails with the following error:

ERROR in TypeError: Cannot read property 'loadChildren' of undefined
    at _collectLoadChildren (/Users/jtomaszewski/src/recruitee/rails/admin_app/node_modules/@angular/compiler/bundles/compiler.umd.js:29034:20)
    at listLazyRoutes (/Users/jtomaszewski/src/recruitee/rails/admin_app/node_modules/@angular/compiler/bundles/compiler.umd.js:29009:49)
    at AotCompiler.listLazyRoutes (/Users/jtomaszewski/src/recruitee/rails/admin_app/node_modules/@angular/compiler/bundles/compiler.umd.js:31150:51)
    at AngularCompilerProgram.listLazyRoutes (/Users/jtomaszewski/src/recruitee/rails/admin_app/node_modules/@angular/compiler-cli/src/transformers/program.js:156:30)
    at AngularCompilerPlugin._listLazyRoutesFromProgram (/Users/jtomaszewski/src/recruitee/rails/admin_app/node_modules/@ngtools/webpack/src/angular_compiler_plugin.js:304:38)
    at Promise.resolve.then.then (/Users/jtomaszewski/src/recruitee/rails/admin_app/node_modules/@ngtools/webpack/src/angular_compiler_plugin.js:583:50)
    at process._tickCallback (internal/process/next_tick.js:109:7)
webpack: Failed to compile.

Expected behavior

Defining a provider for ROUTES with other providers like f.e. FactoryProvider should just work.

Minimal reproduction of the problem with instructions

  1. Write your module like this:
import { NgModule, NgModuleFactory, Compiler } from '@angular/core';
import { Routes, RouterModule, ROUTES } from '@angular/router';

export function MainRoutingLazyRoutesFactory(compiler: Compiler): Routes {
  return [
    {
      path: 'dashboard',
      loadChildren: () => {
        return <any>System.import('../features/dashboard/dashboard.module')
          .then(({ DashboardFeatureModule }) => DashboardFeatureModule)
          .then(ngModule => {
            if (ngModule instanceof NgModuleFactory) {
              return ngModule;
            } else {
              return compiler.compileModuleAsync(ngModule);
            }
          });
      },
    },
  ];
}

@NgModule({
  imports: [RouterModule],
  exports: [RouterModule],
  providers: [
    {
      provide: ROUTES,
      multi: true,
      deps: [Compiler],
      useFactory: MainRoutingLazyRoutesFactory,
    },
  ],
})
export class MainRoutingModule {}
  1. Try to compile it, in either JiT or AoT mode.

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

It lets you to easily implement lazy loaded modules in your routes with your custom way.

Environment

node 6.11.5
npm 5.6.0
angular 5.2.8
@ngtools/webpack 1.10.2
21 Answers

✔️Accepted Answer

For me it consistently fails on run of ng serve but every next recompilation (on file save) is successful.

Other Answers:

I am not quite sure if that is related to this exact issue, but this might just be an issue with anonymous functions in route objects, i found a solution on stackoverflow that worked for me, having a similar setup: https://stackoverflow.com/a/48205304/3880255.

Changing the anonymous functions to named functions fixed the error, i sadly have no insight on why tho, might be worth a try.

const routes: Routes = [
    ....
    {
        path: ':id',
        component: RegionCrudComponent,
        canDeactivate: [LeaveGuard],
        data: {
            staticRouteBreadcrumbName: 'Region',
            dynamicRouteComponent: RegionCrudComponent,
            dynamicRouteComponentModule: () => RegionModule
        }
    }
];

export const routing: ModuleWithProviders = RouterModule.forChild(routes);
export function getRegionModule() {
    return RegionModule;
}

const routes: Routes = [
    ...
    {
        path: ':id',
        component: RegionCrudComponent,
        canDeactivate: [LeaveGuard],
        data: {
            staticRouteBreadcrumbName: 'Region',
            dynamicRouteComponent: RegionCrudComponent,
            dynamicRouteComponentModule: getRegionModule
        }
    }
];

export const routing: ModuleWithProviders = RouterModule.forChild(routes);

I experienced the same behavior as @DominikDitoIvosevic . I was able to solve the issue, i.e. make it not fail on ng serve by exporting my factoring function. Below is my simplified setup (No lazy loading involved).

my-routing.module.ts:

const routes: Routes = [
  createRoute('my-path', MyComponent)
];

@NgModule({
  imports: [
    RouterModule.forChild(routes)
  ]
})
export class MyRoutingModule { }

// Removing export throws "ERROR in Cannot read property 'loadChildren' of undefined"
export function createRoute<T>(path: string, component: Type<T>): Route {
  return {
    path: path,
    component: component
  };
}

I've followed through the stack trace and think I've found what causes the bug:

I think it's because of this code in the angular/compiler's source:

function listLazyRoutes(moduleMeta, reflector) {
    var /** @type {?} */ allLazyRoutes = [];
    for (var _i = 0, _a = moduleMeta.transitiveModule.providers; _i < _a.length; _i++) {
        var _b = _a[_i], provider = _b.provider, module = _b.module;
        if (tokenReference(provider.token) === reflector.ROUTES) {
            var /** @type {?} */ loadChildren = _collectLoadChildren(provider.useValue);
            for (var _c = 0, loadChildren_1 = loadChildren; _c < loadChildren_1.length; _c++) {
                var route = loadChildren_1[_c];
                allLazyRoutes.push(parseLazyRoute(route, reflector, module.reference));
            }
        }
    }
    return allLazyRoutes;
}

According to the stacktrace, it fails on _collectLoadChildren(provider.useValue);.

Obviously, this provider has no useValue. IMHO we should skip parsing it, if it doesn't have such a property.

P.S. We've submitted this issue on angular-cli first ( angular/angular-cli#9913 ) but they redirected us here.

P.S.2. In the meantime, we've managed to build a workaround to make it compile anyway:

export const MainRoutingLazyRoutesFactoryProvider: FactoryProvider = <any>{
  provide: ROUTES,
  multi: true,
  deps: [Compiler],
  useFactory: MainRoutingLazyRoutesFactory,
  useValue: [],
};
delete (<any>rtMainRoutingLazyRoutesFactoryProvider).useValue;

Same problem, but the fix for me was:
{
provide: ROUTES,
multi: true,
deps: [ModuleLoaderService, Compiler],
useValue: {}, // Can't compile when not defined
useFactory: LazyRoutesFactory
}

"@angular/common": "^7.0.0",
"@angular/compiler": "^7.0.0",
"@angular/core": "^7.0.0",

More Issues: