Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is $scope.someArray.push not a valid function to pass to .then on $q.promise

Relevant fiddle: https://jsfiddle.net/tqf4zea7/1/

I'm using $q in an angular controller. To test some scenarios, I created an array on the scope to push messages to:

$scope.messages = [];

I have set up a function that returns a $q function as so:

function returnAPromise(valToReturn){
    return $q(function(resolve, reject){
        $timeout(function(){
            resolve(valToReturn);
        }, 500);
    });
}

I then have a .then() call on the result that looks like this:

returnAPromise('third').then($scope.messages.push);

Since I only want to push the value that the promise was resolved with to the array, I figured I could just pass in the push method of the messages array, but when I do that, I get the following error:

VM289 angular.js:12520 TypeError: Array.prototype.push called on null or undefined
    at processQueue (VM289 angular.js:14792)
    at VM289 angular.js:14808
    at Scope.$eval (VM289 angular.js:16052)
    at Scope.$digest (VM289 angular.js:15870)
    at Scope.$apply (VM289 angular.js:16160)
    at VM289 angular.js:17927
    at completeOutstandingRequest (VM289 angular.js:5552)
    at VM289 angular.js:5829

If I enclose push in a function, it works fine:

returnAPromise('third').then(function(message){ 
     $scope.messages.push(message) 
});

Is this a closure issue I don't understand?

like image 293
sonicblis Avatar asked Aug 16 '16 19:08

sonicblis


2 Answers

You need to bind push since it uses this

returnAPromise('third').then($scope.messages.push.bind($scope.messages));
like image 194
Yury Tarabanko Avatar answered Oct 26 '22 23:10

Yury Tarabanko


I know there is an accepted answer, but i will explain this more clearly here

Example

lets start with an example

var scope = {
  scopeFn: function() {
    console.log('this', this)
  }
}

function callFn(fn) {
  fn();
}

callFn(obj.scopeFn) // will log window object 
callFn(function() {
  obj.scopeFn();
});// will log scope object

as you can see, wrapping the function will give the called object the value of this, but calling it directly without wrapping it will call the window object.

why?

this will bind to the object its called from.

In the first example callFn(obj.scopeFn) you are passing the function as a parameter, hence when the function is called, its called directly not from the scope object. (scope object is lost, only the function reference is sent).

in the second example you are calling the function scopeFn from the object, hence the this will bind to its object. (scope object is not lost, as the whole thing is there when its called)

Solution

To solve this issue you need to bind the function you are passing as a parameter for the resolve, so it will always be called as if it is called from its parent object.

var scopeMessage = $scope.messages;
returnAPromise('third').then(scopeMessage.push.bind(scopeMessage));
like image 39
Bamieh Avatar answered Oct 27 '22 01:10

Bamieh