refactor(core): Move LifeCycle functionality into ApplicationRef.

BREAKING CHANGE:

Before: constructor(@Inject(LifeCycle) lifecycle) { lifecycle.tick(); }
After: constructor(@Inject(ApplicationRef) appRef) { appRef.tick(); }

Closes #5008
This commit is contained in:
Alex Rickabaugh
2015-10-28 10:34:13 -07:00
parent ef23fe66a0
commit 72e65d6797
19 changed files with 144 additions and 182 deletions
-1
View File
@@ -12,7 +12,6 @@ export 'package:angular2/src/core/application_ref.dart'
hide ApplicationRef_, PlatformRef_;
export 'package:angular2/src/core/services.dart';
export 'package:angular2/src/core/linker.dart';
export 'package:angular2/src/core/lifecycle.dart';
export 'package:angular2/src/core/zone.dart';
export 'package:angular2/src/core/render.dart';
export 'package:angular2/src/core/directives.dart';
+1 -1
View File
@@ -12,7 +12,7 @@ export * from './src/core/application';
export * from './src/core/bootstrap';
export * from './src/core/services';
export * from './src/core/linker';
export * from './src/core/lifecycle';
export {ApplicationRef} from './src/core/application_ref';
export * from './src/core/zone';
export * from './src/core/render';
export * from './src/core/directives';
+56 -10
View File
@@ -22,7 +22,6 @@ import {
} from 'angular2/src/core/facade/exceptions';
import {DOM} from 'angular2/src/core/dom/dom_adapter';
import {internalView} from 'angular2/src/core/linker/view_ref';
import {LifeCycle, LifeCycle_} from 'angular2/src/core/life_cycle/life_cycle';
import {
IterableDiffers,
defaultIterableDiffers,
@@ -42,6 +41,8 @@ import {Compiler} from 'angular2/src/core/linker/compiler';
import {DynamicComponentLoader_} from "./linker/dynamic_component_loader";
import {AppViewManager_} from "./linker/view_manager";
import {Compiler_} from "./linker/compiler";
import {wtfLeave, wtfCreateScope, WtfScopeFn} from './profile/profile';
import {ChangeDetectorRef} from 'angular2/src/core/change_detection/change_detector_ref';
/**
* Constructs the set of providers meant for use at the platform level.
@@ -103,12 +104,7 @@ export function applicationCommonProviders(): Array<Type | Provider | any[]> {
provide(KeyValueDiffers, {useValue: defaultKeyValueDiffers}),
DirectiveResolver,
PipeResolver,
provide(DynamicComponentLoader, {useClass: DynamicComponentLoader_}),
provide(LifeCycle,
{
useFactory: (exceptionHandler) => new LifeCycle_(null, assertionsEnabled()),
deps: [ExceptionHandler]
})
provide(DynamicComponentLoader, {useClass: DynamicComponentLoader_})
];
}
@@ -333,6 +329,18 @@ export abstract class ApplicationRef {
*/
abstract dispose(): void;
/**
* Invoke this method to explicitly process change detection and its side-effects.
*
* In development mode, `tick()` also performs a second change detection cycle to ensure that no
* further changes are detected. If additional changes are picked up during this second cycle,
* bindings in the app have side-effects that cannot be resolved in a single change detection
* pass.
* In this case, Angular throws an error, since an Angular application can only have one change
* detection pass during which all change detection must complete.
*/
abstract tick(): void;
/**
* Get a list of component types registered to this application.
*/
@@ -340,13 +348,30 @@ export abstract class ApplicationRef {
}
export class ApplicationRef_ extends ApplicationRef {
/** @internal */
static _tickScope: WtfScopeFn = wtfCreateScope('ApplicationRef#tick()');
/** @internal */
private _bootstrapListeners: Function[] = [];
/** @internal */
private _disposeListeners: Function[] = [];
/** @internal */
private _rootComponents: ComponentRef[] = [];
/** @internal */
private _rootComponentTypes: Type[] = [];
/** @internal */
private _changeDetectorRefs: ChangeDetectorRef[] = [];
/** @internal */
private _runningTick: boolean = false;
/** @internal */
private _enforceNoNewChanges: boolean = false;
constructor(private _platform: PlatformRef_, private _zone: NgZone, private _injector: Injector) {
super();
if (isPresent(this._zone)) {
this._zone.overrideOnTurnDone(() => this.tick());
}
this._enforceNoNewChanges = assertionsEnabled();
}
registerBootstrapListener(listener: (ref: ComponentRef) => void): void {
@@ -355,6 +380,10 @@ export class ApplicationRef_ extends ApplicationRef {
registerDisposeListener(dispose: () => void): void { this._disposeListeners.push(dispose); }
registerChangeDetector(changeDetector: ChangeDetectorRef): void {
this._changeDetectorRefs.push(changeDetector);
}
bootstrap(componentType: Type,
providers?: Array<Type | Provider | any[]>): Promise<ComponentRef> {
var completer = PromiseWrapper.completer();
@@ -370,9 +399,8 @@ export class ApplicationRef_ extends ApplicationRef {
var compRefToken: Promise<ComponentRef> = injector.get(APP_COMPONENT_REF_PROMISE);
var tick = (componentRef) => {
var appChangeDetector = internalView(componentRef.hostView).changeDetector;
var lc = injector.get(LifeCycle);
lc.registerWith(this._zone, appChangeDetector);
lc.tick();
this._changeDetectorRefs.push(appChangeDetector.ref);
this.tick();
completer.resolve(componentRef);
this._rootComponents.push(componentRef);
this._bootstrapListeners.forEach((listener) => listener(componentRef));
@@ -395,6 +423,24 @@ export class ApplicationRef_ extends ApplicationRef {
get zone(): NgZone { return this._zone; }
tick(): void {
if (this._runningTick) {
throw new BaseException("ApplicationRef.tick is called recursively");
}
var s = ApplicationRef_._tickScope();
try {
this._runningTick = true;
this._changeDetectorRefs.forEach((detector) => detector.detectChanges());
if (this._enforceNoNewChanges) {
this._changeDetectorRefs.forEach((detector) => detector.checkNoChanges());
}
} finally {
this._runningTick = false;
wtfLeave(s);
}
}
dispose(): void {
// TODO(alxhub): Dispose of the NgZone.
this._rootComponents.forEach((ref) => ref.dispose());
@@ -126,6 +126,14 @@ export abstract class ChangeDetectorRef {
*/
abstract detectChanges(): void;
/**
* Checks the change detector and its children, and throws if any changes are detected.
*
* This is used in development mode to verify that running change detection doesn't introduce
* other changes.
*/
abstract checkNoChanges(): void;
/**
* Reattach the change detector to the change detector tree.
*
@@ -192,6 +200,7 @@ export class ChangeDetectorRef_ extends ChangeDetectorRef {
markForCheck(): void { this._cd.markPathToRootAsCheckOnce(); }
detach(): void { this._cd.mode = ChangeDetectionStrategy.Detached; }
detectChanges(): void { this._cd.detectChanges(); }
checkNoChanges(): void { this._cd.checkNoChanges(); }
reattach(): void {
this._cd.mode = ChangeDetectionStrategy.CheckAlways;
this.markForCheck();
@@ -1,97 +0,0 @@
import {Injectable} from 'angular2/src/core/di';
import {ChangeDetector} from 'angular2/src/core/change_detection/change_detection';
import {NgZone} from 'angular2/src/core/zone/ng_zone';
import {isPresent} from 'angular2/src/core/facade/lang';
import {BaseException, WrappedException} from 'angular2/src/core/facade/exceptions';
import {wtfLeave, wtfCreateScope, WtfScopeFn} from '../profile/profile';
/**
* Provides access to explicitly trigger change detection in an application.
*
* By default, `Zone` triggers change detection in Angular on each virtual machine (VM) turn. When
* testing, or in some
* limited application use cases, a developer can also trigger change detection with the
* `lifecycle.tick()` method.
*
* Each Angular application has a single `LifeCycle` instance.
*
* ### Example
*
* This is a contrived example, since the bootstrap automatically runs inside of the `Zone`, which
* invokes
* `lifecycle.tick()` on your behalf.
*
* ```javascript
* bootstrap(MyApp).then((ref:ComponentRef) => {
* var lifeCycle = ref.injector.get(LifeCycle);
* var myApp = ref.instance;
*
* ref.doSomething();
* lifecycle.tick();
* });
* ```
*/
export abstract class LifeCycle {
/**
* Invoke this method to explicitly process change detection and its side-effects.
*
* In development mode, `tick()` also performs a second change detection cycle to ensure that no
* further
* changes are detected. If additional changes are picked up during this second cycle, bindings
* in
* the app have
* side-effects that cannot be resolved in a single change detection pass. In this case, Angular
* throws an error,
* since an Angular application can only have one change detection pass during which all change
* detection must
* complete.
*
*/
abstract tick();
}
@Injectable()
export class LifeCycle_ extends LifeCycle {
/** @internal */
static _tickScope: WtfScopeFn = wtfCreateScope('LifeCycle#tick()');
/** @internal */
_changeDetectors: ChangeDetector[];
/** @internal */
_enforceNoNewChanges: boolean;
/** @internal */
_runningTick: boolean = false;
constructor(changeDetector: ChangeDetector = null, enforceNoNewChanges: boolean = false) {
super();
this._changeDetectors = [];
if (isPresent(changeDetector)) {
this._changeDetectors.push(changeDetector);
}
this._enforceNoNewChanges = enforceNoNewChanges;
}
registerWith(zone: NgZone, changeDetector: ChangeDetector = null) {
if (isPresent(changeDetector)) {
this._changeDetectors.push(changeDetector);
}
zone.overrideOnTurnDone(() => this.tick());
}
tick() {
if (this._runningTick) {
throw new BaseException("LifeCycle.tick is called recursively");
}
var s = LifeCycle_._tickScope();
try {
this._runningTick = true;
this._changeDetectors.forEach((detector) => detector.detectChanges());
if (this._enforceNoNewChanges) {
this._changeDetectors.forEach((detector) => detector.checkNoChanges());
}
} finally {
this._runningTick = false;
wtfLeave(s);
}
}
}
-2
View File
@@ -1,2 +0,0 @@
// Public API for LifeCycle
export {LifeCycle} from './life_cycle/life_cycle';
@@ -20,5 +20,7 @@ export class MockApplicationRef extends ApplicationRef {
dispose(): void {}
tick(): void {}
get componentTypes(): Type[] { return null; };
}
+6 -4
View File
@@ -1,4 +1,4 @@
import {LifeCycle} from 'angular2/angular2';
import {ApplicationRef} from 'angular2/src/core/application_ref';
import {ComponentRef, ComponentRef_} from 'angular2/src/core/linker/dynamic_component_loader';
import {isPresent, NumberWrapper} from 'angular2/src/core/facade/lang';
import {performance, window} from 'angular2/src/core/facade/browser';
@@ -19,9 +19,11 @@ export class AngularTools {
* corresponds to the `ng.profiler` in the dev console.
*/
export class AngularProfiler {
lifeCycle: LifeCycle;
appRef: ApplicationRef;
constructor(ref: ComponentRef) { this.lifeCycle = (<ComponentRef_>ref).injector.get(LifeCycle); }
constructor(ref: ComponentRef) {
this.appRef = (<ComponentRef_>ref).injector.get(ApplicationRef);
}
/**
* Exercises change detection in a loop and then prints the average amount of
@@ -50,7 +52,7 @@ export class AngularProfiler {
var start = DOM.performanceNow();
var numTicks = 0;
while (numTicks < 5 || (DOM.performanceNow() - start) < 500) {
this.lifeCycle.tick();
this.appRef.tick();
numTicks++;
}
var end = DOM.performanceNow();
@@ -0,0 +1,30 @@
import {
ddescribe,
describe,
it,
iit,
xit,
expect,
beforeEach,
afterEach,
el,
AsyncTestCompleter,
fakeAsync,
tick,
inject
} from 'angular2/testing_internal';
import {SpyChangeDetector} from './spies';
import {ApplicationRef_} from "angular2/src/core/application_ref";
import {ChangeDetectorRef_} from "angular2/src/core/change_detection/change_detector_ref";
export function main() {
describe("ApplicationRef", () => {
it("should throw when reentering tick", () => {
var cd = <any>new SpyChangeDetector();
var ref = new ApplicationRef_(null, null, null);
ref.registerChangeDetector(new ChangeDetectorRef_(cd));
cd.spy("detectChanges").andCallFake(() => ref.tick());
expect(() => ref.tick()).toThrowError("ApplicationRef.tick is called recursively");
});
});
}
@@ -17,7 +17,7 @@ import {Component, Directive, View} from 'angular2/core';
import {DOM} from 'angular2/src/core/dom/dom_adapter';
import {DOCUMENT} from 'angular2/render';
import {PromiseWrapper} from 'angular2/src/core/facade/async';
import {provide, Inject, Injector, LifeCycle} from 'angular2/core';
import {provide, Inject, Injector} from 'angular2/core';
import {ExceptionHandler} from 'angular2/src/core/facade/exceptions';
import {Testability, TestabilityRegistry} from 'angular2/src/core/testability/testability';
import {IS_DART} from '../platform';
@@ -54,9 +54,9 @@ class HelloRootCmp3 {
@Component({selector: 'hello-app'})
@View({template: ''})
class HelloRootCmp4 {
lc;
appRef;
constructor(@Inject(LifeCycle) lc) { this.lc = lc; }
constructor(@Inject(ApplicationRef) appRef) { this.appRef = appRef; }
}
@Component({selector: 'hello-app'})
@@ -179,7 +179,7 @@ export function main() {
var refPromise = bootstrap(HelloRootCmp4, testProviders);
refPromise.then((ref) => {
expect(ref.hostComponent.lc).toBe((<ComponentRef_>ref).injector.get(LifeCycle));
expect(ref.hostComponent.appRef).toBe((<ComponentRef_>ref).injector.get(ApplicationRef));
async.done();
});
}));
@@ -1,29 +0,0 @@
import {
ddescribe,
describe,
it,
iit,
xit,
expect,
beforeEach,
afterEach,
el,
AsyncTestCompleter,
fakeAsync,
tick,
inject
} from 'angular2/testing_internal';
import {SpyChangeDetector} from '../spies';
import {LifeCycle_} from "angular2/src/core/life_cycle/life_cycle";
export function main() {
describe("LifeCycle", () => {
it("should throw when reentering tick", () => {
var cd = <any>new SpyChangeDetector();
var lc = new LifeCycle_(cd, false);
cd.spy("detectChanges").andCallFake(() => lc.tick());
expect(() => lc.tick()).toThrowError("LifeCycle.tick is called recursively");
});
});
}
+1 -6
View File
@@ -102,6 +102,7 @@ var NG_API = [
'ApplicationRef.dispose()',
'ApplicationRef.registerBootstrapListener()',
'ApplicationRef.registerDisposeListener()',
'ApplicationRef.tick()',
*/
'AsyncPipe',
'AsyncPipe.onDestroy()',
@@ -610,12 +611,6 @@ var NG_API = [
'KeyValueDiffers',
'KeyValueDiffers.factories',
'KeyValueDiffers.find()',
'LifeCycle', // TODO: replace with ApplicationRef
/*
Abstract methods
'LifeCycle.registerWith()',
'LifeCycle.tick()',
*/
'LowerCasePipe',
'LowerCasePipe.transform()',
'NG_VALIDATORS',
+5 -3
View File
@@ -1,11 +1,13 @@
import 'package:angular2/testing_internal.dart' show SpyObject;
import 'package:angular2/core.dart' show LifeCycle, Injector, bind;
import 'package:angular2/core.dart' show Injector, bind;
import 'package:angular2/src/core/application_ref.dart' show ApplicationRef;
import 'package:angular2/src/core/linker/dynamic_component_loader.dart'
show ComponentRef_;
import 'dart:js';
@proxy
class SpyLifeCycle extends SpyObject implements LifeCycle {
class SpyApplicationRef extends SpyObject implements ApplicationRef {
tick() {}
noSuchMethod(m) => super.noSuchMethod(m);
}
@@ -15,7 +17,7 @@ class SpyComponentRef extends SpyObject implements ComponentRef_ {
SpyComponentRef() {
this.injector =
Injector.resolveAndCreate([bind(LifeCycle).toClass(SpyLifeCycle)]);
Injector.resolveAndCreate([bind(ApplicationRef).toClass(SpyApplicationRef)]);
}
noSuchMethod(m) => super.noSuchMethod(m);
+8 -2
View File
@@ -1,13 +1,19 @@
import {SpyObject} from 'angular2/testing_internal';
import {LifeCycle, Injector, provide} from 'angular2/angular2';
import {Injector, provide} from 'angular2/angular2';
import {ComponentRef} from 'angular2/src/core/linker/dynamic_component_loader';
import {global} from 'angular2/src/core/facade/lang';
import {ApplicationRef, ApplicationRef_} from 'angular2/src/core/application_ref';
export class SpyApplicationRef extends SpyObject {
constructor() { super(ApplicationRef_); }
}
export class SpyComponentRef extends SpyObject {
injector;
constructor() {
super();
this.injector = Injector.resolveAndCreate([provide(LifeCycle, {useValue: {tick: () => {}}})]);
this.injector =
Injector.resolveAndCreate([provide(ApplicationRef, {useClass: SpyApplicationRef})]);
}
}
-1
View File
@@ -10,7 +10,6 @@ export * from '../src/core/facade';
export * from '../src/core/application_ref';
export * from '../src/core/services';
export * from '../src/core/linker';
export * from '../src/core/lifecycle';
export * from '../src/core/zone';
// Do not export render in web_worker
// export * from '../src/core/render';