The current plan for Ember is to obviate the need for Controllers (top-level and item controllers) with components.
I believe many use cases for an itemController can be satisfied with a component that holds the state that the itemController would have. However, with itemControllers, ArrayControllers have access to their models wrapped in itemControllers which makes for some pretty nice management of items in an ArrayController.
Here is one example that I have where using an itemController seemed helpful. What is a good way to do this without an itemController?
Letâs not put the isSelected property in the original model. Letâs say there is a naming collision where items can be selected in other parts of the app.
Letâs avoid fragile bookkeeping that might break if an item is destroyed. In other words, having items trigger an action like âaddSelectedItemâ and having the ArrayController keep track of a list of selected items seems fragile to me. If an item is destroyed by some other means (letâs say there is a delete button on each item) then we will get errors about dealing with destroyed objects.
Hereâs another example of a type of pattern I frequently use that shows the importance of itemControllers. My models often contain fairly raw data, and I use item controllers to perform calculations and the parent controllers to aggregate the data. In some cases too, Iâll sort and filter based on these calculated properties. Have access to those calculated properties from the parentController is fairly criticalâŚ(at least without an alternative at the moment).
Is the thought perhaps that this type of functionality would become part a service or something? (As I think this sits somewhere between a presentation and model concern).
That is the approach I was thinking of that felt like a lot of bookkeeping to me. One issue I was worried about with this solution it is that there are 2 sources of truth: isSelected on the component and selectedItems on the controller. This means that changes to selectedItems outside of the item component would not be reflected in the checkboxes.
Here is a solution that I think is an improvement. It passes the array of selectedItems into the item component to be manipulated. This way, the selectedItems array is the single source of truth.
In this example, the single source of truth is the âisSelectedâ property on the component. This has a similar feel to the original example using the item controller. Thereâs something about this example that feels kind of funny, but this one feels like the cleanest to me right now.
In both cases, to be more robust I believe we would need to add a function to the component that removes itself from any lists in its willDestroy hook.
Well, suppose I wanted to do this calculation without rendering each of the rowsâŚhow might it look then? What if I wanted to be able to filter this list, but still make sure that the aggregate still includes every unfiltered row?
I can certainly think of many ways to do thisâŚbut, I really like the fact that I can enhance a model without having to be concerned wether it is rendered or not.
Hereâs a few ways Iâd think about how to solve thisâŚthough Iâm really not sure where this functionality is supposed to fall in the MVC/MVVM/MV* pattern.
Perhaps let Ember Data handle this by creating a ModelMixin that is automatically applied to similarly-named models? Then, these models are always upgraded with the added functionality.
Let the Router worry about this with a transformModel hook or something? (This could be done in the setupController hookâŚbut thatâs going away, yes?)
// routes/template-item
import modelMixin from '../mixins/modelMixin';
export default Ember.Route.extend({
model: function() {
// Whatever the model may be.
return model;
},
transformModel: function(model) {
return model.map(function(item) {
return Ember.ObjectProxy.createWithMixins(modelMixin, {
content: item
});
});
});
Let the component worry about this (similar to #2?)?
The most straight forward thing I can think of is to just pass the parent into a component instead of using an itemController:
I believe that there are plans to give components access to their parents. This can be done today using the âparentViewâ property. That would make this kind of pattern pretty trivial.
I read through all the examples in this topic, and I agree that this is the one that felt the best, but it did still feel like there was bookkeeping going on, and this is how I might implement a similar feature in Backbone or some other low level framework.
I donât know whatâs planned for Components in Ember 2.0, but it would be nice if there was a convention to bind an array of Components to their parent component. Maybe with some kind of child-components component:
child-components would be a fancy each, that knows how to bind a list of child components. This would iterate over people rendering the component selectable-item for each, and saving each component to the parent selectable-list in a property called selectedItems.
Finding a way to make something like the above work with block syntax would be pretty cool, though Iâm not sure how that would work.
Something like this could be planned, or it could be a terrible idea, but finding a way to use Components to replace the ViewModel aspect of Controllers is something I havenât completely wrapped my head around.
I agree overall that controllers were confusing when I was new to Ember (they seemed very black-box), but after reading through the Container, Controller, Router, Resolver, and Application source code I understand them better .
@mitchlloyd in both of your examples, the child components are mutating data that was passed into them. I think thatâs the kind of pattern Ember core team wants us to move away from, since it makes things confusing (a component has its data changed out from underneath its feet, and has no idea who did it).
Itâs kind of true that rwjblueâs example has two sources of truth, but kind of not. As soon as you move to a world of nested components, and each component has its own scope, there is going to be the same data in multiple places. But just because weâre using actions to notify parent components of changes, doesnât mean Emberâs bindings system no longer works for us. It still keeps everything in sync. Child components get their data from parent components, so when parent component data changes, child componentsâ UI will be updated.
The most React-esque way I can think of doing this today would be something like this:
SelectableItemList owns the items, and so itâs the one that makes changes to them. Iâm not sure what the API will look like when the changes to Ember 2.0 shake out, but passing the handler in from the parent like this is how React does it. It makes it clear that the parent gets to decide what happens when the child emits that action, which is in line with the âdata down, events upâ approach. The child simply invokes whatever handler was passed into it, giving it the relevant data.
I think my biggest struggle in adopting the âdata down actions upâ approach was that Ember.Checkbox doesnât send useful actions (?) and instead encourages 2-way data binding. I need to look into how that change method on your SelectableItemComponent works â that seems key.
Also I didnât realize that we can already pass functions into our components as you have shown. Really cool.
To be concrete about the 2 sources of truth in rjwblueâs example: I realize that Ember synchronizes data under the hood with Observers, but in this case it was possible to get out-of-sync data if the list of selectedItems was modified outside of the component. In your example howerver, isSelected on each model wrapper is clearly the source of truth here and easy to reason about.
At the heart of this example youâve replaced an itemController with an Ember.Object wrapper inside of the Route which seems like a good approach for a lot of itemController use cases.
Sure thing. Fyi change is just a named event. All of those exist as methods on views & components. Conceptually itâs the same as something like <input {{action 'whatever'}}, and is fired whenever the change event hits (similar to an action that fires on click).
So yeah, the two-way-binding for inputs makes sense. In my example, I donât need a separate action/event handler just to update the SelectableItemComponentâs item with the val from the input. But, we do want to send an event outside that component to its parent.
Like wycats said in a recent changelog podcast, moving towards âdata down, actions upâ doesnât mean throwing out everything weâve learned about two-way bindings. When it comes to a new technique, I think you need to overuse it before you can really identify when itâs appropriate. Two-way bindings felt sort of like a silver bullet, but now weâre realizing they have downsides in various situations. Going forward I expect the Ember community to more narrowly define their role, while encouraging developers to think âdata down actions upâ when it comes to the big picture.
I listened to that Podcast as well, always good to heard Yehudaâs perspective on âthe long gameâ.
By the way, Iâve been producing some Ember training videos with Tilde which is why a little obsessed with changes coming with Ember 2.0 Trying to future proof my stuff as much as possible!
To that end I really wish that I could pass in a function nested under the actions hash of the outer component, but that doesnât seem to work.
Yeah this is just a minor difference here, but itâs mimicking future syntax that was announced in the RFC. Look at the section titled âImproving Actionsâ.
itâs clear that whoeverâs currently rendering the <selectable-item> is simply passing in its handleItemSelected action as a property, just like they would with data binding. The current scope is clear, and so the location (and owner) of that handler is clear.