fix(router): lazy loaded modules without RouterModule.forChild() won't cause an infinite loop (#36605)
When loading a module that doesn't provide `RouterModule.forChild()` preloader will get stuck in an infinite loop and throw `ERROR Error: Maximum call stack size exceeded.` The issue is that child module's `Injector` will look to its parent `Injector` when it doesn't find any `ROUTES` so it will return routes for it's parent module instead. This will load the child again that returns its parent's routes and so on. Closes #29164 PR Close #36605
This commit is contained in:
parent
96690ed3a4
commit
b37a9eba1a
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Compiler, InjectionToken, Injector, NgModuleFactory, NgModuleFactoryLoader} from '@angular/core';
|
import {Compiler, InjectFlags, InjectionToken, Injector, NgModuleFactory, NgModuleFactoryLoader} from '@angular/core';
|
||||||
import {from, Observable, of} from 'rxjs';
|
import {from, Observable, of} from 'rxjs';
|
||||||
import {map, mergeMap} from 'rxjs/operators';
|
import {map, mergeMap} from 'rxjs/operators';
|
||||||
|
|
||||||
|
@ -41,8 +41,13 @@ export class RouterConfigLoader {
|
||||||
|
|
||||||
const module = factory.create(parentInjector);
|
const module = factory.create(parentInjector);
|
||||||
|
|
||||||
|
// When loading a module that doesn't provide `RouterModule.forChild()` preloader will get
|
||||||
|
// stuck in an infinite loop. The child module's Injector will look to its parent `Injector`
|
||||||
|
// when it doesn't find any ROUTES so it will return routes for it's parent module instead.
|
||||||
return new LoadedRouterConfig(
|
return new LoadedRouterConfig(
|
||||||
flatten(module.injector.get(ROUTES)).map(standardizeConfig), module);
|
flatten(module.injector.get(ROUTES, undefined, InjectFlags.Self | InjectFlags.Optional))
|
||||||
|
.map(standardizeConfig),
|
||||||
|
module);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5291,6 +5291,10 @@ describe('Integration', () => {
|
||||||
class LoadedModule1 {
|
class LoadedModule1 {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NgModule({})
|
||||||
|
class EmptyModule {
|
||||||
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
log.length = 0;
|
log.length = 0;
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
|
@ -5355,6 +5359,19 @@ describe('Integration', () => {
|
||||||
expect(firstConfig).toBeUndefined();
|
expect(firstConfig).toBeUndefined();
|
||||||
expect(log.length).toBe(0);
|
expect(log.length).toBe(0);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it('should allow navigation to modules with no routes', fakeAsync(() => {
|
||||||
|
(TestBed.inject(NgModuleFactoryLoader) as SpyNgModuleFactoryLoader).stubbedModules = {
|
||||||
|
empty: EmptyModule,
|
||||||
|
};
|
||||||
|
const router = TestBed.inject(Router);
|
||||||
|
const fixture = createRoot(router, RootCmp);
|
||||||
|
|
||||||
|
router.resetConfig([{path: 'lazy', loadChildren: 'empty'}]);
|
||||||
|
|
||||||
|
router.navigateByUrl('/lazy');
|
||||||
|
advance(fixture);
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('custom url handling strategies', () => {
|
describe('custom url handling strategies', () => {
|
||||||
|
|
|
@ -261,4 +261,35 @@ describe('RouterPreloader', () => {
|
||||||
expect(c[0]._loadedConfig!.routes[0].component).toBe(configs[0].component);
|
expect(c[0]._loadedConfig!.routes[0].component).toBe(configs[0].component);
|
||||||
})));
|
})));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe(
|
||||||
|
'should work with lazy loaded modules that don\'t provide RouterModule.forChild()', () => {
|
||||||
|
@NgModule({
|
||||||
|
declarations: [LazyLoadedCmp],
|
||||||
|
imports: [RouterModule.forChild([{path: 'LoadedModule1', component: LazyLoadedCmp}])]
|
||||||
|
})
|
||||||
|
class LoadedModule {
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({})
|
||||||
|
class EmptyModule {
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [RouterTestingModule.withRoutes(
|
||||||
|
[{path: 'lazyEmptyModule', loadChildren: 'expected2'}])],
|
||||||
|
providers: [{provide: PreloadingStrategy, useExisting: PreloadAllModules}]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work',
|
||||||
|
fakeAsync(inject(
|
||||||
|
[NgModuleFactoryLoader, RouterPreloader],
|
||||||
|
(loader: SpyNgModuleFactoryLoader, preloader: RouterPreloader) => {
|
||||||
|
loader.stubbedModules = {expected2: EmptyModule};
|
||||||
|
|
||||||
|
preloader.preload().subscribe();
|
||||||
|
})));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue