1

I'm having a problem understanding promises in AngularJS.

I have something like the following:

var loader = angular.module('loadIt', []);

loader.factory('loadService', function($http, $q) {
  return {
    getJs: function(path) {
      var deferred = $q.defer();

      $http.get(path).then(function(response) {
        deferred.resolve(response.data);
      });

      return deferred.promise;
    }
  }
});

var myMod = angular.module('myMod', ['loadIt']);

myMod.service('mymodService', function(loadService) {

  this.vals = {};
  this.readIt = function (pos) {
            this.vals[pos] = loadService.getJs('aaa.js');
  }

});

In the debugger, I see the line:

this.vals[pos] = loadService.getJs('aaa.js');

The correct value is being returned by the getJs() call. But the scope is wrong. this.vals isn't defined inside the readIt function. How can I fix that?

Edit

I also tried the following:

  this.readIt = function (pos) {
     loadService.getJs('aaa.js').then(function(data) {
        this.vals[pos] = data;
    });
  }

Here I see that data contains the JSON object read from a file.

this refers to object Window and this.vals does not exist in the scope.

2
  • What do you see in the console ? Commented May 28, 2014 at 18:50
  • Not the console, but while debugging I can see data is a JSON object, but only data and this (Window) are in the local scope Commented May 28, 2014 at 19:02

2 Answers 2

2

Since you've chosen to use a service (rather than a factory), you need to assign this to another variable (often self as below) if you wish to reference it from within the scope of another function. Otherwise, the fact that this's context changes within the then callback function is problematic.

myMod.service('mymodService', function(loadService) {
  this.vals = {};
  var self = this;

  this.readIt = function (pos) {
    loadService.getJs('aaa.js').then(function(data){
      self.vals[pos] = data;
    });
  }
});

Using the above code, you could then set a scope variable like so:

myMod.controller('MyController', function($scope, mymodService, $timeout){
  mymodService.readIt(0);
  $scope.vals = mymodService.vals;
})

If you find that this approach is not working for you initially, compare this demo against your code and consider what you might need to refactor.

Additional Note:

If you don't like the var self = this syntax, consider using a factory instead of a service:

myMod.factory('mymodService', function(loadService) {
  var modservice = {
    vals: {}
  };

  modservice.readIt = function (pos) {
    loadService.getJs('aaa.js').then(function(data){
      modservice.vals[pos] = data;
    });
  }

  return modservice;
});

Demo

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks. I see both examples working, but mine still isn't. So I'll need to work a bit more on it to see what's the problem.
I tried rewriting it as a factory, like you had suggested. But I saw the same problem. I've worked around it by calling loadService.getJs(path, this) and returning a reference to 'this' as an argument to the function in the then(). I must be doing something wrong somewhere. I've given you a point for your help.
It's hard to say what the problem is. If your workaround works, then good! If you want a review of your code to help identify the problem, then it would be best for you to create a Plunker which demonstrates the problem.
2

You have 2 potential issues here.

1) loadService.getJs() returns a promise. So you're assigning a promise, rather than the actual value to this.vals[pos]. Assuming you want the actual value assigned then you want

loadService.getJs().then(function(data) {
      this.vals[pos] = data;
  });   

2) this refers to the calling context. So within readIt() this will refer to whatever object called it- rather than the lexical scope it is defined in (mymodService in this case). But you can use bind() to fix that. bind lets us force what this will be bound to when a function executes. So, adding bind() to the end of your callback, your code now looks like:

this.vals = {};
this.readIt = function (pos) {
  loadService.getJs().then(function(data) {
      this.vals[pos] = data;
  }.bind(this));   
}

this can be tricky - if you want a bit more detail you might checkout this blog by Dan Wahlin

Here's a stripped down fiddle with both changes that shows it all wired up within a views $scope.

9 Comments

Thanks. I see it working in fiddle, but for me, within the then(), vals isn't defined, only data and this (Window)
I think the theory behind the second part of your answer is incorrect. this does refer to the service (not the caller) and when you use vals = {} (without var or this.), it is set on the window object: jsfiddle.net/marcolepsy/aE744
I think my problem may be that I don use "myApp.service" and "myApp.factory" in my original code? I was trying to make this factory and service more portable and not dependent directly on the app.
No, I don't think that is your problem at all. You can segment the modules however you want as long as you inject them into the main app.
@MarcKline Good point. While this always refers to the calling context. That's kind of the point of this. But I forgot to put a var in front of vals (turning into a global)- I'm used to strict mode catching that.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.