Search code examples
ember.jsember-cliember-routerember-components

Ember DRY nested components with same actions


Right now I am building a particular screen within my ember app that has an unknown number of nested components. Because of this I am trying not to change the url based on the component being shown also there is some base info I want to display on every sub screen.

Currently it seems like I have to redefine and pass in all these action names everywhere when the real action logic is only defined on the route. Is there a way to DRY these action references possibly in a controller or one "parent" component.

here is an example ember-twiddle where I am rendering these components into an outlet http://ember-twiddle.com/31a69c62ceddcb69b02b

here is an example of the route

import Ember from 'ember';

export default Ember.Route.extend({
  _fixtureModels: [
    { person: {name: 'bill'}, sideModel: null},
    { person: {name: 'bill'}, sideModel: { postName: 'test post' }},
    { person: {name: 'bill'}, sideModel: { commentName: 'test comment'}}
    ],

  _renderSideModel: function (template, sideModel) {
    this.render();

    this.render(template, {
      outlet: 'side-model',
      into: 'index',
      model: sideModel
    });
  },

  renderTemplate: function () {
    this.render();

   this.render('someComponentWrapper', {
     outlet: 'side-model',
     into: 'index'
   });
  },

  model: function () {
     return this._fixtureModels[0];
  },

  actions: {
    renderTopLevel: function () {
      return this.renderTemplate();
    },
    renderPost: function () {
      return this._renderSideModel('post', this._fixtureModels[1]);
    },
    renderComment: function () {
      return this._renderSideModel('comment', this._fixtureModels[2]);
    }
  }
});

I know URL is supposed to be king in Ember and this is pretty sinful but it would be very hard to reconstruct a potentially nested view.


Solution

  • So the reason you have to redefine all these action names is because components are designed to be isolated, i.e. reused anywhere. There are however, two shortcuts you can use:

    1. Apply default action names:

    You can use a default action name in your component to avoid having to specify it in every template.

    // components/some-component.js
    
    export default Ember.Component.extend({
        renderPost: "renderPost",
        renderComment: "renderComment"
    });
    
    // some template where you are using this component
    
    {{some-component-wrapper renderPost="myRenderPost"}}
    {{!overrides renderPost, but "inherits" an action named renderComment}}
    
    1. Use closure actions (if you are using Ember 1.13 or later):

    See http://emberjs.com/blog/2015/06/12/ember-1-13-0-released.html#toc_closure-actions

    This is a new feature that lets you pass a function down rather than a name of an action.