fix(change_detection): update the right change detector when using ON_PUSH mode

Previously, in a case where you have a mix of ON_PUSH and DEFAULT detectors, Angular would update the status of a wrong detector.
This commit is contained in:
vsavkin 2015-08-19 10:48:53 -07:00
parent a0b240884b
commit 195c5c21d4
7 changed files with 52 additions and 53 deletions

View File

@ -210,6 +210,10 @@ export class AbstractChangeDetector<T> implements ChangeDetector {
return value; return value;
} }
protected getDetectorFor(directives: any, index: number): ChangeDetector {
return directives.getDetectorFor(this.directiveRecords[index].directiveIndex);
}
protected notifyDispatcher(value: any): void { protected notifyDispatcher(value: any): void {
this.dispatcher.notifyOnBinding(this._currentBinding(), value); this.dispatcher.notifyOnBinding(this._currentBinding(), value);
} }

View File

@ -143,7 +143,7 @@ export class ChangeDetectorJITGenerator {
_maybeGenHydrateDirectives(): string { _maybeGenHydrateDirectives(): string {
var hydrateDirectivesCode = this._genHydrateDirectives(); var hydrateDirectivesCode = this._genHydrateDirectives();
var hydrateDetectorsCode = this._genHydrateDetectors(); var hydrateDetectorsCode = this._logic.genHydrateDetectors(this.directiveRecords);
if (!hydrateDirectivesCode && !hydrateDetectorsCode) return ''; if (!hydrateDirectivesCode && !hydrateDetectorsCode) return '';
return `${this._typeName}.prototype.hydrateDirectives = function(directives) { return `${this._typeName}.prototype.hydrateDirectives = function(directives) {
${hydrateDirectivesCode} ${hydrateDirectivesCode}
@ -161,16 +161,6 @@ export class ChangeDetectorJITGenerator {
return lines.join('\n'); return lines.join('\n');
} }
_genHydrateDetectors(): string {
var detectorFieldNames = this._names.getAllDetectorNames();
var lines = ListWrapper.createFixedSize(detectorFieldNames.length);
for (var i = 0, iLen = detectorFieldNames.length; i < iLen; ++i) {
lines[i] = `${detectorFieldNames[i]} = directives.getDetectorFor(
${this._names.getDirectivesAccessorName()}[${i}].directiveIndex);`;
}
return lines.join('\n');
}
_maybeGenCallOnAllChangesDone(): string { _maybeGenCallOnAllChangesDone(): string {
var notifications = []; var notifications = [];
var dirs = this.directiveRecords; var dirs = this.directiveRecords;

View File

@ -3,6 +3,7 @@ import {BaseException, Json, StringWrapper} from 'angular2/src/facade/lang';
import {CodegenNameUtil} from './codegen_name_util'; import {CodegenNameUtil} from './codegen_name_util';
import {codify, combineGeneratedStrings, rawString} from './codegen_facade'; import {codify, combineGeneratedStrings, rawString} from './codegen_facade';
import {ProtoRecord, RecordType} from './proto_record'; import {ProtoRecord, RecordType} from './proto_record';
import {DirectiveRecord} from './directive_record';
/** /**
* This is an experimental feature. Works only in Dart. * This is an experimental feature. Works only in Dart.
@ -131,4 +132,16 @@ export class CodegenLogicUtil {
iVals.push(codify(protoRec.fixedArgs[protoRec.args.length])); iVals.push(codify(protoRec.fixedArgs[protoRec.args.length]));
return combineGeneratedStrings(iVals); return combineGeneratedStrings(iVals);
} }
genHydrateDetectors(directiveRecords: DirectiveRecord[]): string {
var res = [];
for (var i = 0; i < directiveRecords.length; ++i) {
var r = directiveRecords[i];
if (!r.isDefaultChangeDetection()) {
res.push(
`${this._names.getDetectorName(r.directiveIndex)} = this.getDetectorFor(directives, ${i});`);
}
}
return res.join("\n");
}
} }

View File

@ -200,11 +200,5 @@ export class CodegenNameUtil {
return this._addFieldPrefix(`directive_${d.name}`); return this._addFieldPrefix(`directive_${d.name}`);
} }
getAllDetectorNames(): List<string> {
return ListWrapper.map(
ListWrapper.filter(this.directiveRecords, r => !r.isDefaultChangeDetection()),
(d) => this.getDetectorName(d.directiveIndex));
}
getDetectorName(d: DirectiveIndex): string { return this._addFieldPrefix(`detector_${d.name}`); } getDetectorName(d: DirectiveIndex): string { return this._addFieldPrefix(`detector_${d.name}`); }
} }

View File

@ -226,7 +226,7 @@ class _CodegenState {
String _maybeGenHydrateDirectives() { String _maybeGenHydrateDirectives() {
var hydrateDirectivesCode = _genHydrateDirectives(); var hydrateDirectivesCode = _genHydrateDirectives();
var hydrateDetectorsCode = _genHydrateDetectors(); var hydrateDetectorsCode = _logic.genHydrateDetectors(_directiveRecords);
if (hydrateDirectivesCode.isEmpty && hydrateDetectorsCode.isEmpty) { if (hydrateDirectivesCode.isEmpty && hydrateDetectorsCode.isEmpty) {
return ''; return '';
} }
@ -244,16 +244,6 @@ class _CodegenState {
return '$buf'; return '$buf';
} }
String _genHydrateDetectors() {
var buf = new StringBuffer();
var detectorFieldNames = _names.getAllDetectorNames();
for (var i = 0; i < detectorFieldNames.length; ++i) {
buf.writeln('${detectorFieldNames[i]} = directives.getDetectorFor('
'${_names.getDirectivesAccessorName()}[$i].directiveIndex);');
}
return '$buf';
}
/// Generates calls to `onAllChangesDone` for all `Directive`s that request /// Generates calls to `onAllChangesDone` for all `Directive`s that request
/// them. /// them.
String _maybeGenCallOnAllChangesDone() { String _maybeGenCallOnAllChangesDone() {

View File

@ -183,24 +183,25 @@ class _ExpressionWithMode {
var directiveRecords = []; var directiveRecords = [];
var eventRecords = []; var eventRecords = [];
var dirRecordWithDefault =
new DirectiveRecord({directiveIndex: new DirectiveIndex(0, 0), changeDetection: DEFAULT});
var dirRecordWithOnPush =
new DirectiveRecord({directiveIndex: new DirectiveIndex(0, 1), changeDetection: ON_PUSH});
if (this._withRecords) { if (this._withRecords) {
var dirRecordWithOnPush = var updateDirWithOnDefaultRecord =
new DirectiveRecord({directiveIndex: new DirectiveIndex(0, 0), changeDetection: ON_PUSH}); BindingRecord.createForDirective(_getParser().parseBinding('42', 'location'), 'a',
(o, v) => (<any>o).a = v, dirRecordWithDefault);
var updateDirWithOnPushRecord = var updateDirWithOnPushRecord =
BindingRecord.createForDirective(_getParser().parseBinding('42', 'location'), 'a', BindingRecord.createForDirective(_getParser().parseBinding('42', 'location'), 'a',
(o, v) => (<any>o).a = v, dirRecordWithOnPush); (o, v) => (<any>o).a = v, dirRecordWithOnPush);
bindingRecords = [updateDirWithOnPushRecord];
directiveRecords = [dirRecordWithOnPush]; directiveRecords = [dirRecordWithDefault, dirRecordWithOnPush];
} else { bindingRecords = [updateDirWithOnDefaultRecord, updateDirWithOnPushRecord];
bindingRecords = [];
directiveRecords = [];
} }
if (this._withEvents) { if (this._withEvents) {
var dirRecordWithOnPush = directiveRecords = [dirRecordWithDefault, dirRecordWithOnPush];
new DirectiveRecord({directiveIndex: new DirectiveIndex(0, 0), changeDetection: ON_PUSH});
directiveRecords = [dirRecordWithOnPush];
eventRecords = eventRecords =
ListWrapper.concat(_createEventRecords("(event)='false'"), ListWrapper.concat(_createEventRecords("(event)='false'"),
_createHostEventRecords("(host-event)='false'", dirRecordWithOnPush)) _createHostEventRecords("(host-event)='false'", dirRecordWithOnPush))

View File

@ -704,29 +704,36 @@ export function main() {
}); });
describe('marking ON_PUSH detectors as CHECK_ONCE after an update', () => { describe('marking ON_PUSH detectors as CHECK_ONCE after an update', () => {
var checkedDetector; var childDirectiveDetectorRegular;
var childDirectiveDetectorOnPush;
var directives; var directives;
beforeEach(() => { beforeEach(() => {
checkedDetector = _createWithoutHydrate('emptyUsingOnPushStrategy').changeDetector; childDirectiveDetectorRegular = _createWithoutHydrate('10').changeDetector;
checkedDetector.hydrate(_DEFAULT_CONTEXT, null, null, null); childDirectiveDetectorRegular.hydrate(_DEFAULT_CONTEXT, null, null, null);
checkedDetector.mode = CHECKED; childDirectiveDetectorRegular.mode = CHECK_ALWAYS;
var targetDirective = new TestData(null); childDirectiveDetectorOnPush =
directives = new FakeDirectives([targetDirective], [checkedDetector]); _createWithoutHydrate('emptyUsingOnPushStrategy').changeDetector;
childDirectiveDetectorOnPush.hydrate(_DEFAULT_CONTEXT, null, null, null);
childDirectiveDetectorOnPush.mode = CHECKED;
directives =
new FakeDirectives([new TestData(null), new TestData(null)],
[childDirectiveDetectorRegular, childDirectiveDetectorOnPush]);
}); });
it('should set the mode to CHECK_ONCE when a binding is updated', () => { it('should set the mode to CHECK_ONCE when a binding is updated', () => {
var cd = _createWithoutHydrate('onPushRecordsUsingDefaultStrategy').changeDetector; var parentDetector =
cd.hydrate(_DEFAULT_CONTEXT, null, directives, null); _createWithoutHydrate('onPushRecordsUsingDefaultStrategy').changeDetector;
parentDetector.hydrate(_DEFAULT_CONTEXT, null, directives, null);
expect(checkedDetector.mode).toEqual(CHECKED); parentDetector.detectChanges();
// evaluate the record, update the targetDirective, and mark its detector as // making sure that we only change the status of ON_PUSH components
// CHECK_ONCE expect(childDirectiveDetectorRegular.mode).toEqual(CHECK_ALWAYS);
cd.detectChanges();
expect(checkedDetector.mode).toEqual(CHECK_ONCE); expect(childDirectiveDetectorOnPush.mode).toEqual(CHECK_ONCE);
}); });
it('should mark ON_PUSH detectors as CHECK_ONCE after an event', () => { it('should mark ON_PUSH detectors as CHECK_ONCE after an event', () => {
@ -745,7 +752,7 @@ export function main() {
cd.handleEvent("host-event", 0, null); cd.handleEvent("host-event", 0, null);
expect(checkedDetector.mode).toEqual(CHECK_ONCE); expect(childDirectiveDetectorOnPush.mode).toEqual(CHECK_ONCE);
}); });
if (IS_DART) { if (IS_DART) {