Calling a controller action from a view via the needs API in Ember.js

by Kasper Tidemann

Preface

In relation to my post about the needs API of Ember.js, I was asked how to call an action belonging to the AnotherController that got synthesized. Or, in other words:

If you 1) have an ExampleView and an ExampleController, 2) an AnotherController that’s connected to the ExampleController via needs: [‘another’], how do you 3) call an action on AnotherController from the ExampleView template?

Explanation

For clarity’s sake, let’s bring back the example from the previous post. We have this piece of code:

App.ExampleController = Em.Controller.extend({
  needs: ['another']
});

Now, in our AnotherController, we may have the following code:

App.AnotherController = Em.Controller.extend({
  someAction: function() {
    console.log('Why, hello there, fellow EmbereƱo!');
  }
});

So far, so good. Let’s jump back to the ExampleView for a moment.

The ExampleView belongs to the ExampleController and expects to have a template named example available per default. It’s inside this template that we want to call someAction.

There are essentially two ways to call someAction from the ExampleView template. This may vary depending on the way you architect your Ember.js app.

Architecturally

Some developers believe in a more or less strict way of encapsulating the code. This means that when an action is called from a template, it should be directed to the corresponding view – and possibly the corresponding controller from there on.

Put differently: when calling someAction inside your example.handlebars template, the target should be the ExampleView. The principle could be illustrated this way:

Template:
{{action someAction target="view"}}

View:

App.ExampleView = Em.View.extend({
  someAction: function() {
    this.get('controller.controllers.another').send('someAction');
  }
});

I’ve attempted to visualize this in the below diagram:

Diagram of the first approach

Instead of going through the view, you can change the target option of the action helper. This is done via the following piece of code:

Template:
{{action someAction target="controller.controllers.another"}}

This will work because we’ve connected the ExampleController to the AnotherController via the aforementioned needs: [‘another’] inside ExampleController. Visually, this could be explained like this:

Diagram of the second approach

Conclusion

Choosing between which way of implementing the call to someAction is completely up to you.

Following the encapsulation path, you will always expect for template actions to correspond to functions in your views.

I’ve found this to be a plausible way for two reasons: 1) because my template actions usually are UX-oriented and 2) because it makes for clear organizing of code in larger projects.

Targeting actions directly from a view to a different controller creates a more intertwined map of calls, but is way more straight-forward. Either way is technically sound – it’s up to you to decide for yourself.