14

The success function of a $http.put doesn't have access to the this scope of the service it's being called inside. I need to update a property of the service in the call back from the PUT request.

This is a cut down example of what I'm trying to do in a service:

var myApp = angular.module('myApp', function($routeProvider) {
// route provider stuff
}).service('CatalogueService', function($rootScope, $http) {
    // create an array as part of my catalogue
    this.items = [];

    // make a call to get some data for the catalogue
    this.add = function(id) {
        $http.put(
            $rootScope.apiURL,
            {id:id}
        ).success(function(data,status,headers,config) {
             // on success push the data to the catalogue
             // when I try to access "this" - it treats it as the window
             this.items.push(data);
        }).success(function(data,status,headers,config) {
            alert(data);
        });
    }
}

Sorry if there are some errors in the JS, the main point is how do I access the service scope from inside the success callback?

EDIT : while the answer to this question was correct, I switched to the factory method as both Josh and Mark recommended it

Pete
  • 4,326
  • 9
  • 41
  • 75

2 Answers2

23

As far as I know, you can't. But I wouldn't try to run the service that way anyway. Here is a cleaner way:

.factory('CatalogueService', function($rootScope, $http) {
  // We first define a private API for our service.

  // Private vars.
  var items = [];

  // Private methods.
  function add( id ) {
    $http.put( $rootScope.apiURL, {id:id} )
    .success(function(data,status,headers,config) { items.push(data); })
    .then(function(response) { console.log(response.data); });
  }

  function store( obj ) {
    // do stuff
  }

  function remove( obj ) {
    // do stuff
  }

  // We now return a public API for our service.
  return {
    add: add,
    store: store,
    rm: remove
  };
};

This is a very common pattern of developing services in AngularJS and it doesn't require any use of this in these cases.

Josh David Miller
  • 120,267
  • 16
  • 125
  • 95
  • I'm trying to understand the difference between a service and a factory, but I thought factories were supposed to `return` themselves, and services weren't: http://stackoverflow.com/questions/13762228/confused-about-service-vs-factory – Pete Feb 15 '13 at 23:14
  • When I copied and pasted your code, I forgot to change it to `.factory`. I updated the answer. Services are created using one of several module methods. `factory` returns whatever you want and `service` takes just a constructor that gets run by the provider, which returns the new object. Without resorting to some JavaScript magic, you'll want to use `factory` to store results from asynchronous operations. – Josh David Miller Feb 15 '13 at 23:26
  • Ok, so I've got around the `items` issue by doing what yo've outlined above, but now I'm getting the same problem trying to call a different method from the factory in the success callback. I'm downloading some data, and if it downloads successfully I want to call a `store()` method to write that to localStorage. In your code example it'd be like calling the `get()` method for example. – Pete Feb 17 '13 at 22:10
  • 1
    You need to return a public API of a private implementation. Then you never have to call `this`. I updated the answer. – Josh David Miller Feb 17 '13 at 22:30
  • @JoshDavidMiller will this work if you expose a scalar variable or array in your return object? I noticed that you only returned functions. – Julian Aug 28 '13 at 23:33
  • @Julian I'm not sure precisely what you're asking, so here's two answers: (1) A factory can return anything. Even `.factory("myVal", function () { return "Hello, world!"; })` is a valid factory. (2) you can expose anything on the return object and then manipulate it downstream as it will be the reference to the returned object that gets passed about. – Josh David Miller Aug 28 '13 at 23:56
  • @JoshDavidMiller Yes, I explained myself poorly! See this for a live example of what I mean: http://jsfiddle.net/WZQaF/ – Julian Aug 29 '13 at 00:14
  • @Julian Have you found out what happened in you example ? I have spent sometime to figure out. In fact, in the changePrivateVar, `this.privateVar` is undefined. because factory returns an instance created by the return block, and `this` refers to this instance. It is normal that `this.privateVar` is undefined. But if I delete `this`, it can find `privateVar`, because it finds it uppon the prototype chain. see http://jsfiddle.net/WZQaF/5/ – Qianyue Mar 19 '15 at 17:23
  • @Qianyue I did figure this out kind of in the same direction as you are suggesting but now exactly: https://jsfiddle.net/lefok/t4s7qrjv/ – Julian Mar 19 '15 at 19:12
  • @Julian In your jsfiddle, the data2 seems to be the same as data. I can't catch what is your intention to add the data2. – Qianyue Mar 19 '15 at 19:20
  • @Qianyue you would have if I had sent you the correct fiddle! (I forgot to click update) https://jsfiddle.net/lefok/eMUfT/4/ – Julian Mar 20 '15 at 01:23
  • @Julian, thank you for your update. I rewrite de jsfiddle to have a more clear vision how the `this` works in your case. jsfiddle : https://jsfiddle.net/eMUfT/17/ – Qianyue Mar 20 '15 at 10:08
  • @Qianyue That is beautiful, thanks! I think I finally got it! – Julian Mar 20 '15 at 19:10
16

Create a closure over a variable (often called that) that is assigned to this so that your callback functions will have access to your service object:

app.service('CatalogueService', function($rootScope, $http) {
    var that = this;
    ...
        ).success(function(data,status,headers,config) {
          that.items.push(data);

Here is a Plunker that uses $timeout instead of $http to demonstrate.

Mark Rajcok
  • 348,511
  • 112
  • 482
  • 482