Revert "fix(router): do not finish bootstrap until all the routes are resolved (#14608)"
This reverts commit 2a191cae2dff9b2616d4e7a4d47ae2ffb2cdf15d.
This commit is contained in:
		
							parent
							
								
									2a191cae2d
								
							
						
					
					
						commit
						670f2eca00
					
				| @ -6,7 +6,6 @@ | ||||
|  * found in the LICENSE file at https://angular.io/license
 | ||||
|  */ | ||||
| 
 | ||||
| import {InjectionToken} from '@angular/core'; | ||||
| /** | ||||
|  * This class should not be used directly by an application developer. Instead, use | ||||
|  * {@link Location}. | ||||
| @ -51,12 +50,6 @@ export abstract class PlatformLocation { | ||||
|   abstract back(): void; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * @whatItDoes indicates when a location is initialized | ||||
|  * @experimental | ||||
|  */ | ||||
| export const LOCATION_INITIALIZED = new InjectionToken<Promise<any>>('Location Initialized'); | ||||
| 
 | ||||
| /** | ||||
|  * A serializable version of the event from onPopState or onHashChange | ||||
|  * | ||||
|  | ||||
| @ -6,7 +6,7 @@ | ||||
|  * found in the LICENSE file at https://angular.io/license
 | ||||
|  */ | ||||
| 
 | ||||
| import {LOCATION_INITIALIZED, PlatformLocation} from '@angular/common'; | ||||
| import {PlatformLocation} from '@angular/common'; | ||||
| import {APP_INITIALIZER, InjectionToken, NgZone} from '@angular/core'; | ||||
| 
 | ||||
| import {WebWorkerPlatformLocation} from './platform_location'; | ||||
| @ -25,18 +25,9 @@ export const WORKER_APP_LOCATION_PROVIDERS = [ | ||||
|     multi: true, | ||||
|     deps: [PlatformLocation, NgZone], | ||||
|   }, | ||||
|   { | ||||
|     provide: LOCATION_INITIALIZED, | ||||
|     useFactory: locationInitialized, | ||||
|     deps: [PlatformLocation], | ||||
|   }, | ||||
| ]; | ||||
| 
 | ||||
| export function locationInitialized(platformLocation: WebWorkerPlatformLocation) { | ||||
|   return platformLocation.initialized; | ||||
| } | ||||
| 
 | ||||
| export function appInitFnFactory(platformLocation: WebWorkerPlatformLocation, zone: NgZone): () => | ||||
| function appInitFnFactory(platformLocation: WebWorkerPlatformLocation, zone: NgZone): () => | ||||
|     Promise<boolean> { | ||||
|   return () => zone.runGuarded(() => platformLocation.init()); | ||||
| } | ||||
|  | ||||
| @ -22,8 +22,6 @@ export class WebWorkerPlatformLocation extends PlatformLocation { | ||||
|   private _hashChangeListeners: Array<Function> = []; | ||||
|   private _location: LocationType = null; | ||||
|   private _channelSource: EventEmitter<Object>; | ||||
|   public initialized: Promise<any>; | ||||
|   private initializedResolve: () => void; | ||||
| 
 | ||||
|   constructor( | ||||
|       brokerFactory: ClientMessageBrokerFactory, bus: MessageBus, private _serializer: Serializer) { | ||||
| @ -50,7 +48,6 @@ export class WebWorkerPlatformLocation extends PlatformLocation { | ||||
|         } | ||||
|       } | ||||
|     }); | ||||
|     this.initialized = new Promise(res => this.initializedResolve = res); | ||||
|   } | ||||
| 
 | ||||
|   /** @internal **/ | ||||
| @ -61,7 +58,6 @@ export class WebWorkerPlatformLocation extends PlatformLocation { | ||||
|         .then( | ||||
|             (val: LocationType) => { | ||||
|               this._location = val; | ||||
|               this.initializedResolve(); | ||||
|               return true; | ||||
|             }, | ||||
|             err => { throw new Error(err); }); | ||||
|  | ||||
| @ -180,18 +180,6 @@ type NavigationParams = { | ||||
|   source: NavigationSource, | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * @internal | ||||
|  */ | ||||
| export type RouterHook = (snapshot: RouterStateSnapshot) => Observable<void>; | ||||
| 
 | ||||
| /** | ||||
|  * @internal | ||||
|  */ | ||||
| function defaultRouterHook(snapshot: RouterStateSnapshot): Observable<void> { | ||||
|   return of (null); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Does not detach any subtrees. Reuses routes as long as their route config is the same. | ||||
|  */ | ||||
| @ -233,23 +221,11 @@ export class Router { | ||||
|    */ | ||||
|   errorHandler: ErrorHandler = defaultErrorHandler; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|   /** | ||||
|    * Indicates if at least one navigation happened. | ||||
|    */ | ||||
|   navigated: boolean = false; | ||||
| 
 | ||||
|   /** | ||||
|    * Used by RouterModule. This allows us to | ||||
|    * pause the navigation either before preactivation or after it. | ||||
|    * @internal | ||||
|    */ | ||||
|   hooks: {beforePreactivation: RouterHook, afterPreactivation: RouterHook} = { | ||||
|     beforePreactivation: defaultRouterHook, | ||||
|     afterPreactivation: defaultRouterHook | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * Extracts and merges URLs. Used for AngularJS to Angular migrations. | ||||
|    */ | ||||
| @ -626,25 +602,18 @@ export class Router { | ||||
|         urlAndSnapshot$ = of ({appliedUrl: url, snapshot: precreatedState}); | ||||
|       } | ||||
| 
 | ||||
|       const beforePreactivationDone$ = mergeMap.call( | ||||
|           urlAndSnapshot$, (p: {appliedUrl: string, snapshot: RouterStateSnapshot}) => { | ||||
|             return map.call(this.hooks.beforePreactivation(p.snapshot), () => p); | ||||
|           }); | ||||
| 
 | ||||
|       // run preactivation: guards and data resolvers
 | ||||
|       let preActivation: PreActivation; | ||||
|       const preactivationTraverse$ = map.call( | ||||
|           beforePreactivationDone$, | ||||
|           ({appliedUrl, snapshot}: {appliedUrl: string, snapshot: RouterStateSnapshot}) => { | ||||
|       const preactivationTraverse$ = map.call(urlAndSnapshot$, ({appliedUrl, snapshot}: any) => { | ||||
|         preActivation = | ||||
|             new PreActivation(snapshot, this.currentRouterState.snapshot, this.injector); | ||||
|         preActivation.traverse(this.outletMap); | ||||
|         return {appliedUrl, snapshot}; | ||||
|       }); | ||||
| 
 | ||||
|       const preactivationCheckGuards$ = mergeMap.call( | ||||
|           preactivationTraverse$, | ||||
|           ({appliedUrl, snapshot}: {appliedUrl: string, snapshot: RouterStateSnapshot}) => { | ||||
|       const preactivationCheckGuards = | ||||
|           mergeMap.call(preactivationTraverse$, ({appliedUrl, snapshot}: any) => { | ||||
|             if (this.navigationId !== id) return of (false); | ||||
| 
 | ||||
|             return map.call(preActivation.checkGuards(), (shouldActivate: boolean) => { | ||||
| @ -652,7 +621,7 @@ export class Router { | ||||
|             }); | ||||
|           }); | ||||
| 
 | ||||
|       const preactivationResolveData$ = mergeMap.call(preactivationCheckGuards$, (p: any) => { | ||||
|       const preactivationResolveData$ = mergeMap.call(preactivationCheckGuards, (p: any) => { | ||||
|         if (this.navigationId !== id) return of (false); | ||||
| 
 | ||||
|         if (p.shouldActivate) { | ||||
| @ -662,15 +631,11 @@ export class Router { | ||||
|         } | ||||
|       }); | ||||
| 
 | ||||
|       const preactivationDone$ = mergeMap.call(preactivationResolveData$, (p: any) => { | ||||
|         return map.call(this.hooks.afterPreactivation(p.snapshot), () => p); | ||||
|       }); | ||||
| 
 | ||||
| 
 | ||||
|       // create router state
 | ||||
|       // this operation has side effects => route state is being affected
 | ||||
|       const routerState$ = | ||||
|           map.call(preactivationDone$, ({appliedUrl, snapshot, shouldActivate}: any) => { | ||||
|           map.call(preactivationResolveData$, ({appliedUrl, snapshot, shouldActivate}: any) => { | ||||
|             if (shouldActivate) { | ||||
|               const state = | ||||
|                   createRouterState(this.routeReuseStrategy, snapshot, this.currentRouterState); | ||||
|  | ||||
| @ -58,13 +58,10 @@ export class RouterConfigLoader { | ||||
|     if (typeof loadChildren === 'string') { | ||||
|       return fromPromise(this.loader.load(loadChildren)); | ||||
|     } else { | ||||
|       return mergeMap.call(wrapIntoObservable(loadChildren()), (t: any) => { | ||||
|         if (t instanceof NgModuleFactory) { | ||||
|           return of (t); | ||||
|         } else { | ||||
|           return fromPromise(this.compiler.compileModuleAsync(t)); | ||||
|         } | ||||
|       }); | ||||
|       const offlineMode = this.compiler instanceof Compiler; | ||||
|       return mergeMap.call( | ||||
|           wrapIntoObservable(loadChildren()), | ||||
|           (t: any) => offlineMode ? of (<any>t) : fromPromise(this.compiler.compileModuleAsync(t))); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -6,10 +6,8 @@ | ||||
|  * found in the LICENSE file at https://angular.io/license
 | ||||
|  */ | ||||
| 
 | ||||
| import {APP_BASE_HREF, HashLocationStrategy, LOCATION_INITIALIZED, Location, LocationStrategy, PathLocationStrategy, PlatformLocation} from '@angular/common'; | ||||
| import {ANALYZE_FOR_ENTRY_COMPONENTS, APP_BOOTSTRAP_LISTENER, APP_INITIALIZER, ApplicationRef, Compiler, ComponentRef, Inject, Injectable, InjectionToken, Injector, ModuleWithProviders, NgModule, NgModuleFactoryLoader, NgProbeToken, Optional, Provider, SkipSelf, SystemJsNgModuleLoader} from '@angular/core'; | ||||
| import {Subject} from 'rxjs/Subject'; | ||||
| import {of } from 'rxjs/observable/of'; | ||||
| import {APP_BASE_HREF, HashLocationStrategy, Location, LocationStrategy, PathLocationStrategy, PlatformLocation} from '@angular/common'; | ||||
| import {ANALYZE_FOR_ENTRY_COMPONENTS, APP_BOOTSTRAP_LISTENER, ApplicationRef, Compiler, ComponentRef, Inject, InjectionToken, Injector, ModuleWithProviders, NgModule, NgModuleFactoryLoader, NgProbeToken, Optional, Provider, SkipSelf, SystemJsNgModuleLoader} from '@angular/core'; | ||||
| 
 | ||||
| import {Route, Routes} from './config'; | ||||
| import {RouterLink, RouterLinkWithHref} from './directives/router_link'; | ||||
| @ -21,7 +19,7 @@ import {ErrorHandler, Router} from './router'; | ||||
| import {ROUTES} from './router_config_loader'; | ||||
| import {RouterOutletMap} from './router_outlet_map'; | ||||
| import {NoPreloading, PreloadAllModules, PreloadingStrategy, RouterPreloader} from './router_preloader'; | ||||
| import {ActivatedRoute, RouterStateSnapshot} from './router_state'; | ||||
| import {ActivatedRoute} from './router_state'; | ||||
| import {UrlHandlingStrategy} from './url_handling_strategy'; | ||||
| import {DefaultUrlSerializer, UrlSerializer} from './url_tree'; | ||||
| import {flatten} from './utils/collection'; | ||||
| @ -280,77 +278,22 @@ export function rootRoute(router: Router): ActivatedRoute { | ||||
|   return router.routerState.root; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * To initialize the router properly we need to do in two steps: | ||||
|  * | ||||
|  * We need to start the navigation in a APP_INITIALIZER to block the bootstrap if | ||||
|  * a resolver or a guards executes asynchronously. Second, we need to actually run | ||||
|  * activation in a BOOTSTRAP_LISTENER. We utilize the afterPreactivation | ||||
|  * hook provided by the router to do that. | ||||
|  * | ||||
|  * The router navigation starts, reaches the point when preactivation is done, and then | ||||
|  * pauses. It waits for the hook to be resolved. We then resolve it only in a bootstrap listener. | ||||
|  */ | ||||
| @Injectable() | ||||
| export class RouterInitializer { | ||||
|   private initNavigation: boolean; | ||||
|   private resultOfPreactivationDone = new Subject<void>(); | ||||
| export function initialRouterNavigation( | ||||
|     router: Router, ref: ApplicationRef, preloader: RouterPreloader, opts: ExtraOptions) { | ||||
|   return (bootstrappedComponentRef: ComponentRef<any>) => { | ||||
| 
 | ||||
|   constructor(private injector: Injector) {} | ||||
| 
 | ||||
|   appInitializer(): Promise<any> { | ||||
|     const p: Promise<any> = this.injector.get(LOCATION_INITIALIZED, Promise.resolve(null)); | ||||
|     return p.then(() => { | ||||
|       let resolve: Function = null; | ||||
|       const res = new Promise(r => resolve = r); | ||||
|       const router = this.injector.get(Router); | ||||
|       const opts = this.injector.get(ROUTER_CONFIGURATION); | ||||
| 
 | ||||
|       if (opts.initialNavigation === false) { | ||||
|         router.setUpLocationChangeListener(); | ||||
|       } else { | ||||
|         router.hooks.afterPreactivation = () => { | ||||
|           // only the initial navigation should be delayed
 | ||||
|           if (!this.initNavigation) { | ||||
|             this.initNavigation = true; | ||||
|             resolve(true); | ||||
|             return this.resultOfPreactivationDone; | ||||
| 
 | ||||
|             // subsequent navigations should not be delayed
 | ||||
|           } else { | ||||
|             return of (null); | ||||
|           } | ||||
|         }; | ||||
|         router.initialNavigation(); | ||||
|       } | ||||
| 
 | ||||
|       return res; | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   bootstrapListener(bootstrappedComponentRef: ComponentRef<any>): void { | ||||
|     const ref = this.injector.get(ApplicationRef); | ||||
|     if (bootstrappedComponentRef !== ref.components[0]) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     const preloader = this.injector.get(RouterPreloader); | ||||
|     preloader.setUpPreloading(); | ||||
| 
 | ||||
|     const router = this.injector.get(Router); | ||||
|     router.resetRootComponentType(ref.componentTypes[0]); | ||||
| 
 | ||||
|     this.resultOfPreactivationDone.next(null); | ||||
|     this.resultOfPreactivationDone.complete(); | ||||
|     preloader.setUpPreloading(); | ||||
|     if (opts.initialNavigation === false) { | ||||
|       router.setUpLocationChangeListener(); | ||||
|     } else { | ||||
|       router.initialNavigation(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export function getAppInitializer(r: RouterInitializer) { | ||||
|   return r.appInitializer.bind(r); | ||||
| } | ||||
| 
 | ||||
| export function getBootstrapListener(r: RouterInitializer) { | ||||
|   return r.bootstrapListener.bind(r); | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
| @ -363,14 +306,11 @@ export const ROUTER_INITIALIZER = | ||||
| 
 | ||||
| export function provideRouterInitializer() { | ||||
|   return [ | ||||
|     RouterInitializer, | ||||
|     { | ||||
|       provide: APP_INITIALIZER, | ||||
|       multi: true, | ||||
|       useFactory: getAppInitializer, | ||||
|       deps: [RouterInitializer] | ||||
|       provide: ROUTER_INITIALIZER, | ||||
|       useFactory: initialRouterNavigation, | ||||
|       deps: [Router, ApplicationRef, RouterPreloader, ROUTER_CONFIGURATION] | ||||
|     }, | ||||
|     {provide: ROUTER_INITIALIZER, useFactory: getBootstrapListener, deps: [RouterInitializer]}, | ||||
|     {provide: APP_BOOTSTRAP_LISTENER, multi: true, useExisting: ROUTER_INITIALIZER}, | ||||
|   ]; | ||||
| } | ||||
|  | ||||
| @ -1,128 +0,0 @@ | ||||
| /** | ||||
|  * @license | ||||
|  * Copyright Google Inc. All Rights Reserved. | ||||
|  * | ||||
|  * Use of this source code is governed by an MIT-style license that can be | ||||
|  * found in the LICENSE file at https://angular.io/license
 | ||||
|  */ | ||||
| 
 | ||||
| import {APP_BASE_HREF} from '@angular/common'; | ||||
| import {ApplicationRef, CUSTOM_ELEMENTS_SCHEMA, Component, NgModule, destroyPlatform} from '@angular/core'; | ||||
| import {BrowserModule, DOCUMENT} from '@angular/platform-browser'; | ||||
| import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; | ||||
| import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; | ||||
| import {Resolve, Router, RouterModule} from '@angular/router'; | ||||
| 
 | ||||
| describe('bootstrap', () => { | ||||
| 
 | ||||
|   @Component({selector: 'test-app', template: 'root <router-outlet></router-outlet>'}) | ||||
|   class RootCmp { | ||||
|   } | ||||
| 
 | ||||
|   @Component({selector: 'test-app2', template: 'root <router-outlet></router-outlet>'}) | ||||
|   class SecondRootCmp { | ||||
|   } | ||||
| 
 | ||||
|   @Component({selector: 'test', template: 'test'}) | ||||
|   class TestCmp { | ||||
|   } | ||||
| 
 | ||||
|   class TestResolver implements Resolve<any> { | ||||
|     resolve() { | ||||
|       let resolve: any = null; | ||||
|       const res = new Promise(r => resolve = r); | ||||
|       setTimeout(() => resolve('test-data'), 0); | ||||
|       return res; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   let testProviders: any[] = null; | ||||
| 
 | ||||
|   beforeEach(() => { | ||||
|     destroyPlatform(); | ||||
|     const fakeDoc = getDOM().createHtmlDocument(); | ||||
|     const el1 = getDOM().createElement('test-app', fakeDoc); | ||||
|     const el2 = getDOM().createElement('test-app2', fakeDoc); | ||||
|     getDOM().appendChild(fakeDoc.body, el1); | ||||
|     getDOM().appendChild(fakeDoc.body, el2); | ||||
|     testProviders = | ||||
|         [{provide: DOCUMENT, useValue: fakeDoc}, {provide: APP_BASE_HREF, useValue: ''}]; | ||||
|   }); | ||||
| 
 | ||||
|   it('should wait for resolvers to complete', (done) => { | ||||
|     @NgModule({ | ||||
|       imports: [ | ||||
|         BrowserModule, | ||||
|         RouterModule.forRoot( | ||||
|             [{path: '**', component: TestCmp, resolve: {test: TestResolver}}], {useHash: true}) | ||||
|       ], | ||||
|       declarations: [SecondRootCmp, RootCmp, TestCmp], | ||||
|       bootstrap: [RootCmp], | ||||
|       providers: [...testProviders, TestResolver], | ||||
|       schemas: [CUSTOM_ELEMENTS_SCHEMA] | ||||
|     }) | ||||
|     class TestModule { | ||||
|     } | ||||
| 
 | ||||
|     platformBrowserDynamic([]).bootstrapModule(TestModule).then(res => { | ||||
|       const router = res.injector.get(Router); | ||||
|       const data = router.routerState.snapshot.root.firstChild.data; | ||||
|       expect(data['test']).toEqual('test-data'); | ||||
|       done(); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   it('should not init router navigation listeners if a non root component is bootstrapped', | ||||
|      (done) => { | ||||
|        @NgModule({ | ||||
|          imports: [BrowserModule, RouterModule.forRoot([], {useHash: true})], | ||||
|          declarations: [SecondRootCmp, RootCmp], | ||||
|          entryComponents: [SecondRootCmp], | ||||
|          bootstrap: [RootCmp], | ||||
|          providers: testProviders, | ||||
|          schemas: [CUSTOM_ELEMENTS_SCHEMA] | ||||
|        }) | ||||
|        class TestModule { | ||||
|        } | ||||
| 
 | ||||
|        platformBrowserDynamic([]).bootstrapModule(TestModule).then(res => { | ||||
|          const router = res.injector.get(Router); | ||||
|          spyOn(router, 'resetRootComponentType').and.callThrough(); | ||||
| 
 | ||||
|          const appRef: ApplicationRef = res.injector.get(ApplicationRef); | ||||
|          appRef.bootstrap(SecondRootCmp); | ||||
| 
 | ||||
|          expect(router.resetRootComponentType).not.toHaveBeenCalled(); | ||||
| 
 | ||||
|          done(); | ||||
|        }); | ||||
|      }); | ||||
| 
 | ||||
|   it('should reinit router navigation listeners if a previously bootstrapped root component is destroyed', | ||||
|      (done) => { | ||||
|        @NgModule({ | ||||
|          imports: [BrowserModule, RouterModule.forRoot([], {useHash: true})], | ||||
|          declarations: [SecondRootCmp, RootCmp], | ||||
|          entryComponents: [SecondRootCmp], | ||||
|          bootstrap: [RootCmp], | ||||
|          providers: testProviders, | ||||
|          schemas: [CUSTOM_ELEMENTS_SCHEMA] | ||||
|        }) | ||||
|        class TestModule { | ||||
|        } | ||||
| 
 | ||||
|        platformBrowserDynamic([]).bootstrapModule(TestModule).then(res => { | ||||
|          const router = res.injector.get(Router); | ||||
|          spyOn(router, 'resetRootComponentType').and.callThrough(); | ||||
| 
 | ||||
|          const appRef: ApplicationRef = res.injector.get(ApplicationRef); | ||||
|          appRef.components[0].onDestroy(() => { | ||||
|            appRef.bootstrap(SecondRootCmp); | ||||
|            expect(router.resetRootComponentType).toHaveBeenCalled(); | ||||
|            done(); | ||||
|          }); | ||||
| 
 | ||||
|          appRef.components[0].destroy(); | ||||
|        }); | ||||
|      }); | ||||
| }); | ||||
							
								
								
									
										91
									
								
								modules/@angular/router/test/router_module.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								modules/@angular/router/test/router_module.spec.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,91 @@ | ||||
| /** | ||||
|  * @license | ||||
|  * Copyright Google Inc. All Rights Reserved. | ||||
|  * | ||||
|  * Use of this source code is governed by an MIT-style license that can be | ||||
|  * found in the LICENSE file at https://angular.io/license
 | ||||
|  */ | ||||
| import {APP_BASE_HREF} from '@angular/common'; | ||||
| import {ApplicationRef, Component, NgModule} from '@angular/core'; | ||||
| import {TestBed, inject} from '@angular/core/testing'; | ||||
| import {DOCUMENT} from '@angular/platform-browser'; | ||||
| import {Router, RouterModule, Routes} from '@angular/router'; | ||||
| 
 | ||||
| 
 | ||||
| @Component({selector: 'app-root', template: ''}) | ||||
| export class AppRootComponent { | ||||
| } | ||||
| 
 | ||||
| @Component({selector: 'bootstrappable-component', template: ''}) | ||||
| export class BootstrappableComponent { | ||||
| } | ||||
| 
 | ||||
| export const appRoutes: Routes = [{path: '**', redirectTo: ''}]; | ||||
| 
 | ||||
| 
 | ||||
| @NgModule({ | ||||
|   imports: [RouterModule.forRoot(appRoutes)], | ||||
|   declarations: [AppRootComponent, BootstrappableComponent], | ||||
|   entryComponents: [AppRootComponent, BootstrappableComponent], | ||||
|   providers: [{provide: APP_BASE_HREF, useValue: '/'}] | ||||
| }) | ||||
| export class RouterInitTestModule { | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| describe('RouterModule', () => { | ||||
|   describe('RouterInitializer', () => { | ||||
| 
 | ||||
|     beforeEach(() => { TestBed.configureTestingModule({imports: [RouterInitTestModule]}); }); | ||||
| 
 | ||||
|     beforeEach(inject([DOCUMENT], function(doc: HTMLDocument) { | ||||
| 
 | ||||
|       const elRootApp = doc.createElement('app-root'); | ||||
|       doc.body.appendChild(elRootApp); | ||||
| 
 | ||||
|       const elBootComp = doc.createElement('bootstrappable-component'); | ||||
|       doc.body.appendChild(elBootComp); | ||||
| 
 | ||||
|     })); | ||||
|     it('should not init router navigation listeners if a non root component is bootstrapped', | ||||
|        () => { | ||||
| 
 | ||||
|          const appRef: ApplicationRef = TestBed.get(ApplicationRef); | ||||
|          const r: Router = TestBed.get(Router); | ||||
| 
 | ||||
|          const spy = spyOn(r, 'resetRootComponentType').and.callThrough(); | ||||
| 
 | ||||
|          appRef.bootstrap(AppRootComponent); | ||||
|          expect(r.resetRootComponentType).toHaveBeenCalled(); | ||||
| 
 | ||||
|          spy.calls.reset(); | ||||
| 
 | ||||
|          appRef.bootstrap(BootstrappableComponent); | ||||
|          expect(r.resetRootComponentType).not.toHaveBeenCalled(); | ||||
|        }); | ||||
|     it('should reinit router navigation listeners if a previously bootstrapped root component is destroyed', | ||||
|        (done) => { | ||||
| 
 | ||||
|          const appRef: ApplicationRef = TestBed.get(ApplicationRef); | ||||
|          const r: Router = TestBed.get(Router); | ||||
| 
 | ||||
|          const spy = spyOn(r, 'resetRootComponentType').and.callThrough(); | ||||
| 
 | ||||
|          const compRef = appRef.bootstrap(AppRootComponent); | ||||
|          expect(r.resetRootComponentType).toHaveBeenCalled(); | ||||
| 
 | ||||
|          spy.calls.reset(); | ||||
| 
 | ||||
|          compRef.onDestroy(() => { | ||||
| 
 | ||||
|            appRef.bootstrap(BootstrappableComponent); | ||||
|            expect(r.resetRootComponentType).toHaveBeenCalled(); | ||||
| 
 | ||||
|            done(); | ||||
|          }); | ||||
| 
 | ||||
|          compRef.destroy(); | ||||
|        }); | ||||
|   }); | ||||
| 
 | ||||
| }); | ||||
| @ -6,8 +6,8 @@ | ||||
|  * found in the LICENSE file at https://angular.io/license
 | ||||
|  */ | ||||
| 
 | ||||
| import {APP_BOOTSTRAP_LISTENER, ComponentRef, InjectionToken} from '@angular/core'; | ||||
| import {Router} from '@angular/router'; | ||||
| import {APP_BOOTSTRAP_LISTENER, ApplicationRef, ComponentRef, InjectionToken} from '@angular/core'; | ||||
| import {ExtraOptions, ROUTER_CONFIGURATION, ROUTER_INITIALIZER, Router, RouterPreloader} from '@angular/router'; | ||||
| import {UpgradeModule} from '@angular/upgrade/static'; | ||||
| 
 | ||||
| 
 | ||||
| @ -36,17 +36,38 @@ import {UpgradeModule} from '@angular/upgrade/static'; | ||||
|  * @experimental | ||||
|  */ | ||||
| export const RouterUpgradeInitializer = { | ||||
|   provide: APP_BOOTSTRAP_LISTENER, | ||||
|   multi: true, | ||||
|   useFactory: locationSyncBootstrapListener, | ||||
|   deps: [UpgradeModule] | ||||
|   provide: ROUTER_INITIALIZER, | ||||
|   useFactory: initialRouterNavigation, | ||||
|   deps: [UpgradeModule, ApplicationRef, RouterPreloader, ROUTER_CONFIGURATION] | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * @internal | ||||
|  */ | ||||
| export function locationSyncBootstrapListener(ngUpgrade: UpgradeModule) { | ||||
|   return () => { setUpLocationSync(ngUpgrade); }; | ||||
| export function initialRouterNavigation( | ||||
|     ngUpgrade: UpgradeModule, ref: ApplicationRef, preloader: RouterPreloader, | ||||
|     opts: ExtraOptions): Function { | ||||
|   return () => { | ||||
|     if (!ngUpgrade.$injector) { | ||||
|       throw new Error(` | ||||
|         RouterUpgradeInitializer can be used only after UpgradeModule.bootstrap has been called. | ||||
|         Remove RouterUpgradeInitializer and call setUpLocationSync after UpgradeModule.bootstrap. | ||||
|       `);
 | ||||
|     } | ||||
| 
 | ||||
|     const router = ngUpgrade.injector.get(Router); | ||||
|     const ref = ngUpgrade.injector.get(ApplicationRef); | ||||
| 
 | ||||
|     (router as any).resetRootComponentType(ref.componentTypes[0]); | ||||
|     preloader.setUpPreloading(); | ||||
|     if (opts.initialNavigation === false) { | ||||
|       router.setUpLocationChangeListener(); | ||||
|     } else { | ||||
|       router.initialNavigation(); | ||||
|     } | ||||
| 
 | ||||
|     setUpLocationSync(ngUpgrade); | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
| @ -57,14 +78,7 @@ export function locationSyncBootstrapListener(ngUpgrade: UpgradeModule) { | ||||
|  * | ||||
|  * @experimental | ||||
|  */ | ||||
| export function setUpLocationSync(ngUpgrade: UpgradeModule) { | ||||
|   if (!ngUpgrade.$injector) { | ||||
|     throw new Error(` | ||||
|         RouterUpgradeInitializer can be used only after UpgradeModule.bootstrap has been called. | ||||
|         Remove RouterUpgradeInitializer and call setUpLocationSync after UpgradeModule.bootstrap. | ||||
|       `);
 | ||||
|   } | ||||
| 
 | ||||
| export function setUpLocationSync(ngUpgrade: UpgradeModule): void { | ||||
|   const router: Router = ngUpgrade.injector.get(Router); | ||||
|   const url = document.createElement('a'); | ||||
| 
 | ||||
|  | ||||
| @ -25,9 +25,7 @@ export const ROUTES = [ | ||||
| 
 | ||||
| @NgModule({ | ||||
|   imports: [WorkerAppModule, RouterModule.forRoot(ROUTES, {useHash: true})], | ||||
|   providers: [ | ||||
|     WORKER_APP_LOCATION_PROVIDERS, | ||||
|   ], | ||||
|   providers: [WORKER_APP_LOCATION_PROVIDERS], | ||||
|   bootstrap: [App], | ||||
|   declarations: [App, Start, Contact, About] | ||||
| }) | ||||
|  | ||||
							
								
								
									
										3
									
								
								tools/public_api_guard/common/index.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								tools/public_api_guard/common/index.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -86,9 +86,6 @@ export declare class Location { | ||||
|     static stripTrailingSlash(url: string): string; | ||||
| } | ||||
| 
 | ||||
| /** @experimental */ | ||||
| export declare const LOCATION_INITIALIZED: InjectionToken<Promise<any>>; | ||||
| 
 | ||||
| /** @experimental */ | ||||
| export interface LocationChangeEvent { | ||||
|     type: string; | ||||
|  | ||||
| @ -93,10 +93,6 @@ export declare const WORKER_APP_LOCATION_PROVIDERS: ({ | ||||
|     useFactory: (platformLocation: WebWorkerPlatformLocation, zone: NgZone) => () => Promise<boolean>; | ||||
|     multi: boolean; | ||||
|     deps: (typeof NgZone | typeof PlatformLocation)[]; | ||||
| } | { | ||||
|     provide: InjectionToken<Promise<any>>; | ||||
|     useFactory: (platformLocation: WebWorkerPlatformLocation) => Promise<any>; | ||||
|     deps: typeof PlatformLocation[]; | ||||
| })[]; | ||||
| 
 | ||||
| /** @experimental */ | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user