ember.jshtmlbars

Is it possible to recover from HTMLBars exception in Ember template?


I have a dynamic component similar to:

{{component fooProperty owner=this}}

fooProperty is data driven and sometimes comes off incorrectly, at least now in dev, but I'm afraid it may come off incorrectly in prod too (due to app versioning, persisted storage etc). Basically, I don't trust this to be always correct (ie. resolvable to a component). When the value is off, the entire app crashes though:

Uncaught Error: Assertion Failed: HTMLBars error: Could not find component named "some-inexisting-component" (no component or template with that name was found)
 EmberError @ ember.debug.js:19700
 assert @ ember.debug.js:6719
 assert @ ember.debug.js:19502
 componentHook @ ember.debug.js:10894
 render @ ember.debug.js:12782
 render @ ember.debug.js:12732
 handleKeyword @ ember.debug.js:46584
 keyword @ ember.debug.js:46709
 exports.default @ ember.debug.js:12483
 handleKeyword @ ember.debug.js:46545
 handleRedirect @ ember.debug.js:46531
...

I would prefer to catch such exception and prevent the entire app from crashing. I can think of a workaround eg. vetting the fooProperty return against App.__container__.lookup and returning a generic 'missing' component. But I would still prefer if there is a way to capture and handle exception as raised during rendering, if possible.


Solution

  • The simple answer is "No, but you can implement your own." Template engines have never had a role of exception handling. It's relevant not only for Handlebars/HTMLbars case but other template engines like JSX, Jinja, etc. as exception handling has quite a bit logic involved that has to be specified on the user side and it just not seems in the scope of presentation role they have. The main reason for that appears to be a separation of concerns which Ember Framework developers want to enforce to avoid developers ending up with a lot of additional spaghetti code in their apps which can substantially decrease overall code readability and maintainability.

    A possible solution for your case:

    component.js

    import Component from 'ember-component';
    import get from 'ember-metal/get';
    import getOwner from 'ember-owner/get';
    import { isEmpty } from 'ember-utils';
    import computed from 'ember-computed';
    
    export default Component.extend({
        wantToRenderComponentName: 'name-that-doesnt-exist',
        wantToRenderComponentNameExist: computed('wantToRenderComponentName', {
            get() {
                const owner = getOwner(this);
                return !isEmpty(owner.lookup(`component:${get(this, 'wantToRenderComponentName')}`))
            }
        })    
    });
    

    template.hbs

    {{#if wantToRenderComponentNameExist}}
        {{component wantToRenderComponentName}}
    {{else}}
        // handle your exception presentationally here
    {{/if}}
    

    The owner.lookup('component:component-name') would return a component instance if it is initialized within the app or undefined if it is not. I can imagine some cases where you would want to initialize some of the components at runtime if some condition is met to save memory so this is the way of checking if the component is initialized within the app and can be used in a template. It's not an exception handling as there are no exceptions raised in this code but it uses dynamic component naming, and that's why we can move the actual check for existence of the component to run-time in distinction with compile-time when you statically specify the name of the component to be rendered.