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
_computedObject
to null before recomputing the value (clearing the caches) - observe
object.strings.*
in the main view and callrender
on 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
linkPath
call 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.