|
6 | 6 | * found in the LICENSE file at https://angular.io/license
|
7 | 7 | */
|
8 | 8 |
|
9 |
| -import {APP_BASE_HREF, HashLocationStrategy, Location, LOCATION_INITIALIZED, LocationStrategy, PathLocationStrategy, PlatformLocation, ViewportScroller} from '@angular/common'; |
10 |
| -import {ANALYZE_FOR_ENTRY_COMPONENTS, APP_BOOTSTRAP_LISTENER, APP_INITIALIZER, ApplicationRef, Compiler, ComponentRef, ENVIRONMENT_INITIALIZER, Inject, inject, Injectable, InjectFlags, InjectionToken, Injector, ModuleWithProviders, NgModule, NgProbeToken, OnDestroy, Optional, Provider, SkipSelf, Type} from '@angular/core'; |
11 |
| -import {Title} from '@angular/platform-browser'; |
| 9 | +import {HashLocationStrategy, Location, LOCATION_INITIALIZED, LocationStrategy, PathLocationStrategy, ViewportScroller} from '@angular/common'; |
| 10 | +import {ANALYZE_FOR_ENTRY_COMPONENTS, APP_BOOTSTRAP_LISTENER, APP_INITIALIZER, ApplicationRef, Compiler, ComponentRef, ENVIRONMENT_INITIALIZER, Inject, inject, InjectFlags, InjectionToken, Injector, ModuleWithProviders, NgModule, NgProbeToken, Optional, Provider, SkipSelf, Type} from '@angular/core'; |
12 | 11 | import {of, Subject} from 'rxjs';
|
13 | 12 |
|
14 | 13 | import {EmptyOutletComponent} from './components/empty_outlet';
|
@@ -42,17 +41,19 @@ const ROUTER_DIRECTIVES =
|
42 | 41 | *
|
43 | 42 | * @publicApi
|
44 | 43 | */
|
45 |
| -export const ROUTER_CONFIGURATION = new InjectionToken<ExtraOptions>('ROUTER_CONFIGURATION', { |
46 |
| - providedIn: 'root', |
47 |
| - factory: () => ({}), |
48 |
| -}); |
| 44 | +export const ROUTER_CONFIGURATION = |
| 45 | + new InjectionToken<ExtraOptions>(NG_DEV_MODE ? 'router config' : 'ROUTER_CONFIGURATION', { |
| 46 | + providedIn: 'root', |
| 47 | + factory: () => ({}), |
| 48 | + }); |
49 | 49 |
|
50 | 50 | /**
|
51 | 51 | * @docsNotRequired
|
52 | 52 | */
|
53 |
| -export const ROUTER_FORROOT_GUARD = new InjectionToken<void>('ROUTER_FORROOT_GUARD'); |
| 53 | +export const ROUTER_FORROOT_GUARD = new InjectionToken<void>( |
| 54 | + NG_DEV_MODE ? 'router duplicate forRoot guard' : 'ROUTER_FORROOT_GUARD'); |
54 | 55 |
|
55 |
| -const ROUTER_PRELOADER = new InjectionToken<RouterPreloader>(''); |
| 56 | +const ROUTER_PRELOADER = new InjectionToken<RouterPreloader>(NG_DEV_MODE ? 'router preloader' : ''); |
56 | 57 |
|
57 | 58 | export const ROUTER_PROVIDERS: Provider[] = [
|
58 | 59 | Location,
|
@@ -139,6 +140,7 @@ export class RouterModule {
|
139 | 140 | provideRouterScroller(),
|
140 | 141 | config?.preloadingStrategy ? providePreloading(config.preloadingStrategy) : [],
|
141 | 142 | {provide: NgProbeToken, multi: true, useFactory: routerNgProbeToken},
|
| 143 | + config?.initialNavigation ? provideInitialNavigation(config) : [], |
142 | 144 | provideRouterInitializer(),
|
143 | 145 | ],
|
144 | 146 | };
|
@@ -511,119 +513,123 @@ export function rootRoute(router: Router): ActivatedRoute {
|
511 | 513 | return router.routerState.root;
|
512 | 514 | }
|
513 | 515 |
|
514 |
| -/** |
515 |
| - * Router initialization requires two steps: |
516 |
| - * |
517 |
| - * First, we start the navigation in a `APP_INITIALIZER` to block the bootstrap if |
518 |
| - * a resolver or a guard executes asynchronously. |
519 |
| - * |
520 |
| - * Next, we actually run activation in a `BOOTSTRAP_LISTENER`, using the |
521 |
| - * `afterPreactivation` hook provided by the router. |
522 |
| - * The router navigation starts, reaches the point when preactivation is done, and then |
523 |
| - * pauses. It waits for the hook to be resolved. We then resolve it only in a bootstrap listener. |
524 |
| - */ |
525 |
| -@Injectable() |
526 |
| -export class RouterInitializer implements OnDestroy { |
527 |
| - private initNavigation = false; |
528 |
| - private destroyed = false; |
529 |
| - private resultOfPreactivationDone = new Subject<void>(); |
530 |
| - |
531 |
| - constructor(private injector: Injector) {} |
532 |
| - |
533 |
| - appInitializer(): Promise<any> { |
534 |
| - const p: Promise<any> = this.injector.get(LOCATION_INITIALIZED, Promise.resolve(null)); |
535 |
| - return p.then(() => { |
536 |
| - // If the injector was destroyed, the DI lookups below will fail. |
537 |
| - if (this.destroyed) { |
538 |
| - return Promise.resolve(true); |
539 |
| - } |
540 |
| - |
541 |
| - let resolve: Function = null!; |
542 |
| - const res = new Promise(r => resolve = r); |
543 |
| - const router = this.injector.get(Router); |
544 |
| - const opts = this.injector.get(ROUTER_CONFIGURATION); |
545 |
| - |
546 |
| - if (opts.initialNavigation === 'disabled') { |
547 |
| - router.setUpLocationChangeListener(); |
548 |
| - resolve(true); |
549 |
| - } else if (opts.initialNavigation === 'enabledBlocking') { |
550 |
| - router.afterPreactivation = () => { |
551 |
| - // only the initial navigation should be delayed |
552 |
| - if (!this.initNavigation) { |
553 |
| - this.initNavigation = true; |
554 |
| - resolve(true); |
555 |
| - return this.resultOfPreactivationDone; |
556 |
| - |
557 |
| - // subsequent navigations should not be delayed |
558 |
| - } else { |
559 |
| - return of(void 0); |
560 |
| - } |
561 |
| - }; |
562 |
| - router.initialNavigation(); |
563 |
| - } else { |
564 |
| - resolve(true); |
565 |
| - } |
566 |
| - |
567 |
| - return res; |
568 |
| - }); |
569 |
| - } |
570 |
| - |
571 |
| - bootstrapListener(bootstrappedComponentRef: ComponentRef<any>): void { |
572 |
| - const opts = this.injector.get(ROUTER_CONFIGURATION); |
573 |
| - const routerScroller: RouterScroller|null = |
574 |
| - this.injector.get(ROUTER_SCROLLER, null, InjectFlags.Optional); |
575 |
| - const router = this.injector.get(Router); |
576 |
| - const ref = this.injector.get<ApplicationRef>(ApplicationRef); |
| 516 | +export function getBootstrapListener() { |
| 517 | + const injector = inject(Injector); |
| 518 | + return (bootstrappedComponentRef: ComponentRef<unknown>) => { |
| 519 | + const ref = injector.get(ApplicationRef); |
577 | 520 |
|
578 | 521 | if (bootstrappedComponentRef !== ref.components[0]) {
|
579 | 522 | return;
|
580 | 523 | }
|
581 | 524 |
|
| 525 | + const router = injector.get(Router); |
| 526 | + const bootstrapDone = injector.get(BOOTSTRAP_DONE); |
| 527 | + |
582 | 528 | // Default case
|
583 |
| - if (opts.initialNavigation === 'enabledNonBlocking' || opts.initialNavigation === undefined) { |
| 529 | + if (injector.get(INITIAL_NAVIGATION, null, InjectFlags.Optional) === null) { |
584 | 530 | router.initialNavigation();
|
585 | 531 | }
|
586 | 532 |
|
587 |
| - this.injector.get(ROUTER_PRELOADER, null, InjectFlags.Optional)?.setUpPreloading(); |
588 |
| - routerScroller?.init(); |
| 533 | + injector.get(ROUTER_PRELOADER, null, InjectFlags.Optional)?.setUpPreloading(); |
| 534 | + injector.get(ROUTER_SCROLLER, null, InjectFlags.Optional)?.init(); |
589 | 535 | router.resetRootComponentType(ref.componentTypes[0]);
|
590 |
| - this.resultOfPreactivationDone.next(void 0); |
591 |
| - this.resultOfPreactivationDone.complete(); |
592 |
| - } |
593 |
| - |
594 |
| - ngOnDestroy() { |
595 |
| - this.destroyed = true; |
596 |
| - } |
597 |
| -} |
598 |
| - |
599 |
| -export function getAppInitializer(r: RouterInitializer) { |
600 |
| - return r.appInitializer.bind(r); |
601 |
| -} |
602 |
| - |
603 |
| -export function getBootstrapListener(r: RouterInitializer) { |
604 |
| - return r.bootstrapListener.bind(r); |
| 536 | + bootstrapDone.next(); |
| 537 | + bootstrapDone.complete(); |
| 538 | + }; |
605 | 539 | }
|
606 | 540 |
|
| 541 | +// TODO(atscott): This should not be in the public API |
607 | 542 | /**
|
608 | 543 | * A [DI token](guide/glossary/#di-token) for the router initializer that
|
609 | 544 | * is called after the app is bootstrapped.
|
610 | 545 | *
|
611 | 546 | * @publicApi
|
612 | 547 | */
|
613 |
| -export const ROUTER_INITIALIZER = |
614 |
| - new InjectionToken<(compRef: ComponentRef<any>) => void>('Router Initializer'); |
| 548 | +export const ROUTER_INITIALIZER = new InjectionToken<(compRef: ComponentRef<any>) => void>( |
| 549 | + NG_DEV_MODE ? 'Router Initializer' : ''); |
| 550 | + |
| 551 | +function provideInitialNavigation(config: Pick<ExtraOptions, 'initialNavigation'>): Provider[] { |
| 552 | + return [ |
| 553 | + config.initialNavigation === 'disabled' ? provideDisabledInitialNavigation() : [], |
| 554 | + config.initialNavigation === 'enabledBlocking' ? provideEnabledBlockingInitialNavigation() : [], |
| 555 | + ]; |
| 556 | +} |
| 557 | + |
| 558 | +function provideRouterInitializer(): ReadonlyArray<Provider> { |
| 559 | + return [ |
| 560 | + // ROUTER_INITIALIZER token should be removed. It's public API but shouldn't be. We can just |
| 561 | + // have `getBootstrapListener` directly attached to APP_BOOTSTRAP_LISTENER. |
| 562 | + {provide: ROUTER_INITIALIZER, useFactory: getBootstrapListener}, |
| 563 | + {provide: APP_BOOTSTRAP_LISTENER, multi: true, useExisting: ROUTER_INITIALIZER}, |
| 564 | + ]; |
| 565 | +} |
| 566 | + |
| 567 | +/** |
| 568 | + * A subject used to indicate that the bootstrapping phase is done. When initial navigation is |
| 569 | + * `enabledBlocking`, the first navigation waits until bootstrapping is finished before continuing |
| 570 | + * to the activation phase. |
| 571 | + */ |
| 572 | +const BOOTSTRAP_DONE = |
| 573 | + new InjectionToken<Subject<void>>(NG_DEV_MODE ? 'bootstrap done indicator' : '', { |
| 574 | + factory: () => { |
| 575 | + return new Subject<void>(); |
| 576 | + } |
| 577 | + }); |
615 | 578 |
|
616 |
| -export function provideRouterInitializer(): ReadonlyArray<Provider> { |
| 579 | +function provideEnabledBlockingInitialNavigation(): Provider { |
617 | 580 | return [
|
618 |
| - RouterInitializer, |
| 581 | + {provide: INITIAL_NAVIGATION, useValue: 'enabledBlocking'}, |
619 | 582 | {
|
620 | 583 | provide: APP_INITIALIZER,
|
621 | 584 | multi: true,
|
622 |
| - useFactory: getAppInitializer, |
623 |
| - deps: [RouterInitializer] |
| 585 | + deps: [Injector], |
| 586 | + useFactory: (injector: Injector) => { |
| 587 | + const locationInitialized: Promise<any> = |
| 588 | + injector.get(LOCATION_INITIALIZED, Promise.resolve(null)); |
| 589 | + let initNavigation = false; |
| 590 | + |
| 591 | + return () => { |
| 592 | + return locationInitialized.then(() => { |
| 593 | + return new Promise(resolve => { |
| 594 | + const router = injector.get(Router); |
| 595 | + const bootstrapDone = injector.get(BOOTSTRAP_DONE); |
| 596 | + |
| 597 | + router.afterPreactivation = () => { |
| 598 | + // only the initial navigation should be delayed |
| 599 | + if (!initNavigation) { |
| 600 | + initNavigation = true; |
| 601 | + resolve(true); |
| 602 | + return bootstrapDone; |
| 603 | + // subsequent navigations should not be delayed |
| 604 | + } else { |
| 605 | + return of(void 0); |
| 606 | + } |
| 607 | + }; |
| 608 | + router.initialNavigation(); |
| 609 | + }); |
| 610 | + }); |
| 611 | + }; |
| 612 | + } |
624 | 613 | },
|
625 |
| - {provide: ROUTER_INITIALIZER, useFactory: getBootstrapListener, deps: [RouterInitializer]}, |
626 |
| - {provide: APP_BOOTSTRAP_LISTENER, multi: true, useExisting: ROUTER_INITIALIZER}, |
| 614 | + ]; |
| 615 | +} |
| 616 | + |
| 617 | +const INITIAL_NAVIGATION = |
| 618 | + new InjectionToken<'disabled'|'enabledBlocking'>(NG_DEV_MODE ? 'initial navigation' : ''); |
| 619 | + |
| 620 | +function provideDisabledInitialNavigation(): Provider[] { |
| 621 | + return [ |
| 622 | + { |
| 623 | + provide: APP_INITIALIZER, |
| 624 | + multi: true, |
| 625 | + useFactory: () => { |
| 626 | + const router = inject(Router); |
| 627 | + return () => { |
| 628 | + router.setUpLocationChangeListener(); |
| 629 | + }; |
| 630 | + } |
| 631 | + }, |
| 632 | + {provide: INITIAL_NAVIGATION, useValue: 'disabled'} |
627 | 633 | ];
|
628 | 634 | }
|
629 | 635 |
|
|
0 commit comments