Skip to content

Commit

Permalink
refactor(router): Warn when provideRoutes is used without provideRout…
Browse files Browse the repository at this point in the history
…er (#47896)

Due to being only 1 letter away from `provideRouter`, it is quite
possible that developers may accidentally use `provideRoutes` rather
than `provideRouter` in the `boostrapApplication` function. This change
will warn developers when `provideRoutes` is used without the `Router`.

PR Close #47896
  • Loading branch information
atscott authored and AndrewKushnir committed Nov 4, 2022
1 parent 6b659d0 commit bc0fc02
Show file tree
Hide file tree
Showing 5 changed files with 43 additions and 7 deletions.
2 changes: 1 addition & 1 deletion aio/content/guide/deprecations.md
Expand Up @@ -168,7 +168,7 @@ In the [API reference section](api) of this site, deprecated APIs are indicated
| [`resolver` argument in `RouterOutletContract.activateWith`](api/router/RouterOutletContract#activatewith) | No replacement needed | v14 | Component factories are not required to create an instance of a component dynamically. Passing a factory resolver via `resolver` argument is no longer needed. |
| [`resolver` field of the `OutletContext` class](api/router/OutletContext#resolver) | No replacement needed | v14 | Component factories are not required to create an instance of a component dynamically. Passing a factory resolver via `resolver` class field is no longer needed. |
| [`RouterLinkWithHref` directive](api/router/RouterLinkWithHref) | Use `RouterLink` instead. | v15 | The `RouterLinkWithHref` directive code was merged into `RouterLink`. Now the `RouterLink` directive can be used for all elements that have `routerLink` attribute. |
| [`provideRoutes` function](api/router/provideRoutes) | Use `ROUTES` `InjectionToken` instead. | v15 | The `provideRoutes` helper function is minimally useful and can be unintentionally used instead of `provideRoutes` due similar spelling. |
| [`provideRoutes` function](api/router/provideRoutes) | Use `ROUTES` `InjectionToken` instead. | v15 | The `provideRoutes` helper function is minimally useful and can be unintentionally used instead of `provideRouter` due to similar spelling. |


<a id="platform-browser"></a>
Expand Down
6 changes: 3 additions & 3 deletions packages/core/test/bundling/router/bundle.golden_symbols.json
Expand Up @@ -1577,9 +1577,6 @@
{
"name": "promise"
},
{
"name": "provideRoutes"
},
{
"name": "redirectIfUrlTree"
},
Expand Down Expand Up @@ -1640,6 +1637,9 @@
{
"name": "rootRoute"
},
{
"name": "routes"
},
{
"name": "rxSubscriber"
},
Expand Down
24 changes: 24 additions & 0 deletions packages/router/src/provide_router.ts
Expand Up @@ -61,6 +61,7 @@ const NG_DEV_MODE = typeof ngDevMode === 'undefined' || ngDevMode;
export function provideRouter(routes: Routes, ...features: RouterFeatures[]): EnvironmentProviders {
return makeEnvironmentProviders([
{provide: ROUTES, multi: true, useValue: routes},
NG_DEV_MODE ? {provide: ROUTER_IS_PROVIDED, useValue: true} : [],
{provide: ActivatedRoute, useFactory: rootRoute, deps: [Router]},
{provide: APP_BOOTSTRAP_LISTENER, multi: true, useFactory: getBootstrapListener},
features.map(feature => feature.ɵproviders),
Expand Down Expand Up @@ -93,6 +94,28 @@ function routerFeature<FeatureKind extends RouterFeatureKind>(
return {ɵkind: kind, ɵproviders: providers};
}


/**
* An Injection token used to indicate whether `provideRouter` or `RouterModule.forRoot` was ever
* called.
*/
export const ROUTER_IS_PROVIDED =
new InjectionToken<boolean>('', {providedIn: 'root', factory: () => false});

const routerIsProvidedDevModeCheck = {
provide: ENVIRONMENT_INITIALIZER,
multi: true,
useFactory() {
return () => {
if (!inject(ROUTER_IS_PROVIDED)) {
console.warn(
'`provideRoutes` was called without `provideRouter` or `RouterModule.forRoot`. ' +
'This is likely a mistake.');
}
};
}
};

/**
* Registers a [DI provider](guide/glossary#provider) for a set of routes.
* @param routes The route configuration to provide.
Expand All @@ -113,6 +136,7 @@ function routerFeature<FeatureKind extends RouterFeatureKind>(
export function provideRoutes(routes: Routes): Provider[] {
return [
{provide: ROUTES, multi: true, useValue: routes},
NG_DEV_MODE ? routerIsProvidedDevModeCheck : [],
];
}

Expand Down
5 changes: 4 additions & 1 deletion packages/router/src/router_module.ts
Expand Up @@ -15,7 +15,7 @@ import {RouterLinkActive} from './directives/router_link_active';
import {RouterOutlet} from './directives/router_outlet';
import {RuntimeErrorCode} from './errors';
import {Routes} from './models';
import {getBootstrapListener, rootRoute, withDebugTracing, withDisabledInitialNavigation, withEnabledBlockingInitialNavigation, withPreloading} from './provide_router';
import {getBootstrapListener, rootRoute, ROUTER_IS_PROVIDED, withDebugTracing, withDisabledInitialNavigation, withEnabledBlockingInitialNavigation, withPreloading} from './provide_router';
import {Router, setupRouter} from './router';
import {ExtraOptions, ROUTER_CONFIGURATION} from './router_config';
import {RouterConfigLoader, ROUTES} from './router_config_loader';
Expand Down Expand Up @@ -48,6 +48,9 @@ export const ROUTER_PROVIDERS: Provider[] = [
ChildrenOutletContexts,
{provide: ActivatedRoute, useFactory: rootRoute, deps: [Router]},
RouterConfigLoader,
// Only used to warn when `provideRoutes` is used without `RouterModule` or `provideRouter`. Can
// be removed when `provideRoutes` is removed.
NG_DEV_MODE ? {provide: ROUTER_IS_PROVIDED, useValue: true} : [],
];

export function routerNgProbeToken() {
Expand Down
13 changes: 11 additions & 2 deletions packages/router/test/standalone.spec.ts
Expand Up @@ -9,9 +9,8 @@
import {Component, Injectable, NgModule} from '@angular/core';
import {ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {NavigationEnd, Router, RouterModule} from '@angular/router';
import {provideRoutes, Router, RouterModule, ROUTES} from '@angular/router';
import {RouterTestingModule} from '@angular/router/testing';
import {filter, first} from 'rxjs/operators';

@Component({template: '<div>simple standalone</div>', standalone: true})
export class SimpleStandaloneComponent {
Expand Down Expand Up @@ -372,6 +371,16 @@ describe('standalone in Router API', () => {
});
});

describe('provideRoutes', () => {
it('warns if provideRoutes is used without provideRouter, RouterTestingModule, or RouterModule.forRoot',
() => {
spyOn(console, 'warn');
TestBed.configureTestingModule({providers: [provideRoutes([])]});
TestBed.inject(ROUTES);
expect(console.warn).toHaveBeenCalled();
});
});

function advance(fixture: ComponentFixture<unknown>) {
tick();
fixture.detectChanges();
Expand Down

0 comments on commit bc0fc02

Please sign in to comment.