fix(change_detect): Sort `DirectiveMetadata` properties during processing
The Angular 2 render compiler can get out of sync between its transformer execution and its runtime execution, leading to incorrect change detectors with out-of-order property values. Stable sorting solves this problem (temporarily).
This commit is contained in:
parent
4c8ea12903
commit
b2a0be87e8
|
@ -42,6 +42,7 @@ class MapWrapper {
|
|||
static forEach(Map m, fn(v, k)) {
|
||||
m.forEach((k, v) => fn(v, k));
|
||||
}
|
||||
static get(Map map, key) => map[key];
|
||||
static int size(Map m) => m.length;
|
||||
static void delete(Map m, k) {
|
||||
m.remove(k);
|
||||
|
|
|
@ -64,6 +64,7 @@ export class MapWrapper {
|
|||
}
|
||||
static createFromPairs(pairs: List<any>): Map<any, any> { return createMapFromPairs(pairs); }
|
||||
static forEach<K, V>(m: Map<K, V>, fn: /*(V, K) => void*/ Function) { m.forEach(<any>fn); }
|
||||
static get<K, V>(map: Map<K, V>, key: K): V { return map.get(key); }
|
||||
static size(m: Map<any, any>): number { return m.size; }
|
||||
static delete<K>(m: Map<K, any>, k: K) { m.delete(k); }
|
||||
static clearValues(m: Map<any, any>) { _clearValues(m); }
|
||||
|
|
|
@ -97,6 +97,8 @@ class StringWrapper {
|
|||
static bool contains(String s, String substr) {
|
||||
return s.contains(substr);
|
||||
}
|
||||
|
||||
static int compare(String a, String b) => a.compareTo(b);
|
||||
}
|
||||
|
||||
class StringJoiner {
|
||||
|
|
|
@ -158,6 +158,16 @@ export class StringWrapper {
|
|||
}
|
||||
|
||||
static contains(s: string, substr: string): boolean { return s.indexOf(substr) != -1; }
|
||||
|
||||
static compare(a: string, b: string): int {
|
||||
if (a < b) {
|
||||
return -1;
|
||||
} else if (a > b) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class StringJoiner {
|
||||
|
|
|
@ -76,17 +76,17 @@ export class DirectiveParser implements CompileStep {
|
|||
});
|
||||
}
|
||||
if (isPresent(dirMetadata.hostListeners)) {
|
||||
MapWrapper.forEach(dirMetadata.hostListeners, (action, eventName) => {
|
||||
this._sortedKeysForEach(dirMetadata.hostListeners, (action, eventName) => {
|
||||
this._bindDirectiveEvent(eventName, action, current, directiveBinderBuilder);
|
||||
});
|
||||
}
|
||||
if (isPresent(dirMetadata.hostProperties)) {
|
||||
MapWrapper.forEach(dirMetadata.hostProperties, (expression, hostPropertyName) => {
|
||||
this._sortedKeysForEach(dirMetadata.hostProperties, (expression, hostPropertyName) => {
|
||||
this._bindHostProperty(hostPropertyName, expression, current, directiveBinderBuilder);
|
||||
});
|
||||
}
|
||||
if (isPresent(dirMetadata.hostAttributes)) {
|
||||
MapWrapper.forEach(dirMetadata.hostAttributes, (hostAttrValue, hostAttrName) => {
|
||||
this._sortedKeysForEach(dirMetadata.hostAttributes, (hostAttrValue, hostAttrName) => {
|
||||
this._addHostAttribute(hostAttrName, hostAttrValue, current);
|
||||
});
|
||||
}
|
||||
|
@ -97,6 +97,16 @@ export class DirectiveParser implements CompileStep {
|
|||
});
|
||||
}
|
||||
|
||||
_sortedKeysForEach(map: Map<string, string>, fn: (value: string, key: string) => void): void {
|
||||
var keys = MapWrapper.keys(map);
|
||||
ListWrapper.sort(keys, (a, b) => {
|
||||
// Ensure a stable sort.
|
||||
var compareVal = StringWrapper.compare(a, b);
|
||||
return compareVal == 0 ? -1 : compareVal;
|
||||
});
|
||||
ListWrapper.forEach(keys, (key) => { fn(MapWrapper.get(map, key), key); });
|
||||
}
|
||||
|
||||
_ensureHasOnlyOneComponent(elementBinder: ElementBinderBuilder, elDescription: string): void {
|
||||
if (isPresent(elementBinder.componentId)) {
|
||||
throw new BaseException(
|
||||
|
|
|
@ -645,13 +645,13 @@ export function main() {
|
|||
|
||||
var input = rootTC.query(By.css("input")).nativeElement;
|
||||
expect(DOM.classList(input))
|
||||
.toEqual(['ng-binding', 'ng-untouched', 'ng-pristine', 'ng-invalid']);
|
||||
.toEqual(['ng-binding', 'ng-invalid', 'ng-pristine', 'ng-untouched']);
|
||||
|
||||
dispatchEvent(input, "blur");
|
||||
rootTC.detectChanges();
|
||||
|
||||
expect(DOM.classList(input))
|
||||
.toEqual(["ng-binding", "ng-pristine", "ng-invalid", "ng-touched"]);
|
||||
.toEqual(["ng-binding", "ng-invalid", "ng-pristine", "ng-touched"]);
|
||||
|
||||
input.value = "updatedValue";
|
||||
dispatchEvent(input, "change");
|
||||
|
@ -675,13 +675,13 @@ export function main() {
|
|||
|
||||
var input = rootTC.query(By.css("input")).nativeElement;
|
||||
expect(DOM.classList(input))
|
||||
.toEqual(["ng-binding", "ng-untouched", "ng-pristine", "ng-invalid"]);
|
||||
.toEqual(["ng-binding", "ng-invalid", "ng-pristine", "ng-untouched"]);
|
||||
|
||||
dispatchEvent(input, "blur");
|
||||
rootTC.detectChanges();
|
||||
|
||||
expect(DOM.classList(input))
|
||||
.toEqual(["ng-binding", "ng-pristine", "ng-invalid", "ng-touched"]);
|
||||
.toEqual(["ng-binding", "ng-invalid", "ng-pristine", "ng-touched"]);
|
||||
|
||||
input.value = "updatedValue";
|
||||
dispatchEvent(input, "change");
|
||||
|
@ -703,13 +703,13 @@ export function main() {
|
|||
|
||||
var input = rootTC.query(By.css("input")).nativeElement;
|
||||
expect(DOM.classList(input))
|
||||
.toEqual(["ng-binding", "ng-untouched", "ng-pristine", "ng-invalid"]);
|
||||
.toEqual(["ng-binding", "ng-invalid", "ng-pristine", "ng-untouched"]);
|
||||
|
||||
dispatchEvent(input, "blur");
|
||||
rootTC.detectChanges();
|
||||
|
||||
expect(DOM.classList(input))
|
||||
.toEqual(["ng-binding", "ng-pristine", "ng-invalid", "ng-touched"]);
|
||||
.toEqual(["ng-binding", "ng-invalid", "ng-pristine", "ng-touched"]);
|
||||
|
||||
input.value = "updatedValue";
|
||||
dispatchEvent(input, "change");
|
||||
|
|
Loading…
Reference in New Issue