What's the best way to handle bindings?


#1

I’m trying to figure out a clean, intuitive way to handle data binding. Right now, we have a lot of code that looks like this:

# /app/controllers/foo.coffee
FooController = Ember.Controller.extend({
  foo: something
})

# /app/template/foo.hbs
{{#view 'bar' bazBinding=foo}}

# /app/views/baz.coffee
BazView = Ember.View.extend({
  computedProperty: (->
    return "baz is: #{baz}"
  ).property('baz')
})

The problem is, when new people look at the code, they don’t know where baz is coming from in BazView. To make matters worse, we have a lot of views that have bound properties to parent views. So, if you’re looking in the child view, you have to track down a bound property though it’s parent view’s template then the template the parent view was rendered in to finally get to the property on the controller that you’re ultimately bound to. We haven’t upgraded to an Ember version with components yet – I’m working on it now – but it seems like this will still be an issue when we move away from views and start using components.

Any best practices here? I like the “data down; actions up” motto and have thought that it would be good to start using one way bindings to follow that advice:

BazView = Ember.View.extend({
  bazBinding = Ember.binding.oneWay('controller.foo')
})

This seems like it will make it easier for new people to pick up the code because they will only need to look in the view’s file to understand how values are bound and they won’t need to track down the templates as well. Basically, it cuts the number of files you need to grep in half.

Any other tips, tricks, or hints would be greatly appreciated.

Thanks!


#2

I don’t know that there is a good way to avoid the long chain of pass the variable if you have a deeply nested component that needs data from something at the top of the hierarchy. I can tell you that controllers are going away so the idea of short circuiting to reference to the controller is probably not the most future proof option. Components are all about explicitly passing things in, any references to parentView or nearestOfType etc etc are not considered best practice.

Occasionally you can avoid the chain by using the yield functionality of a component. This works in cases where the intermediate components don’t need access to the data. Although if you’re not declaring the component at the highest level then you will have to pass the yield up which is just as confusing as passing the data down.

// my-component.hbs
{{yield dataManipulatedByTheComponent}}

// my-high-level-template.hbs
{{#my-component as |dataManipulatedByTheComponent|}}
   Display {{dataManipulatedByTheComponent}}
{{/my-component}}

The other potential option if you really want to avoid explicit passing data around is to use a service. Since a service is a global and can be injected wherever you could use it in the same way as you’re using controller in your example. That maybe a bit of an anti-pattern though.


#3

Thanks. This is very helpful. It’s too bad that controllers are being deprecated entirely. I’ve always found it very useful to have views communicate with each other through the controller. I’ve implemented a table where a row will display a certain style based on the state of it’s td’s and trying to pass the data to the TRView was madness while it was trivial to calculate in the controller.


#4

For situations where you have state information that would have to be maintained via actions I’ve moved the state info into a model object. In your example of TR and TD views, I would probably create a TR model that has a computed property for the style based on an array of TD data. Something like:

//tr-component.js
isRowError: computed('tdModels.@each.isError', function(){
    @get('tdModels').findBy('isError', true)
})

{{! tr-component.hbs }}
<tr style="{{if isRowError "error"}}">
  {{#each tdModels as |tdModel|}}
    {{tdView model=tdModel}}
  {{/each}}
</tr>

If you encounter performance issues you can always optimize away from a computed but maintain the same interface.


#5

I actually just saw Stephan Penner talk at the SF Ember Meetup. He threw out the idea that services can be used for “ambient” data (basically what I use the controller for now). I think this makes a lot of sense. It lets you isolate components so that they just know about themselves and, for any data that needs to be shared between components, you’d use a service.

A colleague and I came up with a pretty similar solution independently. Our idea was to create a pub-sub service that could be used for inter-component communication.