Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
} from './exceptions';
import {BindingTarget} from './binding_record';
import {Locals} from './parser/locals';
import {ChangeDetectionStrategy} from './constants';
import {ChangeDetectionStrategy, ChangeDetectorState} from './constants';
import {wtfCreateScope, wtfLeave, WtfScopeFn} from '../profile/profile';
import {isObservable} from './observable_facade';

Expand All @@ -33,7 +33,7 @@ export class AbstractChangeDetector<T> implements ChangeDetector {

// The names of the below fields must be kept in sync with codegen_name_util.ts or
// change detection will fail.
alreadyChecked: any = false;
state: ChangeDetectorState = ChangeDetectorState.NeverChecked;
context: T;
locals: Locals = null;
mode: ChangeDetectionStrategy = null;
Expand Down Expand Up @@ -80,7 +80,7 @@ export class AbstractChangeDetector<T> implements ChangeDetector {

runDetectChanges(throwOnChange: boolean): void {
if (this.mode === ChangeDetectionStrategy.Detached ||
this.mode === ChangeDetectionStrategy.Checked)
this.mode === ChangeDetectionStrategy.Checked || this.state === ChangeDetectorState.Errored)
return;
var s = _scope_check(this.id, throwOnChange);

Expand All @@ -95,7 +95,7 @@ export class AbstractChangeDetector<T> implements ChangeDetector {
if (this.mode === ChangeDetectionStrategy.CheckOnce)
this.mode = ChangeDetectionStrategy.Checked;

this.alreadyChecked = true;
this.state = ChangeDetectorState.CheckedBefore;
wtfLeave(s);
}

Expand All @@ -112,6 +112,10 @@ export class AbstractChangeDetector<T> implements ChangeDetector {
try {
this.detectChangesInRecordsInternal(throwOnChange);
} catch (e) {
// throwOnChange errors aren't counted as fatal errors.
if (!(e instanceof ExpressionChangedAfterItHasBeenCheckedException)) {
this.state = ChangeDetectorState.Errored;
}
this._throwError(e, e.stack);
}
}
Expand All @@ -137,7 +141,7 @@ export class AbstractChangeDetector<T> implements ChangeDetector {
this.locals = locals;
this.pipes = pipes;
this.hydrateDirectives(directives);
this.alreadyChecked = false;
this.state = ChangeDetectorState.NeverChecked;
}

// Subclasses should override this method to hydrate any directives.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ library change_detection.change_detection_jit_generator;
class ChangeDetectorJITGenerator {
String typeName;
ChangeDetectorJITGenerator(
definition, changeDetectionUtilVarName, abstractChangeDetectorVarName) {}
definition, changeDetectionUtilVarName, abstractChangeDetectorVarName, changeDetectorStateVarName) {}

generate() {
throw "Jit Change Detection is not supported in Dart";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {codify} from './codegen_facade';
import {EventBinding} from './event_binding';
import {BindingTarget} from './binding_record';
import {ChangeDetectorGenConfig, ChangeDetectorDefinition} from './interfaces';
import {ChangeDetectionStrategy} from './constants';
import {ChangeDetectionStrategy, ChangeDetectorState} from './constants';
import {createPropertyRecords, createEventRecords} from './proto_change_detector';

/**
Expand Down Expand Up @@ -41,7 +41,8 @@ export class ChangeDetectorJITGenerator {
typeName: string;

constructor(definition: ChangeDetectorDefinition, private changeDetectionUtilVarName: string,
private abstractChangeDetectorVarName: string) {
private abstractChangeDetectorVarName: string,
private changeDetectorStateVarName: string) {
var propertyBindingRecords = createPropertyRecords(definition);
var eventBindingRecords = createEventRecords(definition);
var propertyBindingTargets = definition.bindingRecords.map(b => b.target);
Expand All @@ -55,8 +56,9 @@ export class ChangeDetectorJITGenerator {
this.directiveRecords = definition.directiveRecords;
this._names = new CodegenNameUtil(this.records, this.eventBindings, this.directiveRecords,
this.changeDetectionUtilVarName);
this._logic = new CodegenLogicUtil(this._names, this.changeDetectionUtilVarName,
this.changeDetectionStrategy);
this._logic =
new CodegenLogicUtil(this._names, this.changeDetectionUtilVarName,
this.changeDetectorStateVarName, this.changeDetectionStrategy);
this.typeName = sanitizeName(`ChangeDetector_${this.id}`);
}

Expand All @@ -68,7 +70,8 @@ export class ChangeDetectorJITGenerator {
}
`;
return new Function(this.abstractChangeDetectorVarName, this.changeDetectionUtilVarName,
factorySource)(AbstractChangeDetector, ChangeDetectionUtil);
this.changeDetectorStateVarName, factorySource)(
AbstractChangeDetector, ChangeDetectionUtil, ChangeDetectorState);
}

generateSource(): string {
Expand Down Expand Up @@ -423,7 +426,7 @@ export class ChangeDetectorJITGenerator {
/** @internal */
_genOnInit(r: ProtoRecord): string {
var br = r.bindingRecord;
return `if (!throwOnChange && !${this._names.getAlreadyCheckedName()}) ${this._names.getDirectiveName(br.directiveRecord.directiveIndex)}.onInit();`;
return `if (!throwOnChange && ${this._names.getStateName()} === ${this.changeDetectorStateVarName}.NeverChecked) ${this._names.getDirectiveName(br.directiveRecord.directiveIndex)}.onInit();`;
}

/** @internal */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import {BindingTarget} from './binding_record';
import {DirectiveRecord} from './directive_record';
import {ChangeDetectionStrategy} from './constants';
import {BaseException} from 'angular2/src/core/facade/exceptions';
import {IS_DART} from 'angular2/src/core/compiler/util';

/**
* Class responsible for providing change detection logic for change detector classes.
*/
export class CodegenLogicUtil {
constructor(private _names: CodegenNameUtil, private _utilName: string,
private _changeDetectorStateName: string,
private _changeDetection: ChangeDetectionStrategy) {}

/**
Expand Down Expand Up @@ -182,12 +184,13 @@ export class CodegenLogicUtil {

genContentLifecycleCallbacks(directiveRecords: DirectiveRecord[]): string[] {
var res = [];
var eq = IS_DART ? '==' : '===';
// NOTE(kegluneq): Order is important!
for (var i = directiveRecords.length - 1; i >= 0; --i) {
var dir = directiveRecords[i];
if (dir.callAfterContentInit) {
res.push(
`if(! ${this._names.getAlreadyCheckedName()}) ${this._names.getDirectiveName(dir.directiveIndex)}.afterContentInit();`);
`if(${this._names.getStateName()} ${eq} ${this._changeDetectorStateName}.NeverChecked) ${this._names.getDirectiveName(dir.directiveIndex)}.afterContentInit();`);
}

if (dir.callAfterContentChecked) {
Expand All @@ -199,12 +202,13 @@ export class CodegenLogicUtil {

genViewLifecycleCallbacks(directiveRecords: DirectiveRecord[]): string[] {
var res = [];
var eq = IS_DART ? '==' : '===';
// NOTE(kegluneq): Order is important!
for (var i = directiveRecords.length - 1; i >= 0; --i) {
var dir = directiveRecords[i];
if (dir.callAfterViewInit) {
res.push(
`if(! ${this._names.getAlreadyCheckedName()}) ${this._names.getDirectiveName(dir.directiveIndex)}.afterViewInit();`);
`if(${this._names.getStateName()} ${eq} ${this._changeDetectorStateName}.NeverChecked) ${this._names.getDirectiveName(dir.directiveIndex)}.afterViewInit();`);
}

if (dir.callAfterViewChecked) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {EventBinding} from './event_binding';

// The names of these fields must be kept in sync with abstract_change_detector.ts or change
// detection will fail.
const _ALREADY_CHECKED_ACCESSOR = "alreadyChecked";
const _STATE_ACCESSOR = "state";
const _CONTEXT_ACCESSOR = "context";
const _PROP_BINDING_INDEX = "propertyBindingIndex";
const _DIRECTIVES_ACCESSOR = "directiveIndices";
const _DISPATCHER_ACCESSOR = "dispatcher";
Expand Down Expand Up @@ -77,7 +78,7 @@ export class CodegenNameUtil {

getLocalsAccessorName(): string { return this._addFieldPrefix(_LOCALS_ACCESSOR); }

getAlreadyCheckedName(): string { return this._addFieldPrefix(_ALREADY_CHECKED_ACCESSOR); }
getStateName(): string { return this._addFieldPrefix(_STATE_ACCESSOR); }

getModeName(): string { return this._addFieldPrefix(_MODE_ACCESSOR); }

Expand Down
27 changes: 27 additions & 0 deletions modules/angular2/src/core/change_detection/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
import {StringWrapper, normalizeBool, isBlank} from 'angular2/src/core/facade/lang';

export enum ChangeDetectorState {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Document the values

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current version has the values documented.

/**
* `NeverChecked` means that the change detector has not been checked yet, and
* initialization methods should be called during detection.
*/
NeverChecked,

/**
* `CheckedBefore` means that the change detector has successfully completed at least
* one detection previously.
*/
CheckedBefore,

/**
* `Errored` means that the change detector encountered an error checking a binding
* or calling a directive lifecycle method and is now in an inconsistent state. Change
* detectors in this state will no longer detect changes.
*/
Errored
}

export enum ChangeDetectionStrategy {
/**
* `CheckedOnce` means that after calling detectChanges the mode of the change detector
Expand Down Expand Up @@ -51,6 +72,12 @@ export var CHANGE_DETECTION_STRATEGY_VALUES = [
ChangeDetectionStrategy.OnPushObserve
];

export var CHANGE_DETECTOR_STATE_VALUES = [
ChangeDetectorState.NeverChecked,
ChangeDetectorState.CheckedBefore,
ChangeDetectorState.Errored
];

export function isDefaultChangeDetectionStrategy(
changeDetectionStrategy: ChangeDetectionStrategy): boolean {
return isBlank(changeDetectionStrategy) ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {DirectiveRecord, DirectiveIndex} from './directive_record';
import {Locals} from './parser/locals';
import {ChangeDetectorGenConfig} from './interfaces';
import {ChangeDetectionUtil, SimpleChange} from './change_detection_util';
import {ChangeDetectionStrategy} from './constants';
import {ChangeDetectionStrategy, ChangeDetectorState} from './constants';
import {ProtoRecord, RecordType} from './proto_record';

export class DynamicChangeDetector extends AbstractChangeDetector<any> {
Expand Down Expand Up @@ -134,7 +134,8 @@ export class DynamicChangeDetector extends AbstractChangeDetector<any> {
if (proto.isLifeCycleRecord()) {
if (proto.name === "DoCheck" && !throwOnChange) {
this._getDirectiveFor(directiveRecord.directiveIndex).doCheck();
} else if (proto.name === "OnInit" && !throwOnChange && !this.alreadyChecked) {
} else if (proto.name === "OnInit" && !throwOnChange &&
this.state == ChangeDetectorState.NeverChecked) {
this._getDirectiveFor(directiveRecord.directiveIndex).onInit();
} else if (proto.name === "OnChanges" && isPresent(changes) && !throwOnChange) {
this._getDirectiveFor(directiveRecord.directiveIndex).onChanges(changes);
Expand Down Expand Up @@ -170,7 +171,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector<any> {
var dirs = this._directiveRecords;
for (var i = dirs.length - 1; i >= 0; --i) {
var dir = dirs[i];
if (dir.callAfterContentInit && !this.alreadyChecked) {
if (dir.callAfterContentInit && this.state == ChangeDetectorState.NeverChecked) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

=== instead of ==

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

this._getDirectiveFor(dir.directiveIndex).afterContentInit();
}

Expand All @@ -184,7 +185,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector<any> {
var dirs = this._directiveRecords;
for (var i = dirs.length - 1; i >= 0; --i) {
var dir = dirs[i];
if (dir.callAfterViewInit && !this.alreadyChecked) {
if (dir.callAfterViewInit && this.state == ChangeDetectorState.NeverChecked) {
this._getDirectiveFor(dir.directiveIndex).afterViewInit();
}
if (dir.callAfterViewChecked) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export class JitProtoChangeDetector implements ProtoChangeDetector {

/** @internal */
_createFactory(definition: ChangeDetectorDefinition) {
return new ChangeDetectorJITGenerator(definition, 'util', 'AbstractChangeDetector').generate();
return new ChangeDetectorJITGenerator(definition, 'util', 'AbstractChangeDetector',
'ChangeDetectorStatus')
.generate();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export 'package:angular2/src/core/change_detection/abstract_change_detector.dart
show AbstractChangeDetector;
export 'package:angular2/src/core/change_detection/change_detection.dart'
show ChangeDetectionStrategy;
export 'package:angular2/src/core/change_detection/constants.dart'
show ChangeDetectorState;
export 'package:angular2/src/core/change_detection/directive_record.dart'
show DirectiveIndex, DirectiveRecord;
export 'package:angular2/src/core/change_detection/interfaces.dart'
Expand Down
10 changes: 8 additions & 2 deletions modules/angular2/src/core/compiler/change_detector_compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,16 @@ import {Injectable} from 'angular2/src/core/di';

const ABSTRACT_CHANGE_DETECTOR = "AbstractChangeDetector";
const UTIL = "ChangeDetectionUtil";
const CHANGE_DETECTOR_STATE = "ChangeDetectorState";

var ABSTRACT_CHANGE_DETECTOR_MODULE = moduleRef(
`package:angular2/src/core/change_detection/abstract_change_detector${MODULE_SUFFIX}`);
var UTIL_MODULE =
moduleRef(`package:angular2/src/core/change_detection/change_detection_util${MODULE_SUFFIX}`);
var PREGEN_PROTO_CHANGE_DETECTOR_MODULE = moduleRef(
`package:angular2/src/core/change_detection/pregen_proto_change_detector${MODULE_SUFFIX}`);
var CONSTANTS_MODULE =
moduleRef(`package:angular2/src/core/change_detection/constants${MODULE_SUFFIX}`);

@Injectable()
export class ChangeDetectionCompiler {
Expand All @@ -46,7 +49,9 @@ export class ChangeDetectionCompiler {
var proto = new DynamicProtoChangeDetector(definition);
return (dispatcher) => proto.instantiate(dispatcher);
} else {
return new ChangeDetectorJITGenerator(definition, UTIL, ABSTRACT_CHANGE_DETECTOR).generate();
return new ChangeDetectorJITGenerator(definition, UTIL, ABSTRACT_CHANGE_DETECTOR,
CHANGE_DETECTOR_STATE)
.generate();
}
}

Expand Down Expand Up @@ -74,7 +79,8 @@ export class ChangeDetectionCompiler {
} else {
codegen = new ChangeDetectorJITGenerator(
definition, `${UTIL_MODULE}${UTIL}`,
`${ABSTRACT_CHANGE_DETECTOR_MODULE}${ABSTRACT_CHANGE_DETECTOR}`);
`${ABSTRACT_CHANGE_DETECTOR_MODULE}${ABSTRACT_CHANGE_DETECTOR}`,
`${CONSTANTS_MODULE}${CHANGE_DETECTOR_STATE}`);
factories.push(`function(dispatcher) { return new ${codegen.typeName}(dispatcher); }`);
sourcePart = codegen.generateSource();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -431,10 +431,12 @@ export function main() {
describe('updating directives', () => {
var directive1;
var directive2;
var directive3;

beforeEach(() => {
directive1 = new TestDirective();
directive2 = new TestDirective();
directive3 = new TestDirective(null, null, true);
});

it('should happen directly, without invoking the dispatcher', () => {
Expand Down Expand Up @@ -510,6 +512,30 @@ export function main() {

expect(directive1.onInitCalled).toBe(false);
});

it('should not call onInit again if it throws', () => {
var cd = _createWithoutHydrate('directiveOnInit').changeDetector;

cd.hydrate(_DEFAULT_CONTEXT, null, new FakeDirectives([directive3], []), null);
var errored = false;
// First pass fails, but onInit should be called.
try {
cd.detectChanges();
} catch (e) {
errored = true;
}
expect(errored).toBe(true);
expect(directive3.onInitCalled).toBe(true);
directive3.onInitCalled = false;

// Second change detection also fails, but this time onInit should not be called.
try {
cd.detectChanges();
} catch (e) {
throw new BaseException("Second detectChanges() should not have run detection.");
}
expect(directive3.onInitCalled).toBe(false);
});
});

describe('afterContentInit', () => {
Expand Down Expand Up @@ -1336,13 +1362,19 @@ class TestDirective {
afterViewCheckedCalled = false;
event;

constructor(public afterContentCheckedSpy = null, public afterViewCheckedSpy = null) {}
constructor(public afterContentCheckedSpy = null, public afterViewCheckedSpy = null,
public throwOnInit = false) {}

onEvent(event) { this.event = event; }

doCheck() { this.doCheckCalled = true; }

onInit() { this.onInitCalled = true; }
onInit() {
this.onInitCalled = true;
if (this.throwOnInit) {
throw "simulated onInit failure";
}
}

onChanges(changes) {
var r = {};
Expand Down
Loading