revert: fix(router): do not finish bootstrap until all the routes are resolved (#14327)
This reverts commit 541de26f7e
because it introduced a regression.
Closes #14681, #14588
This commit is contained in:

committed by
Igor Minar

parent
b658fa9ea0
commit
de36f8a3b9
@ -6,7 +6,6 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {OpaqueToken} from '@angular/core';
|
|
||||||
/**
|
/**
|
||||||
* This class should not be used directly by an application developer. Instead, use
|
* This class should not be used directly by an application developer. Instead, use
|
||||||
* {@link Location}.
|
* {@link Location}.
|
||||||
@ -51,12 +50,6 @@ export abstract class PlatformLocation {
|
|||||||
abstract back(): void;
|
abstract back(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @whatItDoes indicates when a location is initialized
|
|
||||||
* @experimental
|
|
||||||
*/
|
|
||||||
export const LOCATION_INITIALIZED = new OpaqueToken('Location Initialized');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A serializable version of the event from onPopState or onHashChange
|
* A serializable version of the event from onPopState or onHashChange
|
||||||
*
|
*
|
||||||
|
@ -6,13 +6,12 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* 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, NgZone, OpaqueToken} from '@angular/core';
|
import {APP_INITIALIZER, NgZone} from '@angular/core';
|
||||||
|
|
||||||
import {WebWorkerPlatformLocation} from './platform_location';
|
import {WebWorkerPlatformLocation} from './platform_location';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Those providers should be added when the router is used in a worker context in addition to the
|
* Those providers should be added when the router is used in a worker context in addition to the
|
||||||
* {@link ROUTER_PROVIDERS} and after them.
|
* {@link ROUTER_PROVIDERS} and after them.
|
||||||
@ -24,15 +23,10 @@ export const WORKER_APP_LOCATION_PROVIDERS = [
|
|||||||
useFactory: appInitFnFactory,
|
useFactory: appInitFnFactory,
|
||||||
multi: true,
|
multi: true,
|
||||||
deps: [PlatformLocation, NgZone]
|
deps: [PlatformLocation, NgZone]
|
||||||
},
|
}
|
||||||
{provide: LOCATION_INITIALIZED, useFactory: locationInitialized, deps: [PlatformLocation]}
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export function locationInitialized(platformLocation: WebWorkerPlatformLocation) {
|
function appInitFnFactory(platformLocation: WebWorkerPlatformLocation, zone: NgZone): () =>
|
||||||
return platformLocation.initialized;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function appInitFnFactory(platformLocation: WebWorkerPlatformLocation, zone: NgZone): () =>
|
|
||||||
Promise<boolean> {
|
Promise<boolean> {
|
||||||
return () => zone.runGuarded(() => platformLocation.init());
|
return () => zone.runGuarded(() => platformLocation.init());
|
||||||
}
|
}
|
||||||
|
@ -25,8 +25,6 @@ export class WebWorkerPlatformLocation extends PlatformLocation {
|
|||||||
private _hashChangeListeners: Array<Function> = [];
|
private _hashChangeListeners: Array<Function> = [];
|
||||||
private _location: LocationType = null;
|
private _location: LocationType = null;
|
||||||
private _channelSource: EventEmitter<Object>;
|
private _channelSource: EventEmitter<Object>;
|
||||||
public initialized: Promise<any>;
|
|
||||||
private initializedResolve: () => void;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
brokerFactory: ClientMessageBrokerFactory, bus: MessageBus, private _serializer: Serializer) {
|
brokerFactory: ClientMessageBrokerFactory, bus: MessageBus, private _serializer: Serializer) {
|
||||||
@ -54,7 +52,6 @@ export class WebWorkerPlatformLocation extends PlatformLocation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.initialized = new Promise(res => this.initializedResolve = res);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @internal **/
|
/** @internal **/
|
||||||
@ -66,7 +63,6 @@ export class WebWorkerPlatformLocation extends PlatformLocation {
|
|||||||
(val: LocationType):
|
(val: LocationType):
|
||||||
boolean => {
|
boolean => {
|
||||||
this._location = val;
|
this._location = val;
|
||||||
this.initializedResolve();
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
(err): boolean => { throw new Error(err); });
|
(err): boolean => { throw new Error(err); });
|
||||||
|
@ -278,18 +278,6 @@ type NavigationParams = {
|
|||||||
source: NavigationSource,
|
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.
|
* Does not detach any subtrees. Reuses routes as long as their route config is the same.
|
||||||
@ -332,23 +320,11 @@ export class Router {
|
|||||||
*/
|
*/
|
||||||
errorHandler: ErrorHandler = defaultErrorHandler;
|
errorHandler: ErrorHandler = defaultErrorHandler;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if at least one navigation happened.
|
* Indicates if at least one navigation happened.
|
||||||
*/
|
*/
|
||||||
navigated: boolean = false;
|
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 Angular 1 to Angular 2 migrations.
|
* Extracts and merges URLs. Used for Angular 1 to Angular 2 migrations.
|
||||||
*/
|
*/
|
||||||
@ -705,25 +681,18 @@ export class Router {
|
|||||||
urlAndSnapshot$ = of ({appliedUrl: url, snapshot: precreatedState});
|
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
|
// run preactivation: guards and data resolvers
|
||||||
let preActivation: PreActivation;
|
let preActivation: PreActivation;
|
||||||
const preactivationTraverse$ = map.call(
|
const preactivationTraverse$ = map.call(urlAndSnapshot$, ({appliedUrl, snapshot}: any) => {
|
||||||
beforePreactivationDone$,
|
preActivation =
|
||||||
({appliedUrl, snapshot}: {appliedUrl: string, snapshot: RouterStateSnapshot}) => {
|
new PreActivation(snapshot, this.currentRouterState.snapshot, this.injector);
|
||||||
preActivation =
|
preActivation.traverse(this.outletMap);
|
||||||
new PreActivation(snapshot, this.currentRouterState.snapshot, this.injector);
|
return {appliedUrl, snapshot};
|
||||||
preActivation.traverse(this.outletMap);
|
});
|
||||||
return {appliedUrl, snapshot};
|
|
||||||
});
|
|
||||||
|
|
||||||
const preactivationCheckGuards$ = mergeMap.call(
|
const preactivationCheckGuards =
|
||||||
preactivationTraverse$,
|
mergeMap.call(preactivationTraverse$, ({appliedUrl, snapshot}: any) => {
|
||||||
({appliedUrl, snapshot}: {appliedUrl: string, snapshot: RouterStateSnapshot}) => {
|
|
||||||
if (this.navigationId !== id) return of (false);
|
if (this.navigationId !== id) return of (false);
|
||||||
|
|
||||||
return map.call(preActivation.checkGuards(), (shouldActivate: boolean) => {
|
return map.call(preActivation.checkGuards(), (shouldActivate: boolean) => {
|
||||||
@ -731,7 +700,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 (this.navigationId !== id) return of (false);
|
||||||
|
|
||||||
if (p.shouldActivate) {
|
if (p.shouldActivate) {
|
||||||
@ -741,15 +710,11 @@ export class Router {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const preactivationDone$ = mergeMap.call(preactivationResolveData$, (p: any) => {
|
|
||||||
return map.call(this.hooks.afterPreactivation(p.snapshot), () => p);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// create router state
|
// create router state
|
||||||
// this operation has side effects => route state is being affected
|
// this operation has side effects => route state is being affected
|
||||||
const routerState$ =
|
const routerState$ =
|
||||||
map.call(preactivationDone$, ({appliedUrl, snapshot, shouldActivate}: any) => {
|
map.call(preactivationResolveData$, ({appliedUrl, snapshot, shouldActivate}: any) => {
|
||||||
if (shouldActivate) {
|
if (shouldActivate) {
|
||||||
const state =
|
const state =
|
||||||
createRouterState(this.routeReuseStrategy, snapshot, this.currentRouterState);
|
createRouterState(this.routeReuseStrategy, snapshot, this.currentRouterState);
|
||||||
|
@ -6,10 +6,8 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* 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 {APP_BASE_HREF, HashLocationStrategy, Location, LocationStrategy, PathLocationStrategy, PlatformLocation} from '@angular/common';
|
||||||
import {ANALYZE_FOR_ENTRY_COMPONENTS, APP_BOOTSTRAP_LISTENER, APP_INITIALIZER, ApplicationRef, Compiler, ComponentRef, Inject, Injectable, Injector, ModuleWithProviders, NgModule, NgModuleFactoryLoader, NgProbeToken, OpaqueToken, Optional, Provider, SkipSelf, SystemJsNgModuleLoader} from '@angular/core';
|
import {ANALYZE_FOR_ENTRY_COMPONENTS, APP_BOOTSTRAP_LISTENER, ApplicationRef, Compiler, ComponentRef, Inject, Injector, ModuleWithProviders, NgModule, NgModuleFactoryLoader, NgProbeToken, OpaqueToken, Optional, Provider, SkipSelf, SystemJsNgModuleLoader} from '@angular/core';
|
||||||
import {Subject} from 'rxjs/Subject';
|
|
||||||
import {of } from 'rxjs/observable/of';
|
|
||||||
|
|
||||||
import {Route, Routes} from './config';
|
import {Route, Routes} from './config';
|
||||||
import {RouterLink, RouterLinkWithHref} from './directives/router_link';
|
import {RouterLink, RouterLinkWithHref} from './directives/router_link';
|
||||||
@ -21,7 +19,7 @@ import {ErrorHandler, Router} from './router';
|
|||||||
import {ROUTES} from './router_config_loader';
|
import {ROUTES} from './router_config_loader';
|
||||||
import {RouterOutletMap} from './router_outlet_map';
|
import {RouterOutletMap} from './router_outlet_map';
|
||||||
import {NoPreloading, PreloadAllModules, PreloadingStrategy, RouterPreloader} from './router_preloader';
|
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 {UrlHandlingStrategy} from './url_handling_strategy';
|
||||||
import {DefaultUrlSerializer, UrlSerializer} from './url_tree';
|
import {DefaultUrlSerializer, UrlSerializer} from './url_tree';
|
||||||
import {flatten} from './utils/collection';
|
import {flatten} from './utils/collection';
|
||||||
@ -112,7 +110,7 @@ export function routerNgProbeToken() {
|
|||||||
* In addition, we often want to split applications into multiple bundles and load them on demand.
|
* In addition, we often want to split applications into multiple bundles and load them on demand.
|
||||||
* Doing this transparently is not trivial.
|
* Doing this transparently is not trivial.
|
||||||
*
|
*
|
||||||
* The Angular router solves these problems. Using the router, you can declaratively specify
|
* The Angular 2 router solves these problems. Using the router, you can declaratively specify
|
||||||
* application states, manage state transitions while taking care of the URL, and load bundles on
|
* application states, manage state transitions while taking care of the URL, and load bundles on
|
||||||
* demand.
|
* demand.
|
||||||
*
|
*
|
||||||
@ -280,77 +278,22 @@ export function rootRoute(router: Router): ActivatedRoute {
|
|||||||
return router.routerState.root;
|
return router.routerState.root;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export function initialRouterNavigation(
|
||||||
* To initialize the router properly we need to do in two steps:
|
router: Router, ref: ApplicationRef, preloader: RouterPreloader, opts: ExtraOptions) {
|
||||||
*
|
return (bootstrappedComponentRef: ComponentRef<any>) => {
|
||||||
* 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>();
|
|
||||||
|
|
||||||
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]) {
|
if (bootstrappedComponentRef !== ref.components[0]) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const preloader = this.injector.get(RouterPreloader);
|
|
||||||
preloader.setUpPreloading();
|
|
||||||
|
|
||||||
const router = this.injector.get(Router);
|
|
||||||
router.resetRootComponentType(ref.componentTypes[0]);
|
router.resetRootComponentType(ref.componentTypes[0]);
|
||||||
|
preloader.setUpPreloading();
|
||||||
this.resultOfPreactivationDone.next(null);
|
if (opts.initialNavigation === false) {
|
||||||
this.resultOfPreactivationDone.complete();
|
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -362,14 +305,11 @@ export const ROUTER_INITIALIZER = new OpaqueToken('Router Initializer');
|
|||||||
|
|
||||||
export function provideRouterInitializer() {
|
export function provideRouterInitializer() {
|
||||||
return [
|
return [
|
||||||
RouterInitializer,
|
|
||||||
{
|
{
|
||||||
provide: APP_INITIALIZER,
|
provide: ROUTER_INITIALIZER,
|
||||||
multi: true,
|
useFactory: initialRouterNavigation,
|
||||||
useFactory: getAppInitializer,
|
deps: [Router, ApplicationRef, RouterPreloader, ROUTER_CONFIGURATION]
|
||||||
deps: [RouterInitializer]
|
|
||||||
},
|
},
|
||||||
{provide: ROUTER_INITIALIZER, useFactory: getBootstrapListener, deps: [RouterInitializer]},
|
|
||||||
{provide: APP_BOOTSTRAP_LISTENER, multi: true, useExisting: ROUTER_INITIALIZER},
|
{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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@ -5,14 +5,14 @@
|
|||||||
* Use of this source code is governed by an MIT-style license that can be
|
* 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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
import {APP_BOOTSTRAP_LISTENER, ComponentRef, OpaqueToken} from '@angular/core';
|
|
||||||
import {Router} from '@angular/router';
|
import {APP_BOOTSTRAP_LISTENER, ApplicationRef, OpaqueToken} from '@angular/core';
|
||||||
|
import {ExtraOptions, ROUTER_CONFIGURATION, ROUTER_INITIALIZER, Router, RouterPreloader} from '@angular/router';
|
||||||
import {UpgradeModule} from '@angular/upgrade/static';
|
import {UpgradeModule} from '@angular/upgrade/static';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @whatItDoes Creates an initializer that in addition to setting up the Angular
|
* @whatItDoes Creates an initializer that in addition to setting up the Angular 2
|
||||||
* router sets up the ngRoute integration.
|
* router sets up the ngRoute integration.
|
||||||
*
|
*
|
||||||
* @howToUse
|
* @howToUse
|
||||||
@ -35,17 +35,38 @@ import {UpgradeModule} from '@angular/upgrade/static';
|
|||||||
* @experimental
|
* @experimental
|
||||||
*/
|
*/
|
||||||
export const RouterUpgradeInitializer = {
|
export const RouterUpgradeInitializer = {
|
||||||
provide: APP_BOOTSTRAP_LISTENER,
|
provide: ROUTER_INITIALIZER,
|
||||||
multi: true,
|
useFactory: initialRouterNavigation,
|
||||||
useFactory: locationSyncBootstrapListener,
|
deps: [UpgradeModule, ApplicationRef, RouterPreloader, ROUTER_CONFIGURATION]
|
||||||
deps: [UpgradeModule]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export function locationSyncBootstrapListener(ngUpgrade: UpgradeModule) {
|
export function initialRouterNavigation(
|
||||||
return () => { setUpLocationSync(ngUpgrade); };
|
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.resetRootComponentType(ref.componentTypes[0]);
|
||||||
|
preloader.setUpPreloading();
|
||||||
|
if (opts.initialNavigation === false) {
|
||||||
|
router.setUpLocationChangeListener();
|
||||||
|
} else {
|
||||||
|
router.initialNavigation();
|
||||||
|
}
|
||||||
|
|
||||||
|
setUpLocationSync(ngUpgrade);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -56,14 +77,7 @@ export function locationSyncBootstrapListener(ngUpgrade: UpgradeModule) {
|
|||||||
*
|
*
|
||||||
* @experimental
|
* @experimental
|
||||||
*/
|
*/
|
||||||
export function setUpLocationSync(ngUpgrade: UpgradeModule) {
|
export function setUpLocationSync(ngUpgrade: UpgradeModule): void {
|
||||||
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: Router = ngUpgrade.injector.get(Router);
|
const router: Router = ngUpgrade.injector.get(Router);
|
||||||
const url = document.createElement('a');
|
const url = document.createElement('a');
|
||||||
|
|
||||||
@ -72,4 +86,4 @@ export function setUpLocationSync(ngUpgrade: UpgradeModule) {
|
|||||||
url.href = next;
|
url.href = next;
|
||||||
router.navigateByUrl(url.pathname);
|
router.navigateByUrl(url.pathname);
|
||||||
});
|
});
|
||||||
}
|
}
|
@ -25,9 +25,7 @@ export const ROUTES = [
|
|||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [WorkerAppModule, RouterModule.forRoot(ROUTES, {useHash: true})],
|
imports: [WorkerAppModule, RouterModule.forRoot(ROUTES, {useHash: true})],
|
||||||
providers: [
|
providers: [WORKER_APP_LOCATION_PROVIDERS],
|
||||||
WORKER_APP_LOCATION_PROVIDERS,
|
|
||||||
],
|
|
||||||
bootstrap: [App],
|
bootstrap: [App],
|
||||||
declarations: [App, Start, Contact, About]
|
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
@ -80,9 +80,6 @@ export declare class Location {
|
|||||||
static stripTrailingSlash(url: string): string;
|
static stripTrailingSlash(url: string): string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @experimental */
|
|
||||||
export declare const LOCATION_INITIALIZED: OpaqueToken;
|
|
||||||
|
|
||||||
/** @experimental */
|
/** @experimental */
|
||||||
export interface LocationChangeEvent {
|
export interface LocationChangeEvent {
|
||||||
type: string;
|
type: string;
|
||||||
|
@ -89,10 +89,6 @@ export declare const WORKER_APP_LOCATION_PROVIDERS: ({
|
|||||||
useFactory: (platformLocation: WebWorkerPlatformLocation, zone: NgZone) => () => Promise<boolean>;
|
useFactory: (platformLocation: WebWorkerPlatformLocation, zone: NgZone) => () => Promise<boolean>;
|
||||||
multi: boolean;
|
multi: boolean;
|
||||||
deps: (typeof NgZone | typeof PlatformLocation)[];
|
deps: (typeof NgZone | typeof PlatformLocation)[];
|
||||||
} | {
|
|
||||||
provide: OpaqueToken;
|
|
||||||
useFactory: (platformLocation: WebWorkerPlatformLocation) => Promise<any>;
|
|
||||||
deps: typeof PlatformLocation[];
|
|
||||||
})[];
|
})[];
|
||||||
|
|
||||||
/** @experimental */
|
/** @experimental */
|
||||||
|
Reference in New Issue
Block a user