angular: 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

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 13
  • Comments: 20 (4 by maintainers)

Most upvoted comments

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

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
  };
}

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",

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 ( https://github.com/angular/angular-cli/issues/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;

@jasonaden I am getting the same error but with a different case scenario. I don’t know if this is related with angular or webpack cli.

My issue seems to be related with using an object initializer in a route object. Here an example code https://stackblitz.com/edit/angular-nxvknf. I am using a ‘test’ key inside app routes and seems like it is working using stackblitz.

When using webpack to compile, it is not working. It is calling _collectLoadChildren with provider.useValue, that is undefined in this case. If i switch the data property to a string, it works. Another strange thing is that it only happens when it compiles the first time. If i save with live reloading it is compiled successfully.

Do you think that they could be related?

ivy fix the bug.

I am facing same issue but unable to resolve with the given solution. I am using below configuration

  • angular 8.3
  • node 10.15 please help me for this issue

As @DominikDitoIvosevic mentioned, for me it was failing on the first

ng serve

but doing just a simple change in code, anywhere, and saving fixed the issue. Sadly it was further failing on Jenkins.

Solution in my case was ridiculous: It was specifically related to a feature module routing module which was lazy loaded.

This was failing:

let routes: Routes;
routes = [
  {
    path: "my-custom-route",
    component: LayoutComponent,
    children: [
      { path: "", component: MyComponent }
    ],
    data: {
      title: "Some title"
    }
  }
];

This was working:

let routes: Routes = [
  {
    path: "my-custom-route",
    component: LayoutComponent,
    children: [
      { path: "", component: MyComponent }
    ],
    data: {
      title: "Some title"
    }
  }
];

Ridiculous, but if this helps someone…

I’m experiencing the same error even after enabling ivy.

Also, as @DominikDitoIvosevic said, the error shows up consistently only on the first iteration ng serve building