Tracking of states/properties in components within vertical-collection
When the addon is adding and removing components with nested subcomponents, I'm noticing that if we quickly go to the bottom then to the top, it's mixing up the states of each individual component instance. To give some details on the set up of this list, we have a vertical-collection of activity-card components:
{{#vertical-collection activities containerSelector="body" lastReached=showInfinityLoader as |activity index|}}
{{activity-card activity=activity}}
{{/vertical-collection}}
In each activity-card component, amongst the many types of activities that exists, it's possible to have a message type activity that shows contents of a message sent through the application, and if it's longer than 100 characters, we set a property "fullMessageShown" to false. When a user clicks "Show more" it sets that property to true.
When I'm following closely in the inspector to see what's going on, it looks like vertical-collection is re-using the component containers (at random) that has the state of that component's instance to display new content. This behavior reflects the description on your Medium blog post going in depth on how this was implemented:
To accomplish this, vertical-collection only ever updates the content of the components for Glimmer and Ember to rerender. The ordering of non-updated components, and their content, remains intact. The changed components are then manually moved. (This works because Glimmer only needs to maintain a stable reference to an existing DOM node, similar to the way that ember-wormhole works.)
Therefore, whichever container that now has the new content, the properties are preserved so using the example before, the activity component instance that had "fullMessageShown" set to true, can hold random new content and so when we scroll back up, it's possible that the activity component now is rendered in a different container so the state of "fullMessageShown" is not correct.
It doesn't seem like there's a way to do this in the options the plugin exposes, so what would be a good workaround for this if we need to keep the states/properties of the instance in sync with the content of the card? The only option we seem to have is populating an array or object with the corresponding IDs and states, and managing these states independent of the component instance.
Hey, thanks for bringing this up! This is actually a very common issue with occlusion since we must reuse as much as possible to be performant, which results in us reusing component instances entirely as you noticed.
There are two main solutions for this right now:
-
Set the
shouldRecycleoption to false. This will cause the collection to completely teardown and recreate component instances each time, which is a bit of a performance regression. However, this is the only way to get certain functionality, like theinithook or{{unbound}}helper, to work properly since they work when the component is first created. -
The second method is to create a proxy object that wraps each item which is passed into the collection and stores the state associated with that object:
{{#vertical-collection proxies as |proxy|}} <!-- access the actual item --> {{#if proxy.fullMessageShown}} {{proxy.item.fullMessage}} {{else}} {{proxy.item.short}} {{/if}} <!-- storing transitory state --> <button {{action (toggle proxy.fullMessageShown)}}>Toggle</button> {{/vertical-collection}}Now, doing this would create another
nitems if you just wrapped the items outright, and you would have to teardown/recreate those items each time you rendered the collection, so that's not ideal at all. You can instead create a proxy object which usesunknownPropertyandsetUnknownPropertyto set the values on a central map of some sort, which allows you to only create as many objects as are rendered at any given time. We've done something like this in the new version of Ember Table: https://github.com/Addepar/ember-table/blob/master/addon/utils/cell-proxy.js
The plan is to fully document both of these solutions for v1.0.0. We'll provide a cookbook for solution 2 which will demonstrate a full minimal example on how to create the proxy objects and use only as many as are rendered.
Any plans on making shouldRecycle a public property? It's still not documented right now.
Thanks!