In the past year, I’ve spent a lot of time developing a large, complex single-page app using Ember.js. One of the challenges when dealing with a complex SPA is organizing the many views and components within the app, especially when dealing with naturally “typed” data.
In this situation we often found we wanted a different view or component based on the type of the data being presented. The advantage to this is it keeps our templates, controllers, and components from growing out of control.
In this post I’ll describe how my team solved the problem of dynamically binding different components based on typed data and also describe a great new helper that is coming in Ember.js 1.11, {{component}}
.
The Problem
The problem we wanted to solve was how to dynamically render a different component based on some bound property. In our app, we had a data model that represented arbitrary operations. Each operation had a “type” property.
We needed components that would let users configure specific options on the operation, but each type warranted a different component, with different UI elements. For instance, when type was typea
we might have two numerical inputs in our component, but when it was typeb
there was a select and a custom graph.
All told, we have over 15 types. Including all these in one gigantic chain of if
blocks in one template file was ugly and not very D.R.Y. We needed something better.
Option A: the {{dynamic-component}} Helper
One nice option that was, unfortunately, not available to us is the {{dynamic-helper}} component. This is a third-party library that lets you add a component based on some computed property:
{{dynamic-component type=typeWidget}}
While the controller has a typeWidget
property:
export default Ember.Controller.extend({
typeWidget: function() {
return this.get('type').lowercase() + "-widget";
}.property('model.type')
})
Using this helper, when model.type
is typea
, a typea-widget (a component) will be rendered. Similarly, when model.type
is typeb
, a typeb-widget will be rendered. Unfortunately, not all projects use ember-cli, so it would be nice to have other options available. (It’s also being deprecated; more on that later!)
Option B: Cheat Using {{partial}}
Another trick to use is the {{partial}}
helper. Partial lets you slap in any template you want—just pass it the name of the template, and it will use the current model, view, and controller. The approach here is to bind the name of the partial to the computed property, then (in each partial file) call the actual component you want to render:
{{partial typePartial}}
And the controller looks like:
export default Ember.Controller.extend({
typePartial: function() {
return this.get('type').lowercase() + "-partial";
}.property('model.type')
})
Finally, the partial template (e.g., typea-partial.hbs
, etc.):
{{component typea-widget model=model}}
This approach works, but there is the added complexity of the partial files. However, it does have the advantage of avoiding a big chain of if
blocks in the the calling template. When new types come into the system, the developer needs to add the template for the partial, the template for the component, and then (optionally) the javascript for the component.
Option C: In Ember 1.11+, use {{component}}!
The reason {{dynamic-component}}
has been deprecated is, as of Ember 1.11, a {{component}} helper has been made available. It works similarly to {{dynamic-component}}
:
{{component typeWidget model=model}}
While the controller has a typeWidget
property:
export default Ember.Controller.extend({
typeWidget: function() {
return this.get('type').lowercase() + "-widget";
}.property('model.type')
})
The above code will render the typea-widget
, typeb-widget
, etc. and pass along any other bindings as you would do with a normal component. The component will change dynamically as the computed property changes.
In the future, I expect to use the {{component}}
helper often for situations like this, and it’s yet another way that Ember.js helps us keep our code succinct and easy to manage.
This will be fantastic for modals, as well as lists with different types of content in adjacent spaces. Thanks for the guide!
I’m glad it was helpful! I’m really looking forward to this helper.
Hey!
Thanks for the great example. Are you going to do some refactoring of your code to use the new component helper with your existing application? Or just for the future?
Hi Erik, all other things being equal, yes I will refactor. In the example I provide in this blog (using dynamically binding a partial name) it’s pretty easy to do.
1) delete the partial
2) change the template that was calling the partial to instead use {{component}}
3) if needed, change the computed property that {{component}} binds to to match the component names
Not too bad. To refactor out of dynamic-component, you just change the helper.
Ember is changing a lot right now and there is value in not falling too far behind.
Are you defining this somewhere?
}.property(‘model.type’)
Good catch, since this was pseudo code that slipped through the cracks. It should be ‘model.type’ throughout with the assumption that the Route has provided the data.