Skip to content

Commit 23a96dc

Browse files
trotylmhevery
authored andcommittedSep 5, 2018
feat(router): warn if navigation triggered outside Angular zone (#24959)
closes #15770, closes #15946, closes #24728 PR Close #24959
1 parent 6f7df8a commit 23a96dc

File tree

3 files changed

+61
-4
lines changed

3 files changed

+61
-4
lines changed
 

‎packages/core/src/zone.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@
77
*/
88

99
// Public API for Zone
10-
export {NgZone} from './zone/ng_zone';
10+
export {NgZone, NoopNgZone as ɵNoopNgZone} from './zone/ng_zone';

‎packages/router/src/router.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
import {Location} from '@angular/common';
10-
import {Compiler, Injector, NgModuleFactoryLoader, NgModuleRef, Type, isDevMode} from '@angular/core';
10+
import {Compiler, Injector, NgModuleFactoryLoader, NgModuleRef, NgZone, Optional, Type, isDevMode, ɵConsole as Console} from '@angular/core';
1111
import {BehaviorSubject, Observable, Subject, Subscription, of } from 'rxjs';
1212
import {concatMap, map, mergeMap} from 'rxjs/operators';
1313

@@ -224,6 +224,8 @@ export class Router {
224224
private navigationId: number = 0;
225225
private configLoader: RouterConfigLoader;
226226
private ngModule: NgModuleRef<any>;
227+
private console: Console;
228+
private isNgZoneEnabled: boolean = false;
227229

228230
public readonly events: Observable<Event> = new Subject<Event>();
229231
public readonly routerState: RouterState;
@@ -314,6 +316,9 @@ export class Router {
314316
const onLoadEnd = (r: Route) => this.triggerEvent(new RouteConfigLoadEnd(r));
315317

316318
this.ngModule = injector.get(NgModuleRef);
319+
this.console = injector.get(Console);
320+
const ngZone = injector.get(NgZone);
321+
this.isNgZoneEnabled = ngZone instanceof NgZone;
317322

318323
this.resetConfig(config);
319324
this.currentUrlTree = createEmptyUrlTree();
@@ -495,6 +500,11 @@ export class Router {
495500
*/
496501
navigateByUrl(url: string|UrlTree, extras: NavigationExtras = {skipLocationChange: false}):
497502
Promise<boolean> {
503+
if (isDevMode() && this.isNgZoneEnabled && !NgZone.isInAngularZone()) {
504+
this.console.warn(
505+
`Navigation triggered outside Angular zone, did you forget to call 'ngZone.run()'?`);
506+
}
507+
498508
const urlTree = url instanceof UrlTree ? url : this.parseUrl(url);
499509
const mergedTree = this.urlHandlingStrategy.merge(urlTree, this.rawUrlTree);
500510

‎packages/router/test/integration.spec.ts

+49-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import {CommonModule, Location} from '@angular/common';
1010
import {SpyLocation} from '@angular/common/testing';
11-
import {ChangeDetectionStrategy, Component, Injectable, NgModule, NgModuleFactoryLoader, NgModuleRef} from '@angular/core';
11+
import {ChangeDetectionStrategy, Component, Injectable, NgModule, NgModuleFactoryLoader, NgModuleRef, NgZone, ɵConsole as Console, ɵNoopNgZone as NoopNgZone} from '@angular/core';
1212
import {ComponentFixture, TestBed, fakeAsync, inject, tick} from '@angular/core/testing';
1313
import {By} from '@angular/platform-browser/src/dom/debug/by';
1414
import {expect} from '@angular/platform-browser/testing/src/matchers';
@@ -20,10 +20,13 @@ import {forEach} from '../src/utils/collection';
2020
import {RouterTestingModule, SpyNgModuleFactoryLoader} from '../testing';
2121

2222
describe('Integration', () => {
23+
const noopConsole: Console = {log() {}, warn() {}};
24+
2325
beforeEach(() => {
2426
TestBed.configureTestingModule({
2527
imports:
26-
[RouterTestingModule.withRoutes([{path: 'simple', component: SimpleCmp}]), TestModule]
28+
[RouterTestingModule.withRoutes([{path: 'simple', component: SimpleCmp}]), TestModule],
29+
providers: [{provide: Console, useValue: noopConsole}]
2730
});
2831
});
2932

@@ -154,6 +157,50 @@ describe('Integration', () => {
154157
})));
155158
});
156159

160+
describe('navigation warning', () => {
161+
let warnings: string[] = [];
162+
163+
class MockConsole {
164+
warn(message: string) { warnings.push(message); }
165+
}
166+
167+
beforeEach(() => {
168+
warnings = [];
169+
TestBed.overrideProvider(Console, {useValue: new MockConsole()});
170+
});
171+
172+
describe('with NgZone enabled', () => {
173+
it('should warn when triggered outside Angular zone',
174+
fakeAsync(inject([Router, NgZone], (router: Router, ngZone: NgZone) => {
175+
ngZone.runOutsideAngular(() => { router.navigateByUrl('/simple'); });
176+
177+
expect(warnings.length).toBe(1);
178+
expect(warnings[0])
179+
.toBe(
180+
`Navigation triggered outside Angular zone, did you forget to call 'ngZone.run()'?`);
181+
})));
182+
183+
it('should not warn when triggered inside Angular zone',
184+
fakeAsync(inject([Router, NgZone], (router: Router, ngZone: NgZone) => {
185+
ngZone.run(() => { router.navigateByUrl('/simple'); });
186+
187+
expect(warnings.length).toBe(0);
188+
})));
189+
});
190+
191+
describe('with NgZone disabled', () => {
192+
beforeEach(() => { TestBed.overrideProvider(NgZone, {useValue: new NoopNgZone()}); });
193+
194+
it('should not warn when triggered outside Angular zone',
195+
fakeAsync(inject([Router, NgZone], (router: Router, ngZone: NgZone) => {
196+
197+
ngZone.runOutsideAngular(() => { router.navigateByUrl('/simple'); });
198+
199+
expect(warnings.length).toBe(0);
200+
})));
201+
});
202+
});
203+
157204
describe('should execute navigations serially', () => {
158205
let log: any[] = [];
159206

0 commit comments

Comments
 (0)
Please sign in to comment.