refactor(change_detect): Move (de)hydrate methods into superclass

Move the implementation of `(de)hydrate`, `hydrated`, and
`detectChangesInRecords` into `AbstractChangeDetector`.

Add comments clarifying the contract between `AbstractChangeDetector`
and its subclasses.

Closes #3245
This commit is contained in:
Tim Blasi 2015-07-29 10:43:07 -07:00
parent 73b7d99dc4
commit 9c19eb906b
6 changed files with 83 additions and 132 deletions

View File

@ -1,5 +1,6 @@
import {isPresent, BaseException} from 'angular2/src/facade/lang'; import {isPresent, BaseException} from 'angular2/src/facade/lang';
import {List, ListWrapper} from 'angular2/src/facade/collection'; import {List, ListWrapper} from 'angular2/src/facade/collection';
import {ChangeDetectionUtil} from './change_detection_util';
import {ChangeDetectorRef} from './change_detector_ref'; import {ChangeDetectorRef} from './change_detector_ref';
import {DirectiveRecord} from './directive_record'; import {DirectiveRecord} from './directive_record';
import {ChangeDetector, ChangeDispatcher} from './interfaces'; import {ChangeDetector, ChangeDispatcher} from './interfaces';
@ -34,7 +35,7 @@ export class AbstractChangeDetector<T> implements ChangeDetector {
protos: List<ProtoRecord>; protos: List<ProtoRecord>;
constructor(public id: string, dispatcher: ChangeDispatcher, protos: List<ProtoRecord>, constructor(public id: string, dispatcher: ChangeDispatcher, protos: List<ProtoRecord>,
directiveRecords: List<DirectiveRecord>) { directiveRecords: List<DirectiveRecord>, public modeOnHydrate: string) {
this.ref = new ChangeDetectorRef(this); this.ref = new ChangeDetectorRef(this);
this.directiveRecords = directiveRecords; this.directiveRecords = directiveRecords;
this.dispatcher = dispatcher; this.dispatcher = dispatcher;
@ -75,16 +76,59 @@ export class AbstractChangeDetector<T> implements ChangeDetector {
if (this.mode === CHECK_ONCE) this.mode = CHECKED; if (this.mode === CHECK_ONCE) this.mode = CHECKED;
} }
detectChangesInRecords(throwOnChange: boolean): void {} // This method is not intended to be overridden. Subclasses should instead provide an
// implementation of `detectChangesInRecordsInternal` which does the work of detecting changes
// and which this method will call.
// This method expects that `detectChangesInRecordsInternal` will set the property
// `this.currentProto` to whichever [ProtoRecord] is currently being processed. This is to
// facilitate error reporting.
detectChangesInRecords(throwOnChange: boolean): void {
if (!this.hydrated()) {
ChangeDetectionUtil.throwDehydrated();
}
try {
this.detectChangesInRecordsInternal(throwOnChange);
} catch (e) {
this.throwError(this.currentProto, e, e.stack);
}
}
hydrate(context: T, locals: Locals, directives: any, pipes: any): void {} // Subclasses should override this method to perform any work necessary to detect and report
// changes. For example, changes should be reported via `ChangeDetectionUtil.addChange`, lifecycle
// methods should be called, etc.
// This implementation should also set `this.currentProto` to whichever [ProtoRecord] is
// currently being processed to facilitate error reporting. See {@link #detectChangesInRecords}.
detectChangesInRecordsInternal(throwOnChange: boolean): void {}
// This method is not intended to be overridden. Subclasses should instead provide an
// implementation of `hydrateDirectives`.
hydrate(context: T, locals: Locals, directives: any, pipes: any): void {
this.mode = this.modeOnHydrate;
this.context = context;
this.locals = locals;
this.pipes = pipes;
this.hydrateDirectives(directives);
this.alreadyChecked = false;
}
// Subclasses should override this method to hydrate any directives.
hydrateDirectives(directives: any): void {} hydrateDirectives(directives: any): void {}
dehydrate(): void {} // This method is not intended to be overridden. Subclasses should instead provide an
// implementation of `dehydrateDirectives`.
dehydrate(): void {
this.dehydrateDirectives(true);
this.context = null;
this.locals = null;
this.pipes = null;
}
// Subclasses should override this method to dehydrate any directives. This method should reverse
// any work done in `hydrateDirectives`.
dehydrateDirectives(destroyPipes: boolean): void {} dehydrateDirectives(destroyPipes: boolean): void {}
hydrated(): boolean { return this.context !== null; }
callOnAllChangesDone(): void {} callOnAllChangesDone(): void {}
_detectChangesInLightDomChildren(throwOnChange: boolean): void { _detectChangesInLightDomChildren(throwOnChange: boolean): void {

View File

@ -6,7 +6,7 @@ import {ChangeDetectionUtil} from './change_detection_util';
import {DirectiveIndex, DirectiveRecord} from './directive_record'; import {DirectiveIndex, DirectiveRecord} from './directive_record';
import {ProtoRecord, RecordType} from './proto_record'; import {ProtoRecord, RecordType} from './proto_record';
import {CONTEXT_INDEX, CodegenNameUtil, sanitizeName} from './codegen_name_util'; import {CodegenNameUtil, sanitizeName} from './codegen_name_util';
/** /**
@ -27,7 +27,7 @@ export class ChangeDetectorJITGenerator {
_names: CodegenNameUtil; _names: CodegenNameUtil;
_typeName: string; _typeName: string;
constructor(public id: string, public changeDetectionStrategy: string, constructor(public id: string, private changeDetectionStrategy: string,
public records: List<ProtoRecord>, public directiveRecords: List<any>, public records: List<ProtoRecord>, public directiveRecords: List<any>,
private generateCheckNoChanges: boolean) { private generateCheckNoChanges: boolean) {
this._names = new CodegenNameUtil(this.records, this.directiveRecords, UTIL); this._names = new CodegenNameUtil(this.records, this.directiveRecords, UTIL);
@ -37,24 +37,15 @@ export class ChangeDetectorJITGenerator {
generate(): Function { generate(): Function {
var classDefinition = ` var classDefinition = `
var ${this._typeName} = function ${this._typeName}(dispatcher, protos, directiveRecords) { var ${this._typeName} = function ${this._typeName}(dispatcher, protos, directiveRecords) {
${ABSTRACT_CHANGE_DETECTOR}.call(this, ${JSON.stringify(this.id)}, dispatcher, protos, directiveRecords); ${ABSTRACT_CHANGE_DETECTOR}.call(
this, ${JSON.stringify(this.id)}, dispatcher, protos, directiveRecords,
"${ChangeDetectionUtil.changeDetectionMode(this.changeDetectionStrategy)}");
this.dehydrateDirectives(false); this.dehydrateDirectives(false);
} }
${this._typeName}.prototype = Object.create(${ABSTRACT_CHANGE_DETECTOR}.prototype); ${this._typeName}.prototype = Object.create(${ABSTRACT_CHANGE_DETECTOR}.prototype);
${this._typeName}.prototype.detectChangesInRecords = function(throwOnChange) { ${this._typeName}.prototype.detectChangesInRecordsInternal = function(throwOnChange) {
if (!this.hydrated()) {
${UTIL}.throwDehydrated();
}
try {
this.__detectChangesInRecords(throwOnChange);
} catch (e) {
this.throwError(${this._names.getCurrentProtoName()}, e, e.stack);
}
}
${this._typeName}.prototype.__detectChangesInRecords = function(throwOnChange) {
${this._names.getCurrentProtoName()} = null; ${this._names.getCurrentProtoName()} = null;
${this._names.genInitLocals()} ${this._names.genInitLocals()}
@ -72,30 +63,10 @@ export class ChangeDetectorJITGenerator {
${this._genCallOnAllChangesDoneBody()} ${this._genCallOnAllChangesDoneBody()}
} }
${this._typeName}.prototype.hydrate = function(context, locals, directives, pipes) {
${this._names.getModeName()} =
"${ChangeDetectionUtil.changeDetectionMode(this.changeDetectionStrategy)}";
${this._names.getFieldName(CONTEXT_INDEX)} = context;
${this._names.getLocalsAccessorName()} = locals;
this.hydrateDirectives(directives);
${this._names.getPipesAccessorName()} = pipes;
${this._names.getAlreadyCheckedName()} = false;
}
${this._maybeGenHydrateDirectives()} ${this._maybeGenHydrateDirectives()}
${this._typeName}.prototype.dehydrate = function() {
this.dehydrateDirectives(true);
${this._names.getLocalsAccessorName()} = null;
${this._names.getPipesAccessorName()} = null;
}
${this._maybeGenDehydrateDirectives()} ${this._maybeGenDehydrateDirectives()}
${this._typeName}.prototype.hydrated = function() {
return ${this._names.getFieldName(CONTEXT_INDEX)} !== null;
}
return function(dispatcher) { return function(dispatcher) {
return new ${this._typeName}(dispatcher, protos, directiveRecords); return new ${this._typeName}(dispatcher, protos, directiveRecords);
} }
@ -153,6 +124,7 @@ export class ChangeDetectorJITGenerator {
var notifications = []; var notifications = [];
var dirs = this.directiveRecords; var dirs = this.directiveRecords;
// NOTE(kegluneq): Order is important!
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.callOnAllChangesDone) { if (dir.callOnAllChangesDone) {

View File

@ -124,11 +124,11 @@ export class CodegenNameUtil {
genDehydrateFields(): string { genDehydrateFields(): string {
var fields = this.getAllFieldNames(); var fields = this.getAllFieldNames();
ListWrapper.removeAt(fields, CONTEXT_INDEX); ListWrapper.removeAt(fields, CONTEXT_INDEX);
if (!ListWrapper.isEmpty(fields)) { if (ListWrapper.isEmpty(fields)) return '';
// At least one assignment.
fields.push(`${this.utilName}.uninitialized;`); // At least one assignment.
} fields.push(`${this.utilName}.uninitialized;`);
return `${this.getFieldName(CONTEXT_INDEX)} = null; ${ListWrapper.join(fields, ' = ')}`; return ListWrapper.join(fields, ' = ');
} }
/** /**

View File

@ -1,10 +1,8 @@
import {isPresent, isBlank, BaseException, FunctionWrapper} from 'angular2/src/facade/lang'; import {isPresent, isBlank, BaseException, FunctionWrapper} from 'angular2/src/facade/lang';
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {Locals} from 'angular2/src/change_detection/parser/locals';
import {AbstractChangeDetector} from './abstract_change_detector'; import {AbstractChangeDetector} from './abstract_change_detector';
import {BindingRecord} from './binding_record'; import {BindingRecord} from './binding_record';
import {Pipes} from './pipes/pipes';
import {ChangeDetectionUtil, SimpleChange} from './change_detection_util'; import {ChangeDetectionUtil, SimpleChange} from './change_detection_util';
@ -17,39 +15,34 @@ export class DynamicChangeDetector extends AbstractChangeDetector<any> {
prevContexts: List<any>; prevContexts: List<any>;
directives: any = null; directives: any = null;
constructor(id: string, private changeControlStrategy: string, dispatcher: any, constructor(id: string, changeDetectionStrategy: string, dispatcher: any,
protos: List<ProtoRecord>, directiveRecords: List<any>) { protos: List<ProtoRecord>, directiveRecords: List<any>) {
super(id, dispatcher, protos, directiveRecords); super(id, dispatcher, protos, directiveRecords,
this.values = ListWrapper.createFixedSize(protos.length + 1); ChangeDetectionUtil.changeDetectionMode(changeDetectionStrategy));
this.localPipes = ListWrapper.createFixedSize(protos.length + 1); var len = protos.length + 1;
this.prevContexts = ListWrapper.createFixedSize(protos.length + 1); this.values = ListWrapper.createFixedSize(len);
this.changes = ListWrapper.createFixedSize(protos.length + 1); this.localPipes = ListWrapper.createFixedSize(len);
this.prevContexts = ListWrapper.createFixedSize(len);
this.changes = ListWrapper.createFixedSize(len);
this.values[0] = null; this.dehydrateDirectives(false);
ListWrapper.fill(this.values, ChangeDetectionUtil.uninitialized, 1);
ListWrapper.fill(this.localPipes, null);
ListWrapper.fill(this.prevContexts, ChangeDetectionUtil.uninitialized);
ListWrapper.fill(this.changes, false);
} }
hydrate(context: any, locals: Locals, directives: any, pipes: Pipes): void { hydrateDirectives(directives: any): void {
this.mode = ChangeDetectionUtil.changeDetectionMode(this.changeControlStrategy); this.values[0] = this.context;
this.values[0] = context;
this.locals = locals;
this.directives = directives; this.directives = directives;
this.alreadyChecked = false;
this.pipes = pipes;
} }
dehydrate() { dehydrateDirectives(destroyPipes: boolean) {
this._destroyPipes(); if (destroyPipes) {
this._destroyPipes();
}
this.values[0] = null; this.values[0] = null;
this.directives = null;
ListWrapper.fill(this.values, ChangeDetectionUtil.uninitialized, 1); ListWrapper.fill(this.values, ChangeDetectionUtil.uninitialized, 1);
ListWrapper.fill(this.changes, false); ListWrapper.fill(this.changes, false);
ListWrapper.fill(this.localPipes, null); ListWrapper.fill(this.localPipes, null);
ListWrapper.fill(this.prevContexts, ChangeDetectionUtil.uninitialized); ListWrapper.fill(this.prevContexts, ChangeDetectionUtil.uninitialized);
this.locals = null;
this.pipes = null;
} }
_destroyPipes() { _destroyPipes() {
@ -60,10 +53,9 @@ export class DynamicChangeDetector extends AbstractChangeDetector<any> {
} }
} }
hydrated(): boolean { return this.values[0] !== null; }
checkNoChanges(): void { this.runDetectChanges(true); } checkNoChanges(): void { this.runDetectChanges(true); }
// TODO(vsavkin): #3366. Update to work with [AbstractChangeDetector#detectChangesInRecords]
detectChangesInRecords(throwOnChange: boolean) { detectChangesInRecords(throwOnChange: boolean) {
if (!this.hydrated()) { if (!this.hydrated()) {
ChangeDetectionUtil.throwDehydrated(); ChangeDetectionUtil.throwDehydrated();

View File

@ -104,22 +104,11 @@ class _CodegenState {
$_changeDetectorTypeName(dispatcher, protos, directiveRecords) $_changeDetectorTypeName(dispatcher, protos, directiveRecords)
: super(${_encodeValue(_changeDetectorDefId)}, : super(${_encodeValue(_changeDetectorDefId)},
dispatcher, protos, directiveRecords) { dispatcher, protos, directiveRecords, '$_changeDetectionMode') {
dehydrateDirectives(false); dehydrateDirectives(false);
} }
void detectChangesInRecords(throwOnChange) { void detectChangesInRecordsInternal(throwOnChange) {
if (!hydrated()) {
$_UTIL.throwDehydrated();
}
try {
__detectChangesInRecords(throwOnChange);
} catch (e, s) {
throwError(${_names.getCurrentProtoName()}, e, s);
}
}
void __detectChangesInRecords(throwOnChange) {
${_names.getCurrentProtoName()} = null; ${_names.getCurrentProtoName()} = null;
${_names.genInitLocals()} ${_names.genInitLocals()}
@ -137,28 +126,10 @@ class _CodegenState {
${_getCallOnAllChangesDoneBody()} ${_getCallOnAllChangesDoneBody()}
} }
void hydrate(
$_contextTypeName context, locals, directives, pipes) {
${_names.getModeName()} = '$_changeDetectionMode';
${_names.getFieldName(CONTEXT_INDEX)} = context;
${_names.getLocalsAccessorName()} = locals;
hydrateDirectives(directives);
${_names.getAlreadyCheckedName()} = false;
${_names.getPipesAccessorName()} = pipes;
}
${_maybeGenHydrateDirectives()} ${_maybeGenHydrateDirectives()}
void dehydrate() {
dehydrateDirectives(true);
${_names.getLocalsAccessorName()} = null;
${_names.getPipesAccessorName()} = null;
}
${_maybeGenDehydrateDirectives()} ${_maybeGenDehydrateDirectives()}
hydrated() => ${_names.getFieldName(CONTEXT_INDEX)} != null;
static $_GEN_PREFIX.ProtoChangeDetector static $_GEN_PREFIX.ProtoChangeDetector
$PROTO_CHANGE_DETECTOR_FACTORY_METHOD( $PROTO_CHANGE_DETECTOR_FACTORY_METHOD(
$_GEN_PREFIX.ChangeDetectorDefinition def) { $_GEN_PREFIX.ChangeDetectorDefinition def) {

View File

@ -27,23 +27,13 @@ class _MyComponent_ChangeDetector0
extends _gen.AbstractChangeDetector<MyComponent> { extends _gen.AbstractChangeDetector<MyComponent> {
var myNum0, interpolate1; var myNum0, interpolate1;
_MyComponent_ChangeDetector0(dispatcher, protos, directiveRecords) _MyComponent_ChangeDetector0(dispatcher, protos, directiveRecords) : super(
: super("MyComponent_comp_0", dispatcher, protos, directiveRecords) { "MyComponent_comp_0", dispatcher, protos, directiveRecords,
'ALWAYS_CHECK') {
dehydrateDirectives(false); dehydrateDirectives(false);
} }
void detectChangesInRecords(throwOnChange) { void detectChangesInRecordsInternal(throwOnChange) {
if (!hydrated()) {
_gen.ChangeDetectionUtil.throwDehydrated();
}
try {
__detectChangesInRecords(throwOnChange);
} catch (e, s) {
throwError(this.currentProto, e, s);
}
}
void __detectChangesInRecords(throwOnChange) {
this.currentProto = null; this.currentProto = null;
var l_context = this.context, var l_context = this.context,
l_myNum0, l_myNum0,
@ -96,28 +86,10 @@ class _MyComponent_ChangeDetector0
this.dispatcher.notifyOnAllChangesDone(); this.dispatcher.notifyOnAllChangesDone();
} }
void hydrate(MyComponent context, locals, directives, pipes) {
this.mode = 'ALWAYS_CHECK';
this.context = context;
this.locals = locals;
hydrateDirectives(directives);
this.alreadyChecked = false;
this.pipes = pipes;
}
void dehydrate() {
dehydrateDirectives(true);
this.locals = null;
this.pipes = null;
}
void dehydrateDirectives(destroyPipes) { void dehydrateDirectives(destroyPipes) {
this.context = null;
this.myNum0 = this.interpolate1 = _gen.ChangeDetectionUtil.uninitialized; this.myNum0 = this.interpolate1 = _gen.ChangeDetectionUtil.uninitialized;
} }
hydrated() => this.context != null;
static _gen.ProtoChangeDetector newProtoChangeDetector( static _gen.ProtoChangeDetector newProtoChangeDetector(
_gen.ChangeDetectorDefinition def) { _gen.ChangeDetectorDefinition def) {
return new _gen.PregenProtoChangeDetector( return new _gen.PregenProtoChangeDetector(