how can I invoke an ember component dynamically via a variable?

Lets say I have an array of widget objects on my controller and each widget object has member variable that is assigned the name of a component class. How can I get my template to invoke that component?

//widgets[0].widget.componentClass="blog-post"

{{#each widget in widgets}}
    {{widget.componentClass}}
{{/each}}

Obviously the above example just spits out a series of string versions of the widget component classes. This however does work (as long as you got everything set up right):

//widgets[0].widgets.viewClass="blogPost"

{{#each widget in widgets}}
    {{view widget.viewClass}}
{{/each}

That was our previous implementation, but we weren't happy with it. We're currently using a custom {{renderWidget ...}} tag with a handlebars helper as described here: Calling Handlebars {{render}} with a variable name. The default render helper has a similar problem where it would not invoke a render on the contents of a variable name. I'd be willing to write a custom component handlebars helper but I can't even figure out where to start. Thanks.


Solution 1:

I tried this and it seems to work, but its just a lot of guesswork on my part:

Ember.Handlebars.registerHelper('renderComponent', function(componentPath, options) {
  var component = Ember.Handlebars.get(this, componentPath, options),
      helper = Ember.Handlebars.resolveHelper(options.data.view.container, component);

  helper.call(this, options);

});

and you use it the same way:

{{#each widget in widgets}}
  {{renderComponent widget.componentClass widget=widget}}
{{/each}}

Solution 2:

In Ember 1.11, the new component helper allows you to do this:

{{#each widget in widgets}}
  {{component widget.componentClass}}
{{/each}}

As of today, Jan 19th, 2015, 1.11 is not a stable release but this feature is in the canary version.

Solution 3:

Sounds like I've run into a lot of the same problems as you. All components are registered as top level helpers, which means you can do a similar method to the one you linked of creating a handlebars helper that does the lookup. Like this:

Ember.Handlebars.registerHelper('lookup', function(component, options) {
  component = Ember.Handlebars.get(this, component, options);
  Ember.Handlebars.helpers[component].call(this, options);
});

Then in your template:

{{#each widget in widgets}}
  {{lookup widget.componentClass}}
{{/each}}

Here's a jsbin with a working example: http://jsbin.com/ucanam/2482/edit

Hope that helps!

-- edit --

For some reason, the new version of handlebars makes calling helpers from other helpers impossible. Instead you can lookup the template through the Ember.TEMPLATES global. I've updated the JSBin to use this method.

You can also get the template via options.data.view.templateForName(component), but it feels a bit more brittle than Ember.TEMPLATES.

-- edit 2 --

It's changed again. Ember.Handlebars.resolveHelper is now the correct way to do it. See @StrangeLooper's answer.

Solution 4:

If you are using Ember CLI and Coffeescript here is a version for that. Create the following file in app/helpers/render-component.coffee:

renderComponent = (componentPath, options)->
  helper = Ember.Handlebars.resolveHelper(options.data.view.container, componentPath)
  helper.call this, options

`export { renderComponent }`
`export default Ember.Handlebars.makeBoundHelper(renderComponent)`

From there, you can call {{render-component "foo-bar"}} from a template.

Since the Ember ecosystem is ever changing, here is the version I tested it on:

  • Ember-CLI v0.0.43
  • Ember v1.7.0
  • Handlebars 1.3.0