feat(router): add new initialNavigation options to replace legacy (#37480)
As of Angular v4, four of the options for `ExtraOptions#initialNavigation` have been deprecated. We intend to remove them in v11. The final state for these options is: `enabledBlocking`, `enabledNonBlocking`, and `disabled`. We plan to remove and deprecate the remaining option in the next two major releases. New options: - `enabledNonBlocking`: same as legacy_enabled - `enabledBlocking`: same as enabled BREAKING CHANGE: * The `initialNavigation` property for the options in `RouterModule.forRoot` no longer supports `legacy_disabled`, `legacy_enabled`, `true`, or `false` as valid values. `legacy_enabled` (the old default) is instead `enabledNonBlocking` * `enabled` is deprecated as a valid value for the `RouterModule.forRoot` `initialNavigation` option. `enabledBlocking` has been introduced to replace it PR Close #37480
This commit is contained in:
parent
b0a8b69ae8
commit
c4becca0e4
|
@ -156,7 +156,7 @@ export declare class GuardsCheckStart extends RouterEvent {
|
||||||
toString(): string;
|
toString(): string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export declare type InitialNavigation = true | false | 'enabled' | 'disabled' | 'legacy_enabled' | 'legacy_disabled';
|
export declare type InitialNavigation = 'disabled' | 'enabled' | 'enabledBlocking' | 'enabledNonBlocking';
|
||||||
|
|
||||||
export declare type LoadChildren = LoadChildrenCallback | DeprecatedLoadChildren;
|
export declare type LoadChildren = LoadChildrenCallback | DeprecatedLoadChildren;
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,7 @@
|
||||||
"master": {
|
"master": {
|
||||||
"uncompressed": {
|
"uncompressed": {
|
||||||
"runtime-es2015": 2289,
|
"runtime-es2015": 2289,
|
||||||
"main-es2015": 218329,
|
"main-es2015": 217827,
|
||||||
"polyfills-es2015": 36723,
|
"polyfills-es2015": 36723,
|
||||||
"5-es2015": 781
|
"5-es2015": 781
|
||||||
}
|
}
|
||||||
|
|
|
@ -222,7 +222,10 @@ export function provideRoutes(routes: Routes): any {
|
||||||
* Allowed values in an `ExtraOptions` object that configure
|
* Allowed values in an `ExtraOptions` object that configure
|
||||||
* when the router performs the initial navigation operation.
|
* when the router performs the initial navigation operation.
|
||||||
*
|
*
|
||||||
* * 'enabled' - The initial navigation starts before the root component is created.
|
* * 'enabledNonBlocking' - (default) The initial navigation starts after the
|
||||||
|
* root component has been created. The bootstrap is not blocked on the completion of the initial
|
||||||
|
* navigation.
|
||||||
|
* * 'enabledBlocking' - The initial navigation starts before the root component is created.
|
||||||
* The bootstrap is blocked until the initial navigation is complete. This value is required
|
* The bootstrap is blocked until the initial navigation is complete. This value is required
|
||||||
* for [server-side rendering](guide/universal) to work.
|
* for [server-side rendering](guide/universal) to work.
|
||||||
* * 'disabled' - The initial navigation is not performed. The location listener is set up before
|
* * 'disabled' - The initial navigation is not performed. The location listener is set up before
|
||||||
|
@ -230,24 +233,16 @@ export function provideRoutes(routes: Routes): any {
|
||||||
* more control over when the router starts its initial navigation due to some complex
|
* more control over when the router starts its initial navigation due to some complex
|
||||||
* initialization logic.
|
* initialization logic.
|
||||||
*
|
*
|
||||||
* The following values have been [deprecated](guide/releases#deprecation-practices) since v4,
|
* The following values have been [deprecated](guide/releases#deprecation-practices) since v11,
|
||||||
* and should not be used for new applications.
|
* and should not be used for new applications.
|
||||||
*
|
*
|
||||||
* * 'legacy_enabled'- (Default, for compatibility.) The initial navigation starts after the root
|
* * 'enabled' - This option is 1:1 replaceable with `enabledNonBlocking`.
|
||||||
* component has been created. The bootstrap is not blocked until the initial navigation is
|
|
||||||
* complete.
|
|
||||||
* * 'legacy_disabled'- The initial navigation is not performed. The location listener is set up
|
|
||||||
* after the root component gets created.
|
|
||||||
* * `true` - same as 'legacy_enabled'.
|
|
||||||
* * `false` - same as 'legacy_disabled'.
|
|
||||||
*
|
|
||||||
* The 'legacy_enabled' and 'legacy_disabled' should not be used for new applications.
|
|
||||||
*
|
*
|
||||||
* @see `forRoot()`
|
* @see `forRoot()`
|
||||||
*
|
*
|
||||||
* @publicApi
|
* @publicApi
|
||||||
*/
|
*/
|
||||||
export type InitialNavigation = true|false|'enabled'|'disabled'|'legacy_enabled'|'legacy_disabled';
|
export type InitialNavigation = 'disabled'|'enabled'|'enabledBlocking'|'enabledNonBlocking';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A set of configuration options for a router module, provided in the
|
* A set of configuration options for a router module, provided in the
|
||||||
|
@ -272,24 +267,15 @@ export interface ExtraOptions {
|
||||||
useHash?: boolean;
|
useHash?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* One of `enabled` or `disabled`.
|
* One of `enabled`, `enabledBlocking`, `enabledNonBlocking` or `disabled`.
|
||||||
* When set to `enabled`, the initial navigation starts before the root component is created.
|
* When set to `enabled` or `enabledBlocking`, the initial navigation starts before the root
|
||||||
* The bootstrap is blocked until the initial navigation is complete. This value is required for
|
* component is created. The bootstrap is blocked until the initial navigation is complete. This
|
||||||
* [server-side rendering](guide/universal) to work.
|
* value is required for [server-side rendering](guide/universal) to work. When set to
|
||||||
* When set to `disabled`, the initial navigation is not performed.
|
* `enabledNonBlocking`, the initial navigation starts after the root component has been created.
|
||||||
* The location listener is set up before the root component gets created.
|
* The bootstrap is not blocked on the completion of the initial navigation. When set to
|
||||||
* Use if there is a reason to have more control over when the router
|
* `disabled`, the initial navigation is not performed. The location listener is set up before the
|
||||||
|
* root component gets created. Use if there is a reason to have more control over when the router
|
||||||
* starts its initial navigation due to some complex initialization logic.
|
* starts its initial navigation due to some complex initialization logic.
|
||||||
*
|
|
||||||
* Legacy values are deprecated since v4 and should not be used for new applications:
|
|
||||||
*
|
|
||||||
* * `legacy_enabled` - Default for compatibility.
|
|
||||||
* The initial navigation starts after the root component has been created,
|
|
||||||
* but the bootstrap is not blocked until the initial navigation is complete.
|
|
||||||
* * `legacy_disabled` - The initial navigation is not performed.
|
|
||||||
* The location listener is set up after the root component gets created.
|
|
||||||
* * `true` - same as `legacy_enabled`.
|
|
||||||
* * `false` - same as `legacy_disabled`.
|
|
||||||
*/
|
*/
|
||||||
initialNavigation?: InitialNavigation;
|
initialNavigation?: InitialNavigation;
|
||||||
|
|
||||||
|
@ -519,14 +505,12 @@ export class RouterInitializer {
|
||||||
const router = this.injector.get(Router);
|
const router = this.injector.get(Router);
|
||||||
const opts = this.injector.get(ROUTER_CONFIGURATION);
|
const opts = this.injector.get(ROUTER_CONFIGURATION);
|
||||||
|
|
||||||
if (this.isLegacyDisabled(opts) || this.isLegacyEnabled(opts)) {
|
if (opts.initialNavigation === 'disabled') {
|
||||||
resolve(true);
|
|
||||||
|
|
||||||
} else if (opts.initialNavigation === 'disabled') {
|
|
||||||
router.setUpLocationChangeListener();
|
router.setUpLocationChangeListener();
|
||||||
resolve(true);
|
resolve(true);
|
||||||
|
} else if (
|
||||||
} else if (opts.initialNavigation === 'enabled') {
|
// TODO: enabled is deprecated as of v11, can be removed in v13
|
||||||
|
opts.initialNavigation === 'enabled' || opts.initialNavigation === 'enabledBlocking') {
|
||||||
router.hooks.afterPreactivation = () => {
|
router.hooks.afterPreactivation = () => {
|
||||||
// only the initial navigation should be delayed
|
// only the initial navigation should be delayed
|
||||||
if (!this.initNavigation) {
|
if (!this.initNavigation) {
|
||||||
|
@ -540,9 +524,8 @@ export class RouterInitializer {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
router.initialNavigation();
|
router.initialNavigation();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Invalid initialNavigation options: '${opts.initialNavigation}'`);
|
resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
|
@ -560,10 +543,9 @@ export class RouterInitializer {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isLegacyEnabled(opts)) {
|
// Default case
|
||||||
|
if (opts.initialNavigation === 'enabledNonBlocking' || opts.initialNavigation === undefined) {
|
||||||
router.initialNavigation();
|
router.initialNavigation();
|
||||||
} else if (this.isLegacyDisabled(opts)) {
|
|
||||||
router.setUpLocationChangeListener();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
preloader.setUpPreloading();
|
preloader.setUpPreloading();
|
||||||
|
@ -572,15 +554,6 @@ export class RouterInitializer {
|
||||||
this.resultOfPreactivationDone.next(null!);
|
this.resultOfPreactivationDone.next(null!);
|
||||||
this.resultOfPreactivationDone.complete();
|
this.resultOfPreactivationDone.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
private isLegacyEnabled(opts: ExtraOptions): boolean {
|
|
||||||
return opts.initialNavigation === 'legacy_enabled' || opts.initialNavigation === true ||
|
|
||||||
opts.initialNavigation === undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
private isLegacyDisabled(opts: ExtraOptions): boolean {
|
|
||||||
return opts.initialNavigation === 'legacy_disabled' || opts.initialNavigation === false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAppInitializer(r: RouterInitializer) {
|
export function getAppInitializer(r: RouterInitializer) {
|
||||||
|
|
|
@ -95,7 +95,44 @@ describe('bootstrap', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should NOT wait for resolvers to complete when initialNavigation = legacy_enabled',
|
it('should wait for resolvers to complete when initialNavigation = enabledBlocking', (done) => {
|
||||||
|
@Component({selector: 'test', template: 'test'})
|
||||||
|
class TestCmpEnabled {
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
BrowserModule,
|
||||||
|
RouterModule.forRoot(
|
||||||
|
[{path: '**', component: TestCmpEnabled, resolve: {test: TestResolver}}],
|
||||||
|
{useHash: true, initialNavigation: 'enabledBlocking'})
|
||||||
|
],
|
||||||
|
declarations: [RootCmp, TestCmpEnabled],
|
||||||
|
bootstrap: [RootCmp],
|
||||||
|
providers: [...testProviders, TestResolver],
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
|
})
|
||||||
|
class TestModule {
|
||||||
|
constructor(router: Router) {
|
||||||
|
log.push('TestModule');
|
||||||
|
router.events.subscribe(e => log.push(e.constructor.name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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');
|
||||||
|
expect(log).toEqual([
|
||||||
|
'TestModule', 'NavigationStart', 'RoutesRecognized', 'GuardsCheckStart',
|
||||||
|
'ChildActivationStart', 'ActivationStart', 'GuardsCheckEnd', 'ResolveStart', 'ResolveEnd',
|
||||||
|
'RootCmp', 'ActivationEnd', 'ChildActivationEnd', 'NavigationEnd', 'Scroll'
|
||||||
|
]);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should NOT wait for resolvers to complete when initialNavigation = enabledNonBlocking',
|
||||||
(done) => {
|
(done) => {
|
||||||
@Component({selector: 'test', template: 'test'})
|
@Component({selector: 'test', template: 'test'})
|
||||||
class TestCmpLegacyEnabled {
|
class TestCmpLegacyEnabled {
|
||||||
|
@ -106,7 +143,7 @@ describe('bootstrap', () => {
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
RouterModule.forRoot(
|
RouterModule.forRoot(
|
||||||
[{path: '**', component: TestCmpLegacyEnabled, resolve: {test: TestResolver}}],
|
[{path: '**', component: TestCmpLegacyEnabled, resolve: {test: TestResolver}}],
|
||||||
{useHash: true, initialNavigation: 'legacy_enabled'})
|
{useHash: true, initialNavigation: 'enabledNonBlocking'})
|
||||||
],
|
],
|
||||||
declarations: [RootCmp, TestCmpLegacyEnabled],
|
declarations: [RootCmp, TestCmpLegacyEnabled],
|
||||||
bootstrap: [RootCmp],
|
bootstrap: [RootCmp],
|
||||||
|
@ -137,6 +174,47 @@ describe('bootstrap', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should NOT wait for resolvers to complete when initialNavigation is not set', (done) => {
|
||||||
|
@Component({selector: 'test', template: 'test'})
|
||||||
|
class TestCmpLegacyEnabled {
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
BrowserModule,
|
||||||
|
RouterModule.forRoot(
|
||||||
|
[{path: '**', component: TestCmpLegacyEnabled, resolve: {test: TestResolver}}],
|
||||||
|
{useHash: true})
|
||||||
|
],
|
||||||
|
declarations: [RootCmp, TestCmpLegacyEnabled],
|
||||||
|
bootstrap: [RootCmp],
|
||||||
|
providers: [...testProviders, TestResolver],
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
|
})
|
||||||
|
class TestModule {
|
||||||
|
constructor(router: Router) {
|
||||||
|
log.push('TestModule');
|
||||||
|
router.events.subscribe(e => log.push(e.constructor.name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
platformBrowserDynamic([]).bootstrapModule(TestModule).then(res => {
|
||||||
|
const router: Router = res.injector.get(Router);
|
||||||
|
expect(router.routerState.snapshot.root.firstChild).toBeNull();
|
||||||
|
// ResolveEnd has not been emitted yet because bootstrap returned too early
|
||||||
|
expect(log).toEqual([
|
||||||
|
'TestModule', 'RootCmp', 'NavigationStart', 'RoutesRecognized', 'GuardsCheckStart',
|
||||||
|
'ChildActivationStart', 'ActivationStart', 'GuardsCheckEnd', 'ResolveStart'
|
||||||
|
]);
|
||||||
|
|
||||||
|
router.events.subscribe((e) => {
|
||||||
|
if (e instanceof NavigationEnd) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should not run navigation when initialNavigation = disabled', (done) => {
|
it('should not run navigation when initialNavigation = disabled', (done) => {
|
||||||
@Component({selector: 'test', template: 'test'})
|
@Component({selector: 'test', template: 'test'})
|
||||||
class TestCmpDiabled {
|
class TestCmpDiabled {
|
||||||
|
@ -168,37 +246,6 @@ describe('bootstrap', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not run navigation when initialNavigation = legacy_disabled', (done) => {
|
|
||||||
@Component({selector: 'test', template: 'test'})
|
|
||||||
class TestCmpLegacyDisabled {
|
|
||||||
}
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [
|
|
||||||
BrowserModule,
|
|
||||||
RouterModule.forRoot(
|
|
||||||
[{path: '**', component: TestCmpLegacyDisabled, resolve: {test: TestResolver}}],
|
|
||||||
{useHash: true, initialNavigation: 'legacy_disabled'})
|
|
||||||
],
|
|
||||||
declarations: [RootCmp, TestCmpLegacyDisabled],
|
|
||||||
bootstrap: [RootCmp],
|
|
||||||
providers: [...testProviders, TestResolver],
|
|
||||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
|
||||||
})
|
|
||||||
class TestModule {
|
|
||||||
constructor(router: Router) {
|
|
||||||
log.push('TestModule');
|
|
||||||
router.events.subscribe(e => log.push(e.constructor.name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
platformBrowserDynamic([]).bootstrapModule(TestModule).then(res => {
|
|
||||||
const router = res.injector.get(Router);
|
|
||||||
expect(log).toEqual(['TestModule', 'RootCmp']);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not init router navigation listeners if a non root component is bootstrapped',
|
it('should not init router navigation listeners if a non root component is bootstrapped',
|
||||||
(done) => {
|
(done) => {
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
|
Loading…
Reference in New Issue