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
 | ||||
|  */ | ||||
| 
 | ||||
| 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 {map, mergeMap} from 'rxjs/operators'; | ||||
| 
 | ||||
| @ -41,8 +41,13 @@ export class RouterConfigLoader { | ||||
| 
 | ||||
|       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( | ||||
|           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 { | ||||
|       } | ||||
| 
 | ||||
|       @NgModule({}) | ||||
|       class EmptyModule { | ||||
|       } | ||||
| 
 | ||||
|       beforeEach(() => { | ||||
|         log.length = 0; | ||||
|         TestBed.configureTestingModule({ | ||||
| @ -5355,6 +5359,19 @@ describe('Integration', () => { | ||||
|            expect(firstConfig).toBeUndefined(); | ||||
|            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', () => { | ||||
|  | ||||
| @ -261,4 +261,35 @@ describe('RouterPreloader', () => { | ||||
|              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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user