Failsafe {{component}} helper


#1

Hi, I need to use the {{component componentName}} helper (or an equivalent) but I need to extend it so it can yield if lookup fails.

Ideally something like that:

{#component chartType data=chartData}
  Sorry, but we can't display your data as a {{humanReadableChartType}}.
{/component}

I have been struggling with this the whole day:

  1. with a helper-based approch - not being able to lookup components in a helper,
  2. with a component-based approch - not being able to pass an arbitrary number of arguments from a component to its child without compiling a new template (nope, I won’t be satisfied with that).

I am running out of ideas. I really hope some of you know if/how that could be achieved (@lukemelia? You’ve made the helper, so maybe… :pray: ) or can point me to some code or ember-ci addon.

Thanks for reading!


#2

++ for this. A couple of months ago I tried dealing with the same thing. Also for charts (d3), as it happens. What I tried was something like (from memory):

componentExists: function() {
  return this.container.lookup("component:" + this.get("model.chartType")) !== undefined;
}.property("chartType")


{{#if componentExists}}
  {{component chartType ...}}
{{else}}
  {{mystery-component name=chartType}}
{{/if}}

The mystery-component would just display a message similar to what you have. However, Ember did not like my computed property. As soon as i added it Ember failed, complaining that a completely unrelated computed property was null. I mean, it was failing in a completely unrelated bit of code. I eventually gave up. Perhaps if i refactor to use Ember.computed everywhere it will work. Maybe someday.


#3

I realised that the syntax I proposed is incorrect: in its current form, the yield is transfered to the rendered component (when found).

Obviously, a part of the solution involves its syntax: an if/else-like seems relevant, as in:

{{#component chartType data=chartData}}
  I will yield in the component if it exists
{{#else}}
  Sorry, but we can't display your data as a {{humanReadableChartType}}.
{{/component}}

Or does it?


#4

I implemented it the {{component}} helper itself in this PR: https://github.com/emberjs/ember.js/pull/12250


#5

@mmun rightfully pointed out that it would break {{else}} blocks for components.

So I’ve published instead ember-cli-if-component that implements the feature.

Hope this helps, feedback more than welcome!


#6

Thanks for this addon, I think it’s a good idea. But you have a bug: this will only find components that have a Javascript file. Components that only have a template will not be returned by container.lookup('component:' + name).

Also, you can simplify your addon substantially by providing a helper instead of a component, and just relying on the normal if/else

{{#if (is-component whichComponent) }}
  {{component whichComponent}}
{{else}}
  Sorry!
{{/if}}

Here is a complete helper that solves both issues. Drop this into app/helpers/is-component.js and the above snippet will work:

export default Ember.Helper.extend({
  compute([name]) {
    return this.container.lookup('component-lookup:main').lookupFactory(name, this.container);
  }
})

Helpers are awesome, and people should reach for them more often. :smile:

The helper can be used in many different places that a component can’t go. For example:

<div class="{{#unless (is-component thing) 'chart-unavailable'}}">

#7

Indeed the code is significantly smaller. So small it may not require its own addon, but I’ve nonetheless updated the repo for the record and because it will probably come in handy to someone else.

Thank you for the advice and thorough answer, it helped a lot and I’ve learnt many things in the process!


#8

Thanks, Mate. This is helpful.