12

I am trying to include a library of functions, held in a factory, into a controller. Similar to questions like this: Creating common controller functions

My main controller looks like this:

recipeApp.controller('recipeController', function ($scope, groceryInterface, ...){

$scope.groceryList = [];
// ...etc...    

/* trying to retrieve the functions here */
$scope.groceryFunc = groceryInterface; // would call ng-click="groceryFunc.addToList()" in main view
    /* Also tried this:
    $scope.addToList = groceryInterface.addToList();
    $scope.clearList = groceryInterface.clearList();
    $scope.add = groceryInterface.add();
    $scope.addUp = groceryInterface.addUp(); */
}

Then, in another .js file, I have created the factory groceryInterface. I've injected this factory into the controller above.

Factory

recipeApp.factory('groceryInterface', function(){

        var factory = {};

    factory.addToList = function(recipe){
        $scope.groceryList.push(recipe);
                    ... etc....
    }

    factory.clearList = function() {
            var last = $scope.prevIngredients.pop();
            .... etc...
    }

    factory.add = function() {
    $scope.ingredientsList[0].amount = $scope.ingredientsList[0].amount + 5;
    }

    factory.addUp = function(){
        etc...
    }

    return factory;
});

But in my console I keep getting ReferenceError: $scope is not defined at Object.factory.addToList, etc. Obviously I'm guessing this has to do with the fact that I'm using $scope in my functions within the factory. How do I resolve this? I notice that in many other examples I've looked at, nobody ever uses $scope within their external factory functions. I've tried injecting $scope as a parameter in my factory, but that plain out did not work. (e.g. recipeApp.factory('groceryInterface', function(){ )

Any help is truly appreciated!

Community
  • 1
  • 1
LazerSharks
  • 3,089
  • 4
  • 42
  • 67

3 Answers3

25

Your factory can't access your $scope, since it's not in the same scope.

Try this instead:

recipeApp.controller('recipeController', function ($scope, groceryInterface) {

    $scope.addToList = groceryInterface.addToList;
    $scope.clearList = groceryInterface.clearList;
    $scope.add       = groceryInterface.add;
    $scope.addUp     = groceryInterface.addUp;
}

recipeApp.factory('groceryInterface', function () {

    var factory = {};

    factory.addToList = function (recipe) {
        this.groceryList.push(recipe);
    }

    factory.clearList = function() {
        var last = this.prevIngredients.pop();
    }
});

Alternatively, you can try using a more object oriented approach:

recipeApp.controller('recipeController', function ($scope, groceryInterface) {

    $scope.groceryFunc = new groceryInterface($scope);
}

recipeApp.factory('groceryInterface', function () {

    function Factory ($scope) {

        this.$scope = $scope;
    }

    Factory.prototype.addToList = function (recipe) {
        this.$scope.groceryList.push(recipe);
    }

    Factory.prototype.clearList = function() {
        var last = this.$scope.prevIngredients.pop();
    }

    return Factory;
});
Joseph Silber
  • 214,931
  • 59
  • 362
  • 292
  • 2
    @Gnuey - Note that `bind` is not available in older versions of IE. If you have to support those and want to use the first method, either use [this MDN polyfill](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind#Compatibility), or - if you have jQuery on the page - use [`$.proxy`](http://api.jquery.com/jQuery.proxy/) instead. – Joseph Silber Oct 28 '13 at 18:26
  • 1
    Okay, great. That is actually extremely helpful info -- my dept head and site visitors use older IE versions at times for some reason :\ Would I instead write `$scope.addToList = $.proxy(groceryInterface.addToList, $scope)` ? – LazerSharks Oct 28 '13 at 18:32
  • 1
    @Gnuey - Yes. Exactly like that. – Joseph Silber Oct 28 '13 at 18:48
  • Hmm, I am getting `TypeError: Cannot call method 'push' of undefined at Object.factory.addToList` I forgot to mention that groceryList is an array in the main controller (`$scope.groceryList = []`). Does this make a difference? – LazerSharks Oct 28 '13 at 22:22
  • @Gnuey - Can you create a [short concise](http://sscce.org/) fiddle of your problem? – Joseph Silber Oct 28 '13 at 22:40
  • Sorry for the delay. Making a fiddle actually helped me to simplify it and finally find the problem(s) (surprise, surprise). It indeed works. Here is a fiddle for anyone interested: http://jsfiddle.net/8rtaT/1/ One last question, though: is `.bind($scope)` simply a security measure to ensure that $scope is corrently binded to the factory (instead of some narrowed scope)? Because my implementation works with or without `.bind($scope)` appended to my function assignment. – LazerSharks Oct 31 '13 at 03:29
4

You cannot use $scope in a factory as it is not defined. Instead, in your factory functions change the properties of the object the factory is returning, e.g.

factory.addToList = function (recipe) {
    this.groceryList.push(recipe);
}

these will then get passed on to your $scope variable

$scope.addToList = groceryInterface.addToList;
// ... = groceryInterface.addToList(); would assign to `$scope.addToList` what is returned, instead of the function itself. 
Ron Martinez
  • 195
  • 4
  • 7
NicolasMoise
  • 7,261
  • 10
  • 44
  • 65
  • 1
    I edited your solution so that instead of `$scope.addToList = groceryInterface.addToList();`, it reads `$scope.addToList = groceryInterface.addToList;` I discovered that the parenthesis at the end of `addTotList()` was one big problem during my debug process... got myself quite a nice refreshing slap of javascript syntax... – LazerSharks Oct 31 '13 at 03:34
3

This isn't the exact answer for this question, but I had a similar issues that I solved by simply passing $scope as an argument to a function in my factory. So it won't be the normal $scope, but $scope at the time the function in the factory is called.

app.controller('AppController', function($scope, AppService) {


  $scope.getList = function(){

    $scope.url = '/someurl'

    // call to service to make rest api call to get data

    AppService.getList($scope).then(function(res) {
      // do some stuff 

    });
  }

});


app.factory('AppService', function($http, $q){
  var AppService = {

    getList: function($scope){
      return $http.get($scope.url).then(function(res){
        return res;
      });
    },

  }

  return AppService;
});
Jazzy
  • 6,029
  • 11
  • 50
  • 74