Skip to content

Commit

Permalink
feat(router): Create APIs for using Router without RouterModule (#47010)
Browse files Browse the repository at this point in the history
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 #47010
  • Loading branch information
atscott authored and alxhub committed Aug 15, 2022
1 parent 8d4bc83 commit 75df404
Show file tree
Hide file tree
Showing 32 changed files with 1,066 additions and 708 deletions.
75 changes: 68 additions & 7 deletions goldens/public-api/router/index.md
Expand Up @@ -210,6 +210,9 @@ export type Data = {
[key: string | symbol]: any;
};

// @public
export type DebugTracingFeature = RouterFeature<RouterFeatureKind.DebugTracingFeature>;

// @public
export class DefaultTitleStrategy extends TitleStrategy {
constructor(title: Title);
Expand All @@ -234,6 +237,12 @@ export class DefaultUrlSerializer implements UrlSerializer {
// @public
export type DetachedRouteHandle = {};

// @public
export type DisabledInitialNavigationFeature = RouterFeature<RouterFeatureKind.DisabledInitialNavigationFeature>;

// @public
export type EnabledBlockingInitialNavigationFeature = RouterFeature<RouterFeatureKind.EnabledBlockingInitialNavigationFeature>;

// @public
type Event_2 = RouterEvent | NavigationStart | NavigationEnd | NavigationCancel | NavigationError | RoutesRecognized | GuardsCheckStart | GuardsCheckEnd | RouteConfigLoadStart | RouteConfigLoadEnd | ChildActivationStart | ChildActivationEnd | ActivationStart | ActivationEnd | Scroll | ResolveStart | ResolveEnd;
export { Event_2 as Event }
Expand Down Expand Up @@ -275,21 +284,15 @@ export const enum EventType {
}

// @public
export interface ExtraOptions {
anchorScrolling?: 'disabled' | 'enabled';
canceledNavigationResolution?: 'replace' | 'computed';
export interface ExtraOptions extends InMemoryScrollingOptions, RouterConfigOptions {
enableTracing?: boolean;
errorHandler?: ErrorHandler;
initialNavigation?: InitialNavigation;
malformedUriErrorHandler?: (error: URIError, urlSerializer: UrlSerializer, url: string) => UrlTree;
onSameUrlNavigation?: 'reload' | 'ignore';
paramsInheritanceStrategy?: 'emptyOnly' | 'always';
preloadingStrategy?: any;
// @deprecated
relativeLinkResolution?: 'legacy' | 'corrected';
scrollOffset?: [number, number] | (() => [number, number]);
scrollPositionRestoration?: 'disabled' | 'enabled' | 'top';
urlUpdateStrategy?: 'deferred' | 'eager';
useHash?: boolean;
}

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

// @public
export type InitialNavigationFeature = EnabledBlockingInitialNavigationFeature | DisabledInitialNavigationFeature;

// @public
export type InMemoryScrollingFeature = RouterFeature<RouterFeatureKind.InMemoryScrollingFeature>;

// @public
export interface InMemoryScrollingOptions {
anchorScrolling?: 'disabled' | 'enabled';
scrollPositionRestoration?: 'disabled' | 'enabled' | 'top';
}

// @public
export interface IsActiveMatchOptions {
fragment: 'exact' | 'ignored';
Expand Down Expand Up @@ -494,6 +509,9 @@ export class PreloadAllModules implements PreloadingStrategy {
static ɵprov: i0.ɵɵInjectableDeclaration<PreloadAllModules>;
}

// @public
export type PreloadingFeature = RouterFeature<RouterFeatureKind.PreloadingFeature>;

// @public
export abstract class PreloadingStrategy {
// (undocumented)
Expand All @@ -503,6 +521,9 @@ export abstract class PreloadingStrategy {
// @public
export const PRIMARY_OUTLET = "primary";

// @public
export function provideRouter(routes: Routes, ...features: RouterFeatures[]): Provider[];

// @public
export function provideRoutes(routes: Routes): Provider[];

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

// @public
export interface RouterConfigOptions {
canceledNavigationResolution?: 'replace' | 'computed';
onSameUrlNavigation?: 'reload' | 'ignore';
paramsInheritanceStrategy?: 'emptyOnly' | 'always';
urlUpdateStrategy?: 'deferred' | 'eager';
}

// @public
export type RouterConfigurationFeature = RouterFeature<RouterFeatureKind.RouterConfigurationFeature>;

// @public
export abstract class RouteReuseStrategy {
abstract retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null;
Expand All @@ -669,6 +701,17 @@ export class RouterEvent {
url: string;
}

// @public
export interface RouterFeature<FeatureKind extends RouterFeatureKind> {
// (undocumented)
ɵkind: FeatureKind;
// (undocumented)
ɵproviders: Provider[];
}

// @public
export type RouterFeatures = PreloadingFeature | DebugTracingFeature | InitialNavigationFeature | InMemoryScrollingFeature | RouterConfigurationFeature;

// @public
export class RouterLink implements OnChanges {
constructor(router: Router, route: ActivatedRoute, tabIndexAttribute: string | null | undefined, renderer: Renderer2, el: ElementRef);
Expand Down Expand Up @@ -1002,6 +1045,24 @@ export class UrlTree {
// @public (undocumented)
export const VERSION: Version;

// @public
export function withDebugTracing(): DebugTracingFeature;

// @public
export function withDisabledInitialNavigation(): DisabledInitialNavigationFeature;

// @public
export function withEnabledBlockingInitialNavigation(): EnabledBlockingInitialNavigationFeature;

// @public
export function withInMemoryScrolling(options?: InMemoryScrollingOptions): InMemoryScrollingFeature;

// @public
export function withPreloading(preloadingStrategy: Type<PreloadingStrategy>): PreloadingFeature;

// @public
export function withRouterConfig(options: RouterConfigOptions): RouterConfigurationFeature;

// (No @packageDocumentation comment for this package)

```
10 changes: 5 additions & 5 deletions goldens/size-tracking/integration-payloads.json
Expand Up @@ -26,16 +26,16 @@
"cli-hello-world-ivy-i18n": {
"uncompressed": {
"runtime": 926,
"main": 124910,
"polyfills": 35252
"main": 124860,
"polyfills": 35246
}
},
"cli-hello-world-lazy": {
"uncompressed": {
"runtime": 2835,
"main": 238737,
"main": 226893,
"polyfills": 33842,
"src_app_lazy_lazy_module_ts": 780
"src_app_lazy_lazy_routes_ts": 487
}
},
"forms": {
Expand All @@ -48,7 +48,7 @@
"animations": {
"uncompressed": {
"runtime": 1070,
"main": 157064,
"main": 156816,
"polyfills": 33814
}
},
Expand Down
11 changes: 0 additions & 11 deletions integration/cli-hello-world-lazy/src/app/app-routing.module.ts

This file was deleted.

Empty file.
Expand Up @@ -6,8 +6,7 @@ describe('AppComponent', () => {
beforeEach(waitForAsync(() => {
TestBed
.configureTestingModule({
imports: [RouterTestingModule],
declarations: [AppComponent],
imports: [AppComponent],
})
.compileComponents();
}));
Expand Down
7 changes: 5 additions & 2 deletions integration/cli-hello-world-lazy/src/app/app.component.ts
@@ -1,9 +1,12 @@
import { Component } from '@angular/core';
import {CommonModule} from '@angular/common';
import {Component} from '@angular/core';
import {RouterOutlet} from '@angular/router';

@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet, CommonModule],
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'cli-hello-world-lazy';
Expand Down
18 changes: 0 additions & 18 deletions integration/cli-hello-world-lazy/src/app/app.module.ts

This file was deleted.

7 changes: 7 additions & 0 deletions integration/cli-hello-world-lazy/src/app/app.routes.ts
@@ -0,0 +1,7 @@
import {Routes} from '@angular/router';


export const appRoutes: Routes = [{
path: 'lazy',
loadChildren: () => import('./lazy/lazy.routes').then(routes => routes.lazyRoutes),
}];

This file was deleted.

Empty file.

This file was deleted.

14 changes: 4 additions & 10 deletions integration/cli-hello-world-lazy/src/app/lazy/lazy.component.ts
@@ -1,15 +1,9 @@
import { Component, OnInit } from '@angular/core';
import {Component} from '@angular/core';

@Component({
standalone: true,
selector: 'app-lazy',
templateUrl: './lazy.component.html',
styleUrls: ['./lazy.component.css']
template: '<p>lazy works!</p>',
})
export class LazyComponent implements OnInit {

constructor() { }

ngOnInit() {
}

export class LazyComponent {
}
15 changes: 0 additions & 15 deletions integration/cli-hello-world-lazy/src/app/lazy/lazy.module.ts

This file was deleted.

5 changes: 5 additions & 0 deletions integration/cli-hello-world-lazy/src/app/lazy/lazy.routes.ts
@@ -0,0 +1,5 @@
import {Routes} from '@angular/router';

import {LazyComponent} from './lazy.component';

export const lazyRoutes: Routes = [{path: '', component: LazyComponent}];
18 changes: 12 additions & 6 deletions integration/cli-hello-world-lazy/src/main.ts
@@ -1,12 +1,18 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import {enableProdMode} from '@angular/core';
import {bootstrapApplication, provideProtractorTestingSupport} from '@angular/platform-browser';
import {provideRouter} from '@angular/router';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import {AppComponent} from './app/app.component';
import {appRoutes} from './app/app.routes';
import {environment} from './environments/environment';

if (environment.production) {
enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));
bootstrapApplication(AppComponent, {
providers: [
provideRouter(appRoutes),
provideProtractorTestingSupport(),
]
}).catch(console.error);

0 comments on commit 75df404

Please sign in to comment.