Where should I define a `save` action?

Hello! I’m a relatively new Ember developer. My understanding of how Ember works is that the route’s primary job is to load a model, template, and controller (the model through the model hook, the others by naming convention if not otherwise specified); the model’s job is to store persistent state; the template’s job is to create the user interface; and the controller’s job is to handle user interactions. I always define my actions on a controller.

However, I was recently playing around with ember-flash-message, and in a discussion with the author in a Github issue, he told me that if an action causes data to be set for the next page or causes the page to transition, it should be defined on the route. This vaguely makes sense to me, but I’ve never seen anything about it in the Ember guides or in anything else I’ve read about Ember.

So, first, is this indeed the generally-recommended practice?

And if so, second, why? What advantage does this give over defining all of the actions in a single place?

Two things make me dislike the ability to define actions in multiple places: when I come to a new Ember project and I see an action in a template, I need to check two places to figure out where it’s defined; and if I ever change an action from staying on the same route to moving to a new route, I need to move it from my controller to my route. Both of these feel like unnecessary work and confusion.

But I’m new to Ember and have only used it in small apps. I could totally be missing something here, and would love to learn from the perspective of folks with more experience.

Thanks!

4 Likes

Absent any special circumstances, I put the ‘save’ action on the controller. For instance:

// controllers/ThingsNewController.js
App.ThingsNewController = Ember.Controller.extend({
    actions: {
        'save': function () {
            var model = this.get('content');
            
            model.save().then(function () {
                // transition wherever
            }, function () {
                // display error message
            });
        }
    }
});

he told me that if an action causes data to be set for the next page or causes the page to transition

I dunno about that…what does “causes data to be set for the next page” mean? If the controls for manipulating data are only on one template then it seems natural to me to put them on the controller for that template. If the data in question could be manipulated from multiple templates then I could see a refactoring argument because the CRUD actions could be written once and put on the ApplicationRoute or something, but I don’t even like that.

Sorry if that’s a little incoherent, I’m a bit tired; please respond with more questions if you are still confused and I’ll clarify. Also, for the record, these are not iron clad rules I’m mentioning, but they are the best practice on the Ember projects on which I worked.

1 Like

I’d generally recommend putting actions on routes in preference to controllers, especially if you’re going to do things like transition, which is a routing concern and not a controller concern. Controllers are there to store your in-memory application state, wrap your models to present them to your templates, and connect your models together using dependency injection. Routes should be where the bulk of your logic and actions live. In my experience you will find your application easier to maintain if you keep your controllers as skinny as possible.

There are some other great reasons to prefer routes to controllers for actions, for further details see this talk (vid at bottom of page): http://madhatted.com/2013/8/31/emberfest-presentation-complex-architectures-in-ember

15 Likes

This is interesting, while I was aware that routes could handle actions I’ve never seen a single example of someone doing it. Handling actions that cause transitions there certainly makes a lot of sense.

I’m curious about views with nested controllers though, for instance if I’m using render or components would you handle actions for those inside their corresponding controller or in the route?

I would still recommend handling the action in the route. Handling them in the controller tightly couples the action to the template. Handling the action in the route allows different behaviour depending on different states of your application. In my apps I very rarely handle actions in controllers and find it to be a much more maintainable pattern.

The exception would be UI only actions on controllers. An example would be an action that shows or hides a part of the UI by setting an isVisible property on the controller or something - in that case a tight coupling of controller and template makes sense.

5 Likes

+97 to everything @alexspeller said.

@alexspeller this is a wonderful description and IMHO it’d be so good if the following page had a great explanation like the on you just gave… it’d stop people wandering about for so long trying to work out what on earth is going on and where things should go:

http://emberjs.com/guides/concepts/core-concepts/

Unless new people soak in ember’s videos and docs for mutliple days, it is really difficult to fully understand where things should be in a real app, and I have a feeling your architectural exaplanation would clarify things loads. You Rock!

:smiley:

3 Likes

I’ve got to admit this has completely changed how I think about organising ember codebases. I’ve always used routes for just setup (should definitely update that core concepts page). I’m a little concerned that I might end up with an awful lot of actions in each route but it’ll be interesting to see how things work out with that structure.

Thanks for all the great insight!

Me too! I have done a fair amount of Ember, but thanks to this thread (and the research it motivated me to do in the Ember blogosphere) I have completely changed my mind about actions. Routes it is, for the most part.

When you’re handling actions in the route do you tend to get context/properties via the controller (e.g. controllerFor) or try to make the information available as params to the action?

e.g.

  actions: {
    save: function(){
      var name = this.controllerFor("user").get("name");
      // use name
    }
  }

vs

  actions: {
    save: function(name){
      // use name
    }
  }
1 Like

Yes – you can use this.controller if you’re getting properties from the route’s controller or this.controllerFor if you’re getting properties from another associated controller (usually a parent route).

My question is more about style really, should you favour accessing the controller or including the context in the params to the action. My feeling is that including the context as params to the action will be more flexible as it’s decoupled from the controllers.

1 Like

You can try

actions: {
    'save': function () {
        var model = this.controllerFor(this.routeName);

        model.save().then(function () {
              // and so on...
    }
}

My thinking was that since there is a this.controller in the route, it’s okay to access the controller directly.

I favour accessing the controller and not passing data up. The extra params are unnecessary.

A good example of when to use params is in a list - if you have an action removeItem in a list of items, you need to provide the actual item you are removing as context. But generally most of the info you need is accessible from the controller in the action, and this is preferred IMO.

4 Likes

@alexspeller is right on. And to the degree that this matters, I’ve read the same thing from @tomdale, I believe. The router and your routes are the state machine for your app. Controllers can be used anywhere outside of their default context and you don’t want your data control wandering aimlessly around your app. Users also have a nasty habit of navigating away from a page before everything is cleaned up or handled as you intended.

I do see how moving CRUD operations from a controller to a route can improve maintenance when changing the template. However, by doing so doesn’t this violate the “Tell don’t ask” principle? Controller has all the information to make a decision and act on it but you’re asking about this information from a route which make the route know about controller’s internals.

2 Likes

Yeah, I think this is the thing that’s been troubling me slightly, I’ve always tried to use a lot of small controllers and keep the actions as localised as possible. I’m curious to try this new structure though so I’ll give it a go and see how everything shakes out.

Thinking about this a bit more… if all actions are moved to the routes, are we not just using controllers as presenters and routes as controllers?

1 Like