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:
parent
a0b240884b
commit
195c5c21d4
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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}`); }
|
||||||
}
|
}
|
||||||
|
@ -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() {
|
||||||
|
@ -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))
|
||||||
|
@ -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) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user