Given a host containing:
<template is="dom-repeat" items="{{obj.strings}}">When the host observes a change, it will pass the value obj.strings to the repeat. The effect is something like:
dom-repeat.items = obj.strings;From the repeat's perspective, the value is a simple array. The repeat has no knowledge of obj.
In the dan-demo application, the value of obj.strings is not actually changing.
That is to say, even though the strings inside are changing, the value itself
always references the same Array object.
To be fast, Polymer uses simple equality to dirty-check property values. Since the value of
obj.strings isn't changing, that means dom-repeat.items doesn't change, so the repeat does
not update.
Solution one: clone strings in _computeObject. The string data themselves are not copied,
so it's not as expensive as it sounds. Now the value of items will change and the repeat will
re-render.
Example:
_computeObject: function(strings, foo) {
console.log("_computeObject was called");
return {
strings: strings.slice(),
foo: foo
};
},Normally, Polymer makes up for the simple equality check by observing specific alterations to
structured objects (either via bindings, or the set or push|pop|... methods). Correctly,
pk-string-input-array uses these APIs to manipulate it's strings property. However, Polymer
is not aware of the relationship between strings and computedObject.strings because that
equivalence is done imperatively. Iow, this code
return {
strings: strings,
foo: foo
};assigns strings to computedObject.strings in a way Polymer does not see.
Solution two: directly notify Polymer about the linkage between strings
and _computedObject.strings. This solution yields the best performance
as Poymer can update the DOM based on specific changes.
Example:
_computeObject: function(strings, foo) {
this.linkPaths('computedObject.strings', 'strings');`
return {
strings: strings,
foo: foo
};
},Other possible solutions:
- set
_computedObjectto null before recomputing the value (clearing the caches) - observe
object.strings.*in the main view and callrenderon the repeat
It's possible Polymer could avoid this problem in the future by ignoring the persistence of _computedObject.strings
when _computedObject itself changes, although this will de-optimize some scenarios. The Polymer Core team is considering
options.
Thank you for this explanation; that makes sense that Polymer was just assigning the object subcomponents and checking them for equality directly.
Based on advice from Rob, I've since refactored my app so that rather than depending on changes from the computed property, I have the subcomponent manually fire events with the relevant changes. Hopefully by avoiding 2way bindings across element boundaries, it will become easier to reason about how the data gets used to perform updates.
I am curious to learn more about the
linkPathcall in solution 2; are there any tutorials or documents you can point me to that show where it is most appropriately used? Since the call allows one to make new couplings between state that could be hard to track down later, it seems like it has the potential to make the codebase hard to maintain.