Polymer 2.x: dom-repeat 不观察计算数组子项的变化
Polymer 2.x: dom-repeat does not observe changes to computed array's children
我有一个 dom-repeat
和另一个 dom-repeat
嵌套在里面。外层绑定到一个计算数组。当我对父数组进行更改时,会观察到更改(意味着触发观察器代码),但更改不会反映在 UI/dom-repeat
中。
这是我的代码示例。单击第二个 dom-repeat
内的 div
会触发函数 handleItemTap
,然后该函数会更改父数组。然后观察到这种变化,并重新计算计算出的数组;但是,将 IsChecked
添加到父对象不会影响 UI 中的复选框,它应该绑定到 IsChecked
属性。我做错了什么?
<template is="dom-repeat"
items="[[outerComputedArray]]" as="group"
id="groupRepeater">
<div class="group-container">
<template is="dom-repeat"
items="[[group.InnerArray]]" as="sesh">
<div on-tap="handleItemTap">
<paper-checkbox id="paperCheckboxId"
checked="[[sesh.IsChecked]]"></paper-checkbox>
</div>
</template>
</div>
</template>
...
static get properties() {
return {
'parentArray': Array,
'outerComputedArray': {
type: Array,
computed: 'computeOuterArray(parentArray.*)'
}
}
}
computeOuterArray(parentArray) {
if (!(typeof parentArray.base === 'undefined' || parentArray.base == null || parentArray.base.length == 0)) {
let groupedGrid = parentArray.base.reduce((a, b) => {
a = a || [];
let idx = a.findIndex(x => x.PaymentType == b.PaymentType);
if (idx == -1) {
let newArray = [];
newArray.push(b);
a.push({ 'PaymentType': b.PaymentType, 'InnerArray': newArray });
}
else {
a[idx].InnerArray.push(b);
}
return a;
}, []).map(x => {
x.Total = x.InnerArray.map(y => y.TotalAmount).reduce((a, b) => a + b, 0);
return x;
});
groupedGrid.sort((a, b) => a.PaymentType.localeCompare(b.PaymentType));
return groupedGrid;
}
return [];
}
handleItemTap(e) {
let idx = this.parentArray.findIndex(x => x.id == e.model.sesh.id);
this.set(`parentArray.${idx}.IsChecked`, !(!!e.model.sesh.IsChecked));
}
看来 Polymer 对象 dirty-checking 是问题所在。它用于加速 Polymer,但有时 dirty-checking 不起作用(例如,当您更改数组中的元素时)。
要绕过它,您需要使用 mutable data mixin.
首先,您需要在声明元素时引入mixin。
class MyMutableElement extends Polymer.MutableData(Polymer.Element) { ... }
您还需要将 mutable-data 标记添加到您的 dom-repeat。
<template is="dom-repeat" items="[[outerComputedArray]]" as="group" id="groupRepeater" mutable-data>
如果它仍然不起作用,请尝试在 computeOuterArray 函数的末尾添加类似这样的内容(但前提是您确实更改了数组)。
timeout(() => { this.notifyPath('outerComputedArray') }, 10)
非常感谢@mary-shaw 的帮助,但我无法让这些建议发挥作用。这是我现在想出的。我在 outerComputedArray
上放置了一个观察者,用于更新第二个数组——一个名为 selectedIds
的新 属性——包含 属性 IsChecked
的任何项目已经变了。然后我添加了另一个计算函数来检查 selectedIds
是否与最里面的对象 dom-repeat
.
具有相同的 id
属性
<template is="dom-repeat"
items="[[outerComputedArray]]" as="group">
<div class="group-container">
<template is="dom-repeat"
items="[[group.InnerArray]]" as="sesh">
<div on-tap="handleItemTap">
<paper-checkbox id="paperCheckboxId"
checked="[[computeIsChecked(selectedIds.*, sesh.id)]]"></paper-checkbox>
</div>
</template>
</div>
</template>
...
<script>
static get properties() {
return {
'parentArray': Array,
'outerComputedArray': {
type: Array,
computed: 'computeOuterArray(parentArray.*)'
},
'selectedIds': Array
}
}
static get observers() {
return [
'onOuterComputedArrayChanged(outerComputedArray.*)'
]
}
onOuterComputedArrayChanged(outerComputedArray) {
if (!(typeof outerComputedArray.base === 'undefined' || outerComputedArray.base == null || outerComputedArray.base.length == 0)) {
this.set('selectedIds', outerComputedArray.base.filter(x => x.InnerArray.some(y => !!y.IsChecked)).map(x => x.InnerArray.filter(y => !!y.IsChecked)).reduce((a, b) => a.concat(b), []).map(x => x.id));
}
}
computeIsChecked(selectedIds, id) {
if (!(typeof selectedIds.base === 'undefined' || selectedIds.base == null || selectedIds.base.length == 0)) {
return selectedIds.base.some(x => x == id);
}
return !!0;
}
computeOuterArray(parentArray) {
if (!(typeof parentArray.base === 'undefined' || parentArray.base == null || parentArray.base.length == 0)) {
let groupedGrid = parentArray.base.reduce((a, b) => {
a = a || [];
let idx = a.findIndex(x => x.PaymentType == b.PaymentType);
if (idx == -1) {
let newArray = [];
newArray.push(b);
a.push({ 'PaymentType': b.PaymentType, 'InnerArray': newArray });
}
else {
a[idx].InnerArray.push(b);
}
return a;
}, []).map(x => {
x.Total = x.InnerArray.map(y => y.TotalAmount).reduce((a, b) => a + b, 0);
return x;
});
groupedGrid.sort((a, b) => a.PaymentType.localeCompare(b.PaymentType));
return groupedGrid;
}
return [];
}
handleItemTap(e) {
let idx = this.parentArray.findIndex(x => x.id == e.model.sesh.id);
this.set(`parentArray.${idx}.IsChecked`, !(!!e.model.sesh.IsChecked));
}
</script>
我有一个 dom-repeat
和另一个 dom-repeat
嵌套在里面。外层绑定到一个计算数组。当我对父数组进行更改时,会观察到更改(意味着触发观察器代码),但更改不会反映在 UI/dom-repeat
中。
这是我的代码示例。单击第二个 dom-repeat
内的 div
会触发函数 handleItemTap
,然后该函数会更改父数组。然后观察到这种变化,并重新计算计算出的数组;但是,将 IsChecked
添加到父对象不会影响 UI 中的复选框,它应该绑定到 IsChecked
属性。我做错了什么?
<template is="dom-repeat"
items="[[outerComputedArray]]" as="group"
id="groupRepeater">
<div class="group-container">
<template is="dom-repeat"
items="[[group.InnerArray]]" as="sesh">
<div on-tap="handleItemTap">
<paper-checkbox id="paperCheckboxId"
checked="[[sesh.IsChecked]]"></paper-checkbox>
</div>
</template>
</div>
</template>
...
static get properties() {
return {
'parentArray': Array,
'outerComputedArray': {
type: Array,
computed: 'computeOuterArray(parentArray.*)'
}
}
}
computeOuterArray(parentArray) {
if (!(typeof parentArray.base === 'undefined' || parentArray.base == null || parentArray.base.length == 0)) {
let groupedGrid = parentArray.base.reduce((a, b) => {
a = a || [];
let idx = a.findIndex(x => x.PaymentType == b.PaymentType);
if (idx == -1) {
let newArray = [];
newArray.push(b);
a.push({ 'PaymentType': b.PaymentType, 'InnerArray': newArray });
}
else {
a[idx].InnerArray.push(b);
}
return a;
}, []).map(x => {
x.Total = x.InnerArray.map(y => y.TotalAmount).reduce((a, b) => a + b, 0);
return x;
});
groupedGrid.sort((a, b) => a.PaymentType.localeCompare(b.PaymentType));
return groupedGrid;
}
return [];
}
handleItemTap(e) {
let idx = this.parentArray.findIndex(x => x.id == e.model.sesh.id);
this.set(`parentArray.${idx}.IsChecked`, !(!!e.model.sesh.IsChecked));
}
看来 Polymer 对象 dirty-checking 是问题所在。它用于加速 Polymer,但有时 dirty-checking 不起作用(例如,当您更改数组中的元素时)。 要绕过它,您需要使用 mutable data mixin.
首先,您需要在声明元素时引入mixin。
class MyMutableElement extends Polymer.MutableData(Polymer.Element) { ... }
您还需要将 mutable-data 标记添加到您的 dom-repeat。
<template is="dom-repeat" items="[[outerComputedArray]]" as="group" id="groupRepeater" mutable-data>
如果它仍然不起作用,请尝试在 computeOuterArray 函数的末尾添加类似这样的内容(但前提是您确实更改了数组)。
timeout(() => { this.notifyPath('outerComputedArray') }, 10)
非常感谢@mary-shaw 的帮助,但我无法让这些建议发挥作用。这是我现在想出的。我在 outerComputedArray
上放置了一个观察者,用于更新第二个数组——一个名为 selectedIds
的新 属性——包含 属性 IsChecked
的任何项目已经变了。然后我添加了另一个计算函数来检查 selectedIds
是否与最里面的对象 dom-repeat
.
id
属性
<template is="dom-repeat"
items="[[outerComputedArray]]" as="group">
<div class="group-container">
<template is="dom-repeat"
items="[[group.InnerArray]]" as="sesh">
<div on-tap="handleItemTap">
<paper-checkbox id="paperCheckboxId"
checked="[[computeIsChecked(selectedIds.*, sesh.id)]]"></paper-checkbox>
</div>
</template>
</div>
</template>
...
<script>
static get properties() {
return {
'parentArray': Array,
'outerComputedArray': {
type: Array,
computed: 'computeOuterArray(parentArray.*)'
},
'selectedIds': Array
}
}
static get observers() {
return [
'onOuterComputedArrayChanged(outerComputedArray.*)'
]
}
onOuterComputedArrayChanged(outerComputedArray) {
if (!(typeof outerComputedArray.base === 'undefined' || outerComputedArray.base == null || outerComputedArray.base.length == 0)) {
this.set('selectedIds', outerComputedArray.base.filter(x => x.InnerArray.some(y => !!y.IsChecked)).map(x => x.InnerArray.filter(y => !!y.IsChecked)).reduce((a, b) => a.concat(b), []).map(x => x.id));
}
}
computeIsChecked(selectedIds, id) {
if (!(typeof selectedIds.base === 'undefined' || selectedIds.base == null || selectedIds.base.length == 0)) {
return selectedIds.base.some(x => x == id);
}
return !!0;
}
computeOuterArray(parentArray) {
if (!(typeof parentArray.base === 'undefined' || parentArray.base == null || parentArray.base.length == 0)) {
let groupedGrid = parentArray.base.reduce((a, b) => {
a = a || [];
let idx = a.findIndex(x => x.PaymentType == b.PaymentType);
if (idx == -1) {
let newArray = [];
newArray.push(b);
a.push({ 'PaymentType': b.PaymentType, 'InnerArray': newArray });
}
else {
a[idx].InnerArray.push(b);
}
return a;
}, []).map(x => {
x.Total = x.InnerArray.map(y => y.TotalAmount).reduce((a, b) => a + b, 0);
return x;
});
groupedGrid.sort((a, b) => a.PaymentType.localeCompare(b.PaymentType));
return groupedGrid;
}
return [];
}
handleItemTap(e) {
let idx = this.parentArray.findIndex(x => x.id == e.model.sesh.id);
this.set(`parentArray.${idx}.IsChecked`, !(!!e.model.sesh.IsChecked));
}
</script>