Skip to content

Commit 75df404

Browse files
atscottalxhub
authored andcommitted
feat(router): Create APIs for using Router without RouterModule (angular#47010)
This commit creates and exposes the APIs required to use the Angular Router without importing `RouterModule`. The newly added APIs are tree-shakable and you can add features using special functions rather than using `ExtraOptions` to control the providers via an internal switch in Router code. ``` const appRoutes: Routes = []; bootstrapApplication(AppComponent, { providers: [ provideRouter(appRoutes, withDebugTracing(), // enables debug tracing feature withInMemoryScrolling() // enables scrolling feature ] } ); ``` This "features" pattern allows for router behavior to evolve in a backwards compatible and tree-shakable way in the future. This approach also makes features more discoverable. The newly added APIs can be used in any application today (doesn't require an application to be bootstrapped using standalone-based APIs). Note: APIs added in this commit are released in the "Developer Preview" mode, read more about this mode in Angular docs: https://angular.io/guide/releases#developer-preview PR Close angular#47010
1 parent 8d4bc83 commit 75df404

32 files changed

+1066
-708
lines changed

goldens/public-api/router/index.md

+68-7
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,9 @@ export type Data = {
210210
[key: string | symbol]: any;
211211
};
212212

213+
// @public
214+
export type DebugTracingFeature = RouterFeature<RouterFeatureKind.DebugTracingFeature>;
215+
213216
// @public
214217
export class DefaultTitleStrategy extends TitleStrategy {
215218
constructor(title: Title);
@@ -234,6 +237,12 @@ export class DefaultUrlSerializer implements UrlSerializer {
234237
// @public
235238
export type DetachedRouteHandle = {};
236239

240+
// @public
241+
export type DisabledInitialNavigationFeature = RouterFeature<RouterFeatureKind.DisabledInitialNavigationFeature>;
242+
243+
// @public
244+
export type EnabledBlockingInitialNavigationFeature = RouterFeature<RouterFeatureKind.EnabledBlockingInitialNavigationFeature>;
245+
237246
// @public
238247
type Event_2 = RouterEvent | NavigationStart | NavigationEnd | NavigationCancel | NavigationError | RoutesRecognized | GuardsCheckStart | GuardsCheckEnd | RouteConfigLoadStart | RouteConfigLoadEnd | ChildActivationStart | ChildActivationEnd | ActivationStart | ActivationEnd | Scroll | ResolveStart | ResolveEnd;
239248
export { Event_2 as Event }
@@ -275,21 +284,15 @@ export const enum EventType {
275284
}
276285

277286
// @public
278-
export interface ExtraOptions {
279-
anchorScrolling?: 'disabled' | 'enabled';
280-
canceledNavigationResolution?: 'replace' | 'computed';
287+
export interface ExtraOptions extends InMemoryScrollingOptions, RouterConfigOptions {
281288
enableTracing?: boolean;
282289
errorHandler?: ErrorHandler;
283290
initialNavigation?: InitialNavigation;
284291
malformedUriErrorHandler?: (error: URIError, urlSerializer: UrlSerializer, url: string) => UrlTree;
285-
onSameUrlNavigation?: 'reload' | 'ignore';
286-
paramsInheritanceStrategy?: 'emptyOnly' | 'always';
287292
preloadingStrategy?: any;
288293
// @deprecated
289294
relativeLinkResolution?: 'legacy' | 'corrected';
290295
scrollOffset?: [number, number] | (() => [number, number]);
291-
scrollPositionRestoration?: 'disabled' | 'enabled' | 'top';
292-
urlUpdateStrategy?: 'deferred' | 'eager';
293296
useHash?: boolean;
294297
}
295298

@@ -333,6 +336,18 @@ export class GuardsCheckStart extends RouterEvent {
333336
// @public
334337
export type InitialNavigation = 'disabled' | 'enabledBlocking' | 'enabledNonBlocking';
335338

339+
// @public
340+
export type InitialNavigationFeature = EnabledBlockingInitialNavigationFeature | DisabledInitialNavigationFeature;
341+
342+
// @public
343+
export type InMemoryScrollingFeature = RouterFeature<RouterFeatureKind.InMemoryScrollingFeature>;
344+
345+
// @public
346+
export interface InMemoryScrollingOptions {
347+
anchorScrolling?: 'disabled' | 'enabled';
348+
scrollPositionRestoration?: 'disabled' | 'enabled' | 'top';
349+
}
350+
336351
// @public
337352
export interface IsActiveMatchOptions {
338353
fragment: 'exact' | 'ignored';
@@ -494,6 +509,9 @@ export class PreloadAllModules implements PreloadingStrategy {
494509
static ɵprov: i0.ɵɵInjectableDeclaration<PreloadAllModules>;
495510
}
496511

512+
// @public
513+
export type PreloadingFeature = RouterFeature<RouterFeatureKind.PreloadingFeature>;
514+
497515
// @public
498516
export abstract class PreloadingStrategy {
499517
// (undocumented)
@@ -503,6 +521,9 @@ export abstract class PreloadingStrategy {
503521
// @public
504522
export const PRIMARY_OUTLET = "primary";
505523

524+
// @public
525+
export function provideRouter(routes: Routes, ...features: RouterFeatures[]): Provider[];
526+
506527
// @public
507528
export function provideRoutes(routes: Routes): Provider[];
508529

@@ -651,6 +672,17 @@ export const ROUTER_CONFIGURATION: InjectionToken<ExtraOptions>;
651672
// @public
652673
export const ROUTER_INITIALIZER: InjectionToken<(compRef: ComponentRef<any>) => void>;
653674

675+
// @public
676+
export interface RouterConfigOptions {
677+
canceledNavigationResolution?: 'replace' | 'computed';
678+
onSameUrlNavigation?: 'reload' | 'ignore';
679+
paramsInheritanceStrategy?: 'emptyOnly' | 'always';
680+
urlUpdateStrategy?: 'deferred' | 'eager';
681+
}
682+
683+
// @public
684+
export type RouterConfigurationFeature = RouterFeature<RouterFeatureKind.RouterConfigurationFeature>;
685+
654686
// @public
655687
export abstract class RouteReuseStrategy {
656688
abstract retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null;
@@ -669,6 +701,17 @@ export class RouterEvent {
669701
url: string;
670702
}
671703

704+
// @public
705+
export interface RouterFeature<FeatureKind extends RouterFeatureKind> {
706+
// (undocumented)
707+
ɵkind: FeatureKind;
708+
// (undocumented)
709+
ɵproviders: Provider[];
710+
}
711+
712+
// @public
713+
export type RouterFeatures = PreloadingFeature | DebugTracingFeature | InitialNavigationFeature | InMemoryScrollingFeature | RouterConfigurationFeature;
714+
672715
// @public
673716
export class RouterLink implements OnChanges {
674717
constructor(router: Router, route: ActivatedRoute, tabIndexAttribute: string | null | undefined, renderer: Renderer2, el: ElementRef);
@@ -1002,6 +1045,24 @@ export class UrlTree {
10021045
// @public (undocumented)
10031046
export const VERSION: Version;
10041047

1048+
// @public
1049+
export function withDebugTracing(): DebugTracingFeature;
1050+
1051+
// @public
1052+
export function withDisabledInitialNavigation(): DisabledInitialNavigationFeature;
1053+
1054+
// @public
1055+
export function withEnabledBlockingInitialNavigation(): EnabledBlockingInitialNavigationFeature;
1056+
1057+
// @public
1058+
export function withInMemoryScrolling(options?: InMemoryScrollingOptions): InMemoryScrollingFeature;
1059+
1060+
// @public
1061+
export function withPreloading(preloadingStrategy: Type<PreloadingStrategy>): PreloadingFeature;
1062+
1063+
// @public
1064+
export function withRouterConfig(options: RouterConfigOptions): RouterConfigurationFeature;
1065+
10051066
// (No @packageDocumentation comment for this package)
10061067

10071068
```

goldens/size-tracking/integration-payloads.json

+5-5
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,16 @@
2626
"cli-hello-world-ivy-i18n": {
2727
"uncompressed": {
2828
"runtime": 926,
29-
"main": 124910,
30-
"polyfills": 35252
29+
"main": 124860,
30+
"polyfills": 35246
3131
}
3232
},
3333
"cli-hello-world-lazy": {
3434
"uncompressed": {
3535
"runtime": 2835,
36-
"main": 238737,
36+
"main": 226893,
3737
"polyfills": 33842,
38-
"src_app_lazy_lazy_module_ts": 780
38+
"src_app_lazy_lazy_routes_ts": 487
3939
}
4040
},
4141
"forms": {
@@ -48,7 +48,7 @@
4848
"animations": {
4949
"uncompressed": {
5050
"runtime": 1070,
51-
"main": 157064,
51+
"main": 156816,
5252
"polyfills": 33814
5353
}
5454
},

integration/cli-hello-world-lazy/src/app/app-routing.module.ts

-11
This file was deleted.

integration/cli-hello-world-lazy/src/app/app.component.css

Whitespace-only changes.

integration/cli-hello-world-lazy/src/app/app.component.spec.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ describe('AppComponent', () => {
66
beforeEach(waitForAsync(() => {
77
TestBed
88
.configureTestingModule({
9-
imports: [RouterTestingModule],
10-
declarations: [AppComponent],
9+
imports: [AppComponent],
1110
})
1211
.compileComponents();
1312
}));

integration/cli-hello-world-lazy/src/app/app.component.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
import { Component } from '@angular/core';
1+
import {CommonModule} from '@angular/common';
2+
import {Component} from '@angular/core';
3+
import {RouterOutlet} from '@angular/router';
24

35
@Component({
46
selector: 'app-root',
7+
standalone: true,
8+
imports: [RouterOutlet, CommonModule],
59
templateUrl: './app.component.html',
6-
styleUrls: ['./app.component.css']
710
})
811
export class AppComponent {
912
title = 'cli-hello-world-lazy';

integration/cli-hello-world-lazy/src/app/app.module.ts

-18
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import {Routes} from '@angular/router';
2+
3+
4+
export const appRoutes: Routes = [{
5+
path: 'lazy',
6+
loadChildren: () => import('./lazy/lazy.routes').then(routes => routes.lazyRoutes),
7+
}];

integration/cli-hello-world-lazy/src/app/lazy/lazy-routing.module.ts

-12
This file was deleted.

integration/cli-hello-world-lazy/src/app/lazy/lazy.component.css

Whitespace-only changes.

integration/cli-hello-world-lazy/src/app/lazy/lazy.component.html

-1
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,9 @@
1-
import { Component, OnInit } from '@angular/core';
1+
import {Component} from '@angular/core';
22

33
@Component({
4+
standalone: true,
45
selector: 'app-lazy',
5-
templateUrl: './lazy.component.html',
6-
styleUrls: ['./lazy.component.css']
6+
template: '<p>lazy works!</p>',
77
})
8-
export class LazyComponent implements OnInit {
9-
10-
constructor() { }
11-
12-
ngOnInit() {
13-
}
14-
8+
export class LazyComponent {
159
}

integration/cli-hello-world-lazy/src/app/lazy/lazy.module.ts

-15
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import {Routes} from '@angular/router';
2+
3+
import {LazyComponent} from './lazy.component';
4+
5+
export const lazyRoutes: Routes = [{path: '', component: LazyComponent}];
+12-6
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
1-
import { enableProdMode } from '@angular/core';
2-
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
1+
import {enableProdMode} from '@angular/core';
2+
import {bootstrapApplication, provideProtractorTestingSupport} from '@angular/platform-browser';
3+
import {provideRouter} from '@angular/router';
34

4-
import { AppModule } from './app/app.module';
5-
import { environment } from './environments/environment';
5+
import {AppComponent} from './app/app.component';
6+
import {appRoutes} from './app/app.routes';
7+
import {environment} from './environments/environment';
68

79
if (environment.production) {
810
enableProdMode();
911
}
1012

11-
platformBrowserDynamic().bootstrapModule(AppModule)
12-
.catch(err => console.error(err));
13+
bootstrapApplication(AppComponent, {
14+
providers: [
15+
provideRouter(appRoutes),
16+
provideProtractorTestingSupport(),
17+
]
18+
}).catch(console.error);

0 commit comments

Comments
 (0)