fix(core): Add an error state for ChangeDetectors that is set when bindings or lifecycle events throw exceptions and prevents further detection.
- Changes the `alreadyChecked` flag of AbstractChangeDetector to a new `state` flag. - Changes all checks of alreadyChecked to check that the state is NeverChecked. - Set state to Errored if an error is thrown during detection. - Skip change detection for a detector and its children when the state is Errored. - Add a test to validate this fixes issue #4323. Closes #4953
This commit is contained in:
parent
c930a533d1
commit
d1b54d6807
@ -13,7 +13,7 @@ import {
|
|||||||
} from './exceptions';
|
} from './exceptions';
|
||||||
import {BindingTarget} from './binding_record';
|
import {BindingTarget} from './binding_record';
|
||||||
import {Locals} from './parser/locals';
|
import {Locals} from './parser/locals';
|
||||||
import {ChangeDetectionStrategy} from './constants';
|
import {ChangeDetectionStrategy, ChangeDetectorState} from './constants';
|
||||||
import {wtfCreateScope, wtfLeave, WtfScopeFn} from '../profile/profile';
|
import {wtfCreateScope, wtfLeave, WtfScopeFn} from '../profile/profile';
|
||||||
import {isObservable} from './observable_facade';
|
import {isObservable} from './observable_facade';
|
||||||
|
|
||||||
@ -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
|
// The names of the below fields must be kept in sync with codegen_name_util.ts or
|
||||||
// change detection will fail.
|
// change detection will fail.
|
||||||
alreadyChecked: any = false;
|
state: ChangeDetectorState = ChangeDetectorState.NeverChecked;
|
||||||
context: T;
|
context: T;
|
||||||
locals: Locals = null;
|
locals: Locals = null;
|
||||||
mode: ChangeDetectionStrategy = null;
|
mode: ChangeDetectionStrategy = null;
|
||||||
@ -80,7 +80,7 @@ export class AbstractChangeDetector<T> implements ChangeDetector {
|
|||||||
|
|
||||||
runDetectChanges(throwOnChange: boolean): void {
|
runDetectChanges(throwOnChange: boolean): void {
|
||||||
if (this.mode === ChangeDetectionStrategy.Detached ||
|
if (this.mode === ChangeDetectionStrategy.Detached ||
|
||||||
this.mode === ChangeDetectionStrategy.Checked)
|
this.mode === ChangeDetectionStrategy.Checked || this.state === ChangeDetectorState.Errored)
|
||||||
return;
|
return;
|
||||||
var s = _scope_check(this.id, throwOnChange);
|
var s = _scope_check(this.id, throwOnChange);
|
||||||
|
|
||||||
@ -95,7 +95,7 @@ export class AbstractChangeDetector<T> implements ChangeDetector {
|
|||||||
if (this.mode === ChangeDetectionStrategy.CheckOnce)
|
if (this.mode === ChangeDetectionStrategy.CheckOnce)
|
||||||
this.mode = ChangeDetectionStrategy.Checked;
|
this.mode = ChangeDetectionStrategy.Checked;
|
||||||
|
|
||||||
this.alreadyChecked = true;
|
this.state = ChangeDetectorState.CheckedBefore;
|
||||||
wtfLeave(s);
|
wtfLeave(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,6 +112,10 @@ export class AbstractChangeDetector<T> implements ChangeDetector {
|
|||||||
try {
|
try {
|
||||||
this.detectChangesInRecordsInternal(throwOnChange);
|
this.detectChangesInRecordsInternal(throwOnChange);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
// throwOnChange errors aren't counted as fatal errors.
|
||||||
|
if (!(e instanceof ExpressionChangedAfterItHasBeenCheckedException)) {
|
||||||
|
this.state = ChangeDetectorState.Errored;
|
||||||
|
}
|
||||||
this._throwError(e, e.stack);
|
this._throwError(e, e.stack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -137,7 +141,7 @@ export class AbstractChangeDetector<T> implements ChangeDetector {
|
|||||||
this.locals = locals;
|
this.locals = locals;
|
||||||
this.pipes = pipes;
|
this.pipes = pipes;
|
||||||
this.hydrateDirectives(directives);
|
this.hydrateDirectives(directives);
|
||||||
this.alreadyChecked = false;
|
this.state = ChangeDetectorState.NeverChecked;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subclasses should override this method to hydrate any directives.
|
// Subclasses should override this method to hydrate any directives.
|
||||||
|
@ -9,7 +9,7 @@ library change_detection.change_detection_jit_generator;
|
|||||||
class ChangeDetectorJITGenerator {
|
class ChangeDetectorJITGenerator {
|
||||||
String typeName;
|
String typeName;
|
||||||
ChangeDetectorJITGenerator(
|
ChangeDetectorJITGenerator(
|
||||||
definition, changeDetectionUtilVarName, abstractChangeDetectorVarName) {}
|
definition, changeDetectionUtilVarName, abstractChangeDetectorVarName, changeDetectorStateVarName) {}
|
||||||
|
|
||||||
generate() {
|
generate() {
|
||||||
throw "Jit Change Detection is not supported in Dart";
|
throw "Jit Change Detection is not supported in Dart";
|
||||||
|
@ -13,7 +13,7 @@ import {codify} from './codegen_facade';
|
|||||||
import {EventBinding} from './event_binding';
|
import {EventBinding} from './event_binding';
|
||||||
import {BindingTarget} from './binding_record';
|
import {BindingTarget} from './binding_record';
|
||||||
import {ChangeDetectorGenConfig, ChangeDetectorDefinition} from './interfaces';
|
import {ChangeDetectorGenConfig, ChangeDetectorDefinition} from './interfaces';
|
||||||
import {ChangeDetectionStrategy} from './constants';
|
import {ChangeDetectionStrategy, ChangeDetectorState} from './constants';
|
||||||
import {createPropertyRecords, createEventRecords} from './proto_change_detector';
|
import {createPropertyRecords, createEventRecords} from './proto_change_detector';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -41,7 +41,8 @@ export class ChangeDetectorJITGenerator {
|
|||||||
typeName: string;
|
typeName: string;
|
||||||
|
|
||||||
constructor(definition: ChangeDetectorDefinition, private changeDetectionUtilVarName: string,
|
constructor(definition: ChangeDetectorDefinition, private changeDetectionUtilVarName: string,
|
||||||
private abstractChangeDetectorVarName: string) {
|
private abstractChangeDetectorVarName: string,
|
||||||
|
private changeDetectorStateVarName: string) {
|
||||||
var propertyBindingRecords = createPropertyRecords(definition);
|
var propertyBindingRecords = createPropertyRecords(definition);
|
||||||
var eventBindingRecords = createEventRecords(definition);
|
var eventBindingRecords = createEventRecords(definition);
|
||||||
var propertyBindingTargets = definition.bindingRecords.map(b => b.target);
|
var propertyBindingTargets = definition.bindingRecords.map(b => b.target);
|
||||||
@ -55,8 +56,9 @@ export class ChangeDetectorJITGenerator {
|
|||||||
this.directiveRecords = definition.directiveRecords;
|
this.directiveRecords = definition.directiveRecords;
|
||||||
this._names = new CodegenNameUtil(this.records, this.eventBindings, this.directiveRecords,
|
this._names = new CodegenNameUtil(this.records, this.eventBindings, this.directiveRecords,
|
||||||
this.changeDetectionUtilVarName);
|
this.changeDetectionUtilVarName);
|
||||||
this._logic = new CodegenLogicUtil(this._names, this.changeDetectionUtilVarName,
|
this._logic =
|
||||||
this.changeDetectionStrategy);
|
new CodegenLogicUtil(this._names, this.changeDetectionUtilVarName,
|
||||||
|
this.changeDetectorStateVarName, this.changeDetectionStrategy);
|
||||||
this.typeName = sanitizeName(`ChangeDetector_${this.id}`);
|
this.typeName = sanitizeName(`ChangeDetector_${this.id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,7 +70,8 @@ export class ChangeDetectorJITGenerator {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
return new Function(this.abstractChangeDetectorVarName, this.changeDetectionUtilVarName,
|
return new Function(this.abstractChangeDetectorVarName, this.changeDetectionUtilVarName,
|
||||||
factorySource)(AbstractChangeDetector, ChangeDetectionUtil);
|
this.changeDetectorStateVarName, factorySource)(
|
||||||
|
AbstractChangeDetector, ChangeDetectionUtil, ChangeDetectorState);
|
||||||
}
|
}
|
||||||
|
|
||||||
generateSource(): string {
|
generateSource(): string {
|
||||||
@ -423,7 +426,7 @@ export class ChangeDetectorJITGenerator {
|
|||||||
/** @internal */
|
/** @internal */
|
||||||
_genOnInit(r: ProtoRecord): string {
|
_genOnInit(r: ProtoRecord): string {
|
||||||
var br = r.bindingRecord;
|
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 */
|
/** @internal */
|
||||||
|
@ -6,12 +6,14 @@ import {BindingTarget} from './binding_record';
|
|||||||
import {DirectiveRecord} from './directive_record';
|
import {DirectiveRecord} from './directive_record';
|
||||||
import {ChangeDetectionStrategy} from './constants';
|
import {ChangeDetectionStrategy} from './constants';
|
||||||
import {BaseException} from 'angular2/src/core/facade/exceptions';
|
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.
|
* Class responsible for providing change detection logic for change detector classes.
|
||||||
*/
|
*/
|
||||||
export class CodegenLogicUtil {
|
export class CodegenLogicUtil {
|
||||||
constructor(private _names: CodegenNameUtil, private _utilName: string,
|
constructor(private _names: CodegenNameUtil, private _utilName: string,
|
||||||
|
private _changeDetectorStateName: string,
|
||||||
private _changeDetection: ChangeDetectionStrategy) {}
|
private _changeDetection: ChangeDetectionStrategy) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -182,12 +184,13 @@ export class CodegenLogicUtil {
|
|||||||
|
|
||||||
genContentLifecycleCallbacks(directiveRecords: DirectiveRecord[]): string[] {
|
genContentLifecycleCallbacks(directiveRecords: DirectiveRecord[]): string[] {
|
||||||
var res = [];
|
var res = [];
|
||||||
|
var eq = IS_DART ? '==' : '===';
|
||||||
// NOTE(kegluneq): Order is important!
|
// NOTE(kegluneq): Order is important!
|
||||||
for (var i = directiveRecords.length - 1; i >= 0; --i) {
|
for (var i = directiveRecords.length - 1; i >= 0; --i) {
|
||||||
var dir = directiveRecords[i];
|
var dir = directiveRecords[i];
|
||||||
if (dir.callAfterContentInit) {
|
if (dir.callAfterContentInit) {
|
||||||
res.push(
|
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) {
|
if (dir.callAfterContentChecked) {
|
||||||
@ -199,12 +202,13 @@ export class CodegenLogicUtil {
|
|||||||
|
|
||||||
genViewLifecycleCallbacks(directiveRecords: DirectiveRecord[]): string[] {
|
genViewLifecycleCallbacks(directiveRecords: DirectiveRecord[]): string[] {
|
||||||
var res = [];
|
var res = [];
|
||||||
|
var eq = IS_DART ? '==' : '===';
|
||||||
// NOTE(kegluneq): Order is important!
|
// NOTE(kegluneq): Order is important!
|
||||||
for (var i = directiveRecords.length - 1; i >= 0; --i) {
|
for (var i = directiveRecords.length - 1; i >= 0; --i) {
|
||||||
var dir = directiveRecords[i];
|
var dir = directiveRecords[i];
|
||||||
if (dir.callAfterViewInit) {
|
if (dir.callAfterViewInit) {
|
||||||
res.push(
|
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) {
|
if (dir.callAfterViewChecked) {
|
||||||
|
@ -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
|
// The names of these fields must be kept in sync with abstract_change_detector.ts or change
|
||||||
// detection will fail.
|
// detection will fail.
|
||||||
const _ALREADY_CHECKED_ACCESSOR = "alreadyChecked";
|
const _STATE_ACCESSOR = "state";
|
||||||
|
const _CONTEXT_ACCESSOR = "context";
|
||||||
const _PROP_BINDING_INDEX = "propertyBindingIndex";
|
const _PROP_BINDING_INDEX = "propertyBindingIndex";
|
||||||
const _DIRECTIVES_ACCESSOR = "directiveIndices";
|
const _DIRECTIVES_ACCESSOR = "directiveIndices";
|
||||||
const _DISPATCHER_ACCESSOR = "dispatcher";
|
const _DISPATCHER_ACCESSOR = "dispatcher";
|
||||||
@ -77,7 +78,7 @@ export class CodegenNameUtil {
|
|||||||
|
|
||||||
getLocalsAccessorName(): string { return this._addFieldPrefix(_LOCALS_ACCESSOR); }
|
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); }
|
getModeName(): string { return this._addFieldPrefix(_MODE_ACCESSOR); }
|
||||||
|
|
||||||
|
@ -1,5 +1,26 @@
|
|||||||
import {StringWrapper, normalizeBool, isBlank} from 'angular2/src/core/facade/lang';
|
import {StringWrapper, normalizeBool, isBlank} from 'angular2/src/core/facade/lang';
|
||||||
|
|
||||||
|
export enum ChangeDetectorState {
|
||||||
|
/**
|
||||||
|
* `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 {
|
export enum ChangeDetectionStrategy {
|
||||||
/**
|
/**
|
||||||
* `CheckedOnce` means that after calling detectChanges the mode of the change detector
|
* `CheckedOnce` means that after calling detectChanges the mode of the change detector
|
||||||
@ -51,6 +72,12 @@ export var CHANGE_DETECTION_STRATEGY_VALUES = [
|
|||||||
ChangeDetectionStrategy.OnPushObserve
|
ChangeDetectionStrategy.OnPushObserve
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export var CHANGE_DETECTOR_STATE_VALUES = [
|
||||||
|
ChangeDetectorState.NeverChecked,
|
||||||
|
ChangeDetectorState.CheckedBefore,
|
||||||
|
ChangeDetectorState.Errored
|
||||||
|
];
|
||||||
|
|
||||||
export function isDefaultChangeDetectionStrategy(
|
export function isDefaultChangeDetectionStrategy(
|
||||||
changeDetectionStrategy: ChangeDetectionStrategy): boolean {
|
changeDetectionStrategy: ChangeDetectionStrategy): boolean {
|
||||||
return isBlank(changeDetectionStrategy) ||
|
return isBlank(changeDetectionStrategy) ||
|
||||||
|
@ -9,7 +9,7 @@ import {DirectiveRecord, DirectiveIndex} from './directive_record';
|
|||||||
import {Locals} from './parser/locals';
|
import {Locals} from './parser/locals';
|
||||||
import {ChangeDetectorGenConfig} from './interfaces';
|
import {ChangeDetectorGenConfig} from './interfaces';
|
||||||
import {ChangeDetectionUtil, SimpleChange} from './change_detection_util';
|
import {ChangeDetectionUtil, SimpleChange} from './change_detection_util';
|
||||||
import {ChangeDetectionStrategy} from './constants';
|
import {ChangeDetectionStrategy, ChangeDetectorState} from './constants';
|
||||||
import {ProtoRecord, RecordType} from './proto_record';
|
import {ProtoRecord, RecordType} from './proto_record';
|
||||||
|
|
||||||
export class DynamicChangeDetector extends AbstractChangeDetector<any> {
|
export class DynamicChangeDetector extends AbstractChangeDetector<any> {
|
||||||
@ -134,7 +134,8 @@ export class DynamicChangeDetector extends AbstractChangeDetector<any> {
|
|||||||
if (proto.isLifeCycleRecord()) {
|
if (proto.isLifeCycleRecord()) {
|
||||||
if (proto.name === "DoCheck" && !throwOnChange) {
|
if (proto.name === "DoCheck" && !throwOnChange) {
|
||||||
this._getDirectiveFor(directiveRecord.directiveIndex).doCheck();
|
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();
|
this._getDirectiveFor(directiveRecord.directiveIndex).onInit();
|
||||||
} else if (proto.name === "OnChanges" && isPresent(changes) && !throwOnChange) {
|
} else if (proto.name === "OnChanges" && isPresent(changes) && !throwOnChange) {
|
||||||
this._getDirectiveFor(directiveRecord.directiveIndex).onChanges(changes);
|
this._getDirectiveFor(directiveRecord.directiveIndex).onChanges(changes);
|
||||||
@ -170,7 +171,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector<any> {
|
|||||||
var dirs = this._directiveRecords;
|
var dirs = this._directiveRecords;
|
||||||
for (var i = dirs.length - 1; i >= 0; --i) {
|
for (var i = dirs.length - 1; i >= 0; --i) {
|
||||||
var dir = dirs[i];
|
var dir = dirs[i];
|
||||||
if (dir.callAfterContentInit && !this.alreadyChecked) {
|
if (dir.callAfterContentInit && this.state == ChangeDetectorState.NeverChecked) {
|
||||||
this._getDirectiveFor(dir.directiveIndex).afterContentInit();
|
this._getDirectiveFor(dir.directiveIndex).afterContentInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,7 +185,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector<any> {
|
|||||||
var dirs = this._directiveRecords;
|
var dirs = this._directiveRecords;
|
||||||
for (var i = dirs.length - 1; i >= 0; --i) {
|
for (var i = dirs.length - 1; i >= 0; --i) {
|
||||||
var dir = dirs[i];
|
var dir = dirs[i];
|
||||||
if (dir.callAfterViewInit && !this.alreadyChecked) {
|
if (dir.callAfterViewInit && this.state == ChangeDetectorState.NeverChecked) {
|
||||||
this._getDirectiveFor(dir.directiveIndex).afterViewInit();
|
this._getDirectiveFor(dir.directiveIndex).afterViewInit();
|
||||||
}
|
}
|
||||||
if (dir.callAfterViewChecked) {
|
if (dir.callAfterViewChecked) {
|
||||||
|
@ -18,6 +18,8 @@ export class JitProtoChangeDetector implements ProtoChangeDetector {
|
|||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_createFactory(definition: ChangeDetectorDefinition) {
|
_createFactory(definition: ChangeDetectorDefinition) {
|
||||||
return new ChangeDetectorJITGenerator(definition, 'util', 'AbstractChangeDetector').generate();
|
return new ChangeDetectorJITGenerator(definition, 'util', 'AbstractChangeDetector',
|
||||||
|
'ChangeDetectorStatus')
|
||||||
|
.generate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,8 @@ export 'package:angular2/src/core/change_detection/abstract_change_detector.dart
|
|||||||
show AbstractChangeDetector;
|
show AbstractChangeDetector;
|
||||||
export 'package:angular2/src/core/change_detection/change_detection.dart'
|
export 'package:angular2/src/core/change_detection/change_detection.dart'
|
||||||
show ChangeDetectionStrategy;
|
show ChangeDetectionStrategy;
|
||||||
|
export 'package:angular2/src/core/change_detection/constants.dart'
|
||||||
|
show ChangeDetectorState;
|
||||||
export 'package:angular2/src/core/change_detection/directive_record.dart'
|
export 'package:angular2/src/core/change_detection/directive_record.dart'
|
||||||
show DirectiveIndex, DirectiveRecord;
|
show DirectiveIndex, DirectiveRecord;
|
||||||
export 'package:angular2/src/core/change_detection/interfaces.dart'
|
export 'package:angular2/src/core/change_detection/interfaces.dart'
|
||||||
|
@ -21,6 +21,7 @@ import {Injectable} from 'angular2/src/core/di';
|
|||||||
|
|
||||||
const ABSTRACT_CHANGE_DETECTOR = "AbstractChangeDetector";
|
const ABSTRACT_CHANGE_DETECTOR = "AbstractChangeDetector";
|
||||||
const UTIL = "ChangeDetectionUtil";
|
const UTIL = "ChangeDetectionUtil";
|
||||||
|
const CHANGE_DETECTOR_STATE = "ChangeDetectorState";
|
||||||
|
|
||||||
var ABSTRACT_CHANGE_DETECTOR_MODULE = moduleRef(
|
var ABSTRACT_CHANGE_DETECTOR_MODULE = moduleRef(
|
||||||
`package:angular2/src/core/change_detection/abstract_change_detector${MODULE_SUFFIX}`);
|
`package:angular2/src/core/change_detection/abstract_change_detector${MODULE_SUFFIX}`);
|
||||||
@ -28,6 +29,8 @@ var UTIL_MODULE =
|
|||||||
moduleRef(`package:angular2/src/core/change_detection/change_detection_util${MODULE_SUFFIX}`);
|
moduleRef(`package:angular2/src/core/change_detection/change_detection_util${MODULE_SUFFIX}`);
|
||||||
var PREGEN_PROTO_CHANGE_DETECTOR_MODULE = moduleRef(
|
var PREGEN_PROTO_CHANGE_DETECTOR_MODULE = moduleRef(
|
||||||
`package:angular2/src/core/change_detection/pregen_proto_change_detector${MODULE_SUFFIX}`);
|
`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()
|
@Injectable()
|
||||||
export class ChangeDetectionCompiler {
|
export class ChangeDetectionCompiler {
|
||||||
@ -46,7 +49,9 @@ export class ChangeDetectionCompiler {
|
|||||||
var proto = new DynamicProtoChangeDetector(definition);
|
var proto = new DynamicProtoChangeDetector(definition);
|
||||||
return (dispatcher) => proto.instantiate(dispatcher);
|
return (dispatcher) => proto.instantiate(dispatcher);
|
||||||
} else {
|
} else {
|
||||||
return new ChangeDetectorJITGenerator(definition, UTIL, ABSTRACT_CHANGE_DETECTOR).generate();
|
return new ChangeDetectorJITGenerator(definition, UTIL, ABSTRACT_CHANGE_DETECTOR,
|
||||||
|
CHANGE_DETECTOR_STATE)
|
||||||
|
.generate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,7 +79,8 @@ export class ChangeDetectionCompiler {
|
|||||||
} else {
|
} else {
|
||||||
codegen = new ChangeDetectorJITGenerator(
|
codegen = new ChangeDetectorJITGenerator(
|
||||||
definition, `${UTIL_MODULE}${UTIL}`,
|
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); }`);
|
factories.push(`function(dispatcher) { return new ${codegen.typeName}(dispatcher); }`);
|
||||||
sourcePart = codegen.generateSource();
|
sourcePart = codegen.generateSource();
|
||||||
}
|
}
|
||||||
|
@ -431,10 +431,12 @@ export function main() {
|
|||||||
describe('updating directives', () => {
|
describe('updating directives', () => {
|
||||||
var directive1;
|
var directive1;
|
||||||
var directive2;
|
var directive2;
|
||||||
|
var directive3;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
directive1 = new TestDirective();
|
directive1 = new TestDirective();
|
||||||
directive2 = new TestDirective();
|
directive2 = new TestDirective();
|
||||||
|
directive3 = new TestDirective(null, null, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should happen directly, without invoking the dispatcher', () => {
|
it('should happen directly, without invoking the dispatcher', () => {
|
||||||
@ -510,6 +512,30 @@ export function main() {
|
|||||||
|
|
||||||
expect(directive1.onInitCalled).toBe(false);
|
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', () => {
|
describe('afterContentInit', () => {
|
||||||
@ -1336,13 +1362,19 @@ class TestDirective {
|
|||||||
afterViewCheckedCalled = false;
|
afterViewCheckedCalled = false;
|
||||||
event;
|
event;
|
||||||
|
|
||||||
constructor(public afterContentCheckedSpy = null, public afterViewCheckedSpy = null) {}
|
constructor(public afterContentCheckedSpy = null, public afterViewCheckedSpy = null,
|
||||||
|
public throwOnInit = false) {}
|
||||||
|
|
||||||
onEvent(event) { this.event = event; }
|
onEvent(event) { this.event = event; }
|
||||||
|
|
||||||
doCheck() { this.doCheckCalled = true; }
|
doCheck() { this.doCheckCalled = true; }
|
||||||
|
|
||||||
onInit() { this.onInitCalled = true; }
|
onInit() {
|
||||||
|
this.onInitCalled = true;
|
||||||
|
if (this.throwOnInit) {
|
||||||
|
throw "simulated onInit failure";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onChanges(changes) {
|
onChanges(changes) {
|
||||||
var r = {};
|
var r = {};
|
||||||
|
@ -121,7 +121,7 @@ class _CodegenState {
|
|||||||
|
|
||||||
var names = new CodegenNameUtil(
|
var names = new CodegenNameUtil(
|
||||||
protoRecords, eventBindings, def.directiveRecords, '$genPrefix$_UTIL');
|
protoRecords, eventBindings, def.directiveRecords, '$genPrefix$_UTIL');
|
||||||
var logic = new CodegenLogicUtil(names, '$genPrefix$_UTIL', def.strategy);
|
var logic = new CodegenLogicUtil(names, '$genPrefix$_UTIL', '$genPrefix$_STATE', def.strategy);
|
||||||
return new _CodegenState._(
|
return new _CodegenState._(
|
||||||
genPrefix,
|
genPrefix,
|
||||||
def.id,
|
def.id,
|
||||||
@ -503,7 +503,7 @@ class _CodegenState {
|
|||||||
|
|
||||||
String _genOnInit(ProtoRecord r) {
|
String _genOnInit(ProtoRecord r) {
|
||||||
var br = r.bindingRecord;
|
var br = r.bindingRecord;
|
||||||
return 'if (!throwOnChange && !${_names.getAlreadyCheckedName()}) '
|
return 'if (!throwOnChange && ${_names.getStateName()} == ${_genPrefix}$_STATE.NeverChecked) '
|
||||||
'${_names.getDirectiveName(br.directiveRecord.directiveIndex)}.onInit();';
|
'${_names.getDirectiveName(br.directiveRecord.directiveIndex)}.onInit();';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -539,3 +539,4 @@ const _GEN_PROPERTY_BINDING_TARGETS_NAME =
|
|||||||
'${_GEN_PREFIX}_propertyBindingTargets';
|
'${_GEN_PREFIX}_propertyBindingTargets';
|
||||||
const _GEN_DIRECTIVE_INDICES_NAME = '${_GEN_PREFIX}_directiveIndices';
|
const _GEN_DIRECTIVE_INDICES_NAME = '${_GEN_PREFIX}_directiveIndices';
|
||||||
const _UTIL = 'ChangeDetectionUtil';
|
const _UTIL = 'ChangeDetectionUtil';
|
||||||
|
const _STATE = 'ChangeDetectorState';
|
||||||
|
Loading…
x
Reference in New Issue
Block a user