Prototypical inheritance and function references as applied to function references/scopes

82 views Asked by At

Let's say I have the following two directives:

angular.module('demo').directive('functional', [function (){
  var idempotentMethods = ['idempotentMethod', 'otherIdempotentMethod'];
  return {
    restrict: 'E',
    scope: {
      'demoObject': '='
    },
    templateUrl: 'directive.html',
    link: function (scope){
      for(var i = 0; i < idempotentMethods.length - 1; i++){
        scope[idempotentMethods[i]] = function(){
          scope.demoObject[idempotentMethods[i]]();
        }
      }
    }
  }
}]);

angular.module('demo').directive('nonFunctional', [function (){
  var idempotentMethods = ['idempotentMethod', 'otherIdempotentMethod'];
  return {
    restrict: 'E',
    scope: {
      'demoObject': '='
    },
    templateUrl: 'directive.html',
    link: function (scope){
      for(var i = 0; i < idempotentMethods.length; i++){
        scope[idempotentMethods[i]] = scope.demoObject[idempotentMethods[i]];
      }
    }
  }
}]);

And the following factory:

angular.module('demo').factory('demoFactory', ['$resource', function(resource){

  var DemoFactory = resource('demo.json');

  angular.extend(DemoFactory.prototype, {
    idempotentMethod: function () {
      return this.$get();
    },
    otherIdempotentMethod: function () {
      return this.$get();
    }
  });

  return DemoFactory;
}]);

In the functional directive when scope.idempotentMethod() is triggered (through an ng-click or otherwise), the INCORRECT Factory method gets called.

In the nonFunctional directive it triggers

  TypeError: undefined is not a function
    at Scope.angular.extend.idempotentMethod
    at $parseFunctionCall
    at callback
    at Scope.$eval

In the Factories method. This suggests two things. 1) The function reference is binding (as expected),however only the last function reference is binding. 2) The this reference is incorrect. Printing out this in the Factory methods appears to corroborate this, with the non-Functional directive producing Scope, while the functional directive produces a Resource.

What is causing these two behaviours? Why is the this reference binding incorrectly? Why is the wrong function being called?

A plunkr demoing the problem: http://plnkr.co/B52DV0jmSWXc0M6GAamM

1

There are 1 answers

3
HMR On BEST ANSWER

I think it's the way you set the closure:

function createBound(){
  var fn = function(){console.log('this is:',this);}
  ,i = -1//ret items are set using the value i inside createBound
    //by the time we call the function the value of i is 2
  ,ret = [];
  for(i = 0; i < 2; i++){
    ret.push(function(){
      console.log('i is:',i);//says 2 every time
      fn.call({hello:'world'});
    });
  }
  return ret;                                  
}
var bound = createBound();
bound[0]();
bound[1]();

correct way:

function createBound(){
  var fn = function(){console.log('this is:',this);}
  ,i = -1
  ,ret = [];
  for(i = 0; i < 2; i++){
    ret.push(
      (function(i,fn){//IIFE returning a function with correct i and fn
        //the value of i and fn that are passed are used in the returned closure
        //scope is within this IIFE, not within createBound
        return function(){
          console.log('i is:',i);//says: 0,1
          fn.call({hello:'world'});
        };
      }(i,fn))//end of IIFE where i and fn are passed to create a new closure
    );
  }
  return ret;                                  
}
var bound = createBound();
bound[0]();
bound[1]();

[UPDATE]

In your code to pass the correct closure you can do:

  for(var i = 0; i < idempotentMethods.length - 1; i++){
    scope[idempotentMethods[i]] = (function(i,idempotentMethods){
      return function(){
        scope.demoObject[idempotentMethods[i]]();
      };
    }(i,idempotentMethods));
  }

Please note that i doesn't count to the end of idempotentMethods that is why your code executes idempotentMethods[idempotentMethods.length-1] when ng-click is triggered.