Search code examples
javascriptember.jscallbackpromiseic-ajax

Should I and how to transitionToRoute in a promise callback


In an Ember controller, I want to call my API to create a user and on success, transitionToRoute. This is what I currently want to work:

import ajax from "ic-ajax";
import Ember from "ember";

export default Ember.Controller.extend({
  actions: {  
    createAndLoginUser: function() {
      var user = { "user": this.getProperties("email", "name") };
      ajax({ url: "api/users", type: "POST", data: user })
        .then(transitionToHome);
    }   
  }
});

var transitionToHome = function() {
  this.transitionToRoute("home")
}

But when I place a debugger in the method, this is no longer the controller object and is out of scope to call transitionToRoute.

I've only ever written hacky javascript, but I'm trying to learn core concepts and some frameworks. Is this the right way to use a promise? And is this the right place in Ember to put a transition?


Solution

  • Your problem has nothing to do with Ember or transitions; it's all about this handling. The simplest solution is just

    .then(transitionToHome.bind(this));
    

    Putting transition method inside the controller

    You could also consider putting transitionToHome inside the controller as a method. Then, you could call it as in the following:

    export default Ember.Controller.extend({
    
      transitionToHome: function() { this.transitionToRoute("home"); },
    
      actions: {  
        createAndLoginUser: function() {
          var self = this;
          var user = { "user": this.getProperties("email", "name") };
          ajax({ url: "api/users", type: "POST", data: user })
            .then(function() { self.transitionToHome(); });
        }   
      }
    
    });
    

    This might be a simpler approach which is more readable and doesn't require reasoning about this and using bind (but does require using self).

    Moving the transition to the route?

    Outside the scope of your question, but some would say that route-related logic (including transitioning) logically belongs in the route, not the controller. If you agree, you could refactor the above to do a send

    createAndLoginUser: function() {
      var user = { "user": this.getProperties("email", "name") };
      var self = this;
      ajax({ url: "api/users", type: "POST", data: user })
        .then(function() { self.send("goHome"); });
      }
    }
    

    and then implement the goHome action in your route. Or, you could implement goHome in a higher-level route, even the application route, thus making it available from any controller or lower-level route.

    Moving the AJAX logic to a service?

    Others might say that the ajax logic does not really belong here in the controller, and should rightfully be in a services layer, so

    // services/user.js
    export function createUser(data) {
      return ajax({ url: "api/users", type: "POST", data: { user: data } });
    }
    
    // controller
    import { createUser } from 'app/services/user';
    
    export default Ember.Controller.extend({
      createAndLoginUser: function() {
        var data = this.getProperties("email", "name"));
        var self = this;
        createUser(data) . then(function() { self.send("goHome"); });
      }
    };
    

    Using ES6 syntax, we can avoid the self, simplifying a bit in the process:

      createAndLoginUser: function() {
        var data   = this.getProperties("email", "name"));
        var goHome = () => this.send("goHome");
    
        createUser(data) . then(goHome);
      }
    

    Now this code is starting to look more semantic and readable.