Emberjs Handlebars #each helper slow when bound to computed properties - ember.js

I'm running into a performance issue when I render a list of items using the #each helper or a collection view bound to some computed properties of an Ember.ArrayController. Performance is fine with a small list of 10 - 20 items, but around 50 - 100 it starts to lag quite noticeably. Try checking off a few todos or clicking "Add Todo"
Example code is here: http://jsfiddle.net/Jonesy/ed3ZS/4/
I noticed that the childViews in the DOM get re-rendered with each change, which could very well be the intended behaviour at the moment, but I'd prefer to be able to just have a todo be removed from the DOM of unfinished todos list individually and appended to the bottom of the finished todos list, which would in theory be much less costly.
What I'm hoping to have answered is whether am I looking at a performance issue with Ember collection views, or is displaying a list populated from a computed property a bad idea, and if so, will I need to manually manage the todo model's location in the view layer as it changes from unfinished to finished and vice versa.

This is a side-effect of how {{#each}} (and CollectionView, which is what powers it) works.
Internally, CollectionView uses something called array observers. An array observer allows you to subscribe to mutations made to an array when they are done using Ember.Array's mutation methods (replace, pushObject, popObject, etc.) The API for array observers is described here.
What this means is that, if you push a new object into a collection view, it will insert render one new element in the DOM and leave the rest in place.
In the example you posted, however, the array is not being mutated--you're creating a brand new Array object every time a new item is added or removed. When the binding synchronizes, it replaces the old array with the new array. To {{#each}}, this is no different than removing all of the elements and then adding them back in.
The solution to the problem is to use a single array, instead of a computed property that returns a different array object each time it changes. You can see the Contacts app for an example of how to do this.
Obviously this is a very common pattern, and we'd like to add some kind of filtering that does the right thing by default to Ember.ArrayController down the road.

Related

Can’t update existing view with each-in helper

We are creating a Sportsbook web app using Emberjs, where we retrieve data through an API and draw the view.
The received data is an object with many nested levels. Based on this object we have constructed the template file(.hbs) using each-in helper!
We are unable to use 'each' helper as we are receiving object, not array!
After the first retrieval, we are receiving updates every milliseconds and need to update the view as well by changing only the difference.
Sometimes we are receiving new entries in the object, and as the object itself is big, we need to add the new entry to the layout without redrawing the whole stuff.
Here is the problem: Each-in is not observable in this case, so it is not redrawing after pushing the new object.
Question: Is there a good way to implement this with Ember or is there a known helper that we can use instead of each-in, OR is there someone who can recommend me if we CAN create a new helper for this?

Ember.js: sending actions to components?

I have a mixin for Ember components, named in-view, the job of which is to request that that the element be brought in view. It is provided an attribute whose value is an piece of content to be brought into view, and if that attribute matches the component's content then I call scrollIntoView or the equivalent. The code looks something like this:
// calling template
{{#each items as |item|}}
{{my-item content=item inViewItem=inViewItem}}
}}
// mixins/in-view.js
scrollIntoView() {
if (this.get('content') === this.get('inViewItem'))
this.get('element').scrollIntoView();
}.on('didInsertElement')
// components/my-item/component.js
import InView from 'mixins/in-view';
export default Ember.Component.extend(InView,
This works fine. The question I have arises when I want to change the item in view. I can have the in-view mixin observe the inviewItem attribute:
}.on('didInsertElement').observes('inViewItem')
and this also works, but seems like a bit of a code smell.
In addition, my actual code structure is that there is a controller which knows which item is supposed to be in view, and then its template calls a my-item-list component which displays the scrollable div containing the item list, and that in turn calls the my-item component. This means I have to pass the inViewItem attribute from the controller down through two levels, as in
// resource/controller.js
inViewItem: something
// resource/template.js
{{my-item-list items=item inViewItem=inViewItem}}
// components/my-item-list/template.js
{{#each items as |item|}}
{{my-item content=item inViewItem=inViewItem}}
}}
I could avoid this by having the my-item template hard-wired to access the inViewItem attribute on the controller:
scrollIntoView() {
if (this.get('content') === this.get('controller.inViewItem'))
this.get('element').scrollIntoView();
}.on('didInsertElement')
but that's another code smell; I don't want to build this kind of dependency on a specific controller field into the mixin. Instead I could possibly pass the component the name of the controller attribute to watch, but this seems unduly clumsy, and it's tricky to observe an attribute whose name is variable. More importantly, I don't think this will work when controllers go away in 2.0.
What I want essentially is a way to "ping" or somehow send a message to a template. I know that in principle this violates the DDAU principle, but in this particular case what I need is exactly to somehow send an "action down"--an action telling the component to adjust itself to bring itself into view.
Of course, I could give up on the entire idea of the in-view mixin and simply have the controller dig down into the generated HTML to find the item to bring into view and issue the scrollIntoView on it directly. However, this seems to violate some principle of separation of concerns; the my-item template would no longer be in complete control of itself.
What is the recommended design pattern for this kind of case?
The solution here is to go the opposite direction that you have. Your component here is a localized scope, and the pain you are feeling is that your localized scope needs to access and mutate global state (the app's scroll position).
Some people use a scroll-service for keeping track of and mutating state, I've used several variations on that myself.
It sounds though like you're dealing with a scrollable list, perhaps a div, and that what item is in view isn't merely a function of page state, but programmatically may change. For instance, a new item has been inserted and you want to scroll the new item into view.
A plugin like jquery.scrollTo or similar (collectively "scroller") would be better for that than simply jumping to the new position as it preserves the user's contextual awareness to where they are on page.
With a scrollable div or list or similar, you might choose to have your top level component control scroll state. The scroll state is still localized in this case, but instead of being localized to each item it's been localized to the scrollable region as a whole, which is where it better belongs.
There are a number of patterns for list items to register themselves with a parent list-component. In a robust scenario, I might do so, but a quick and not very dirty approach is to do something wherein on didInsertElement the new child emits an action to the parent containing it's context, which the parent then uses to check if it's the active item and if so triggers the scrollTo.

Binding to an array in ember with #each

I was reading about how to observe an array and do something when elements were added or removed. See the official explanation here. The page says that if we observe with anArray.#each then it should fire in the case of adding an object to the array.
I tested that here and had two problems:
The observer I put on the component did not fire.
The dom did not update its displaying of the length of the array.
Any help appreciated, thanks.
You need to use pushObject when adding items to an array in Ember, it's how Ember is able to keep track of whether or not the array has changed. Likewise you need to always use get/set on your objects, also Ember's way of knowing that a property has changed on an object.
this.get('testArray').pushObject('foo');
http://emberjs.jsbin.com/hobarenu/2/edit

stop meteor rerender every child template inside of each block when find query changes?

i'm rendering a list of elements based on a collection on the meteor server.
the elements are rendered using an #each block which is populated from a helper method that returns a cursor which is the result of a find query. in order to sort the list based on some attribute of its data i run a new find with a different sort option.
this works fine and the list is rendered in the desired order. the problem is meteor is rerendering each element.
now i have two problems:
1) the elements can contain an embedded video that is playing. this is reset when meteor rerenders the element
2) i want to have some css transitions animate the repositioning of the elements. since meteor rerenders every element in the dom, the css transitions don't work.
i was partially able to solve problem #1 using a constant block but problem #2 remains a mystery. i could do all the sorting and filtering client side without relying on meteor but that seems clumsy.
is there any way that meteor can just reposition the elements in the dom instead of removing and readding them?
in the documentation for renderList i found this (which makes it seem like meteor could at least theoretically do what i want):
renderList is more efficient than using Meteor.render to render HTML for a list of documents. For example, if a new document is created in the database that matches the query, a new item will be rendered and inserted at the appropriate place in the DOM without re-rendering the other elements. Similarly, if a document changes position in a sorted query, the DOM nodes will simply be moved and not re-rendered.

Container View - reorder child views

Based on the docs, I'm able to rearrange child views A,B,C of a container view to C,A,B by doing a "removeObject(A/B/C)" followed by a "pushObject(C/A/B)". For larger sets (my actual example is currently 64), is there a possibly more efficient way (such as say, just modifying a couple of values in the existing child views array? Although the remove/pushObject is probably just fine, given the relatively larger time for the actual UI re-render.
Thanks so much.
If you were using an ArrayController in combination with a CollectionView, you could use SortableMixin support to order its content according to your needs. Then your collection rendering would be updated as needed without any intervention on your side.
You could, for example, bind the sortProperties property of you controller to reflect the sorting criteria to apply.
(If you need further explanation, some code extract from your app will be welcome)