What is a good way to do this in Ember 2.0? (No itemController, no ArrayController)


#1

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?

Some things to keep in mind:

  • 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.


Non-context-switching #each with itemView and itemController
Worried about what Ember 2.x will become
#2

First thoughts here: http://emberjs.jsbin.com/rwjblue/128/edit?html,js,output

Probably many other ways though…


#3

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).


#4

@rwjblue Thanks for putting together that JSBin!

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.

Here is another solution that has the component register itself my pushing itself onto an array of items on the controller:

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.


#5

@Spencer_Price Is something like this tenable?

I imaging the new block parameters for components would mean that we wouldn’t need to extract the template into the {{x-row}} component.


#6

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.

  1. 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.
// models/datum.js
export default DS.Model.extend({
  dataA: DS.attr('number'),
  dataB: DS.attr('number')
});

// modelMixins/datum.js
export default DS.ModelMixin.extend({
  average: function() {
    return (this.get('dataA') + this.get('dataB')) / 2;
  }.property('dataA', 'dataB')
});
  1. 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
      });
    });
});
  1. Let the component worry about this (similar to #2?)?
// components/template-item
import modelMixin from '../mixins/modelMixin';
export default Ember.Component.extend({
  wrappedModel: Ember.computed.map('model', function(item) {
    return Ember.ObjectProxy.createWithMixins(modelMixin, {
      content: item
    });
  })
});

#7

Here’s another use case for itemController that I’m not certain how to replace with a component.

In essence, the gist is using an itemController to calculate the the index of the item in the array.


#8

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.


#9

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:

<selectable-list as |selectableList|>
    <button {{action 'deleteSelected'}}>Delete selected</button>

    <child-components each={{people}} belongsTo={{selectableList}} as="selectedItems" component="selectable-item">
</selectable-list>

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 :smile:.


#10

@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.


#11

@samselikoff Thank you! I learned something here.

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.

Really good stuff!


#12

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.


#13

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 :slight_smile: 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.

{{selectable-item item=item onItemSelected=actions.handleItemSelected}}

I suppose that’s why we need to wait for the HTML component syntax:

<input key-press={{action "valueChanged"}}>

But I wish I could shoe-horn this feature in with the {{}} style components. Any insights here?


#14

Yes, I’m with you. No ideas how to do this yet :confused:

And that’s awesome re: the vids, looking forward to checking them out!


#15

actions is moved to _actions at extend time (this is a bit of legacy from pre-1.0 days when controllers had actions in their root).


#16

Thanks @rwjblue that explains it.


#17

What is the idea behind passing a function as a callback instead of just sending an action from the child component ?

Adapting @samselikoff’s example, this would look like: http://emberjs.jsbin.com/gedepugiho/1/edit?html,js,output


#18

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”.


#19

Thanks! I forgot about this section.


#20

I think the main point is to make everything more explicit. When you read

{{selectable-item onItemSelected='selectItem'}}

where does that selectItem go? It bubbles up, but who handles it? These kind of implicit leaps can make things tough to follow.

Instead, just by reading

<selectable-item onItemSelected={{action 'handleItemSelected'}}>

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.