8

I have a factory that is retrieving the data from an external source. As soon as i get the data, i use a second factory to filter it by a certain criteria.

The factory property is assigned to scope.

Now when i do this in my factory, it doesn't update the scope:

factory.foo = [{id:1,name:'foo'}]; // doesn't work

therefor also the filterin in a second factory doesn't work

factory.foo = Filter.filter(); // doesn't work

while this works:

factory.foo.push({id:1,name:'foo'}); // works

Does anyone have an idea if this is intended and why it is like this, and how to solve it?

Full Sample plus plunkr

app.factory('Foo',function(Filter) {
  var factory = {
    foo:[],
    getDataForFoo:function() {
      factory.foo = Filter.filter(); // doesn't work
      //factory.foo = [{id:1,name:'foo'},{id:1,name:'foo'}]; // doesn't work
      //factory.foo.push({id:1,name:'foo'}); // works
    }
  };
  return factory;
});

app.factory('Filter',function() {
  var factory = {
    filter:function() {
      var arr = [];
      arr.push({id:1,name:'foo'});
      return arr;
    }
  }
  return factory;
});

app.controller('MainCtrl', function($scope,Foo) {
  $scope.test = 'running';
  $scope.foo = Foo.foo;

  $scope.click = Foo.getDataForFoo;
});

Plunkr

Kilian Schefer
  • 579
  • 5
  • 18
  • 3
    Is what Simon Belanger says, you lose the reference. I explain it here: http://angular-tips.com/blog/2013/08/consuming-services/ – Jesus Rodriguez Sep 02 '13 at 09:29
  • For a full explanation of scope inheritance see this answer http://stackoverflow.com/questions/14049480/what-are-the-nuances-of-scope-prototypal-prototypical-inheritance-in-angularjs – piatek Sep 02 '13 at 10:46
  • @piatek i don't think that it's related to the scope inheritance, as i have only one scope here. – Kilian Schefer Sep 02 '13 at 11:28
  • pardon, I've jumped the gun as I've had a similar problem but related to scope earlier! – piatek Sep 02 '13 at 16:31

2 Answers2

12

The problem is that your factory replace the reference to Factory.foo. When your scope is initialized, $scope.foo holds a reference to an array (empty). When you call Foo.getDataForFoo, it internally changes the reference to Factory.foo but your scope still hold a reference to the previous array. This is why using push works as it doesn't change the array reference, but the array content.

There are a few ways to fix this. Without going in all the different options, the easiest one is to wrap your $scope.foo in a function, returning Factory.foo. This way, Angular will detect a reference change in a digest cycle and will update the view accordingly.

app.controller('MainCtrl', function($scope,Foo) {
  $scope.test = 'running';
  $scope.foo = function() { return Foo.foo };

  $scope.click = Foo.getDataForFoo
});

// And in the view (the relevant part)

<ul ng-repeat="elem in foo()">
  <li>{{elem.id}} {{elem.name}}</li>
</ul>
<a href="" ng-click="click()">add</a>
Simon Belanger
  • 14,752
  • 3
  • 41
  • 35
  • thanks for that explanation. is this a good approach...or is there some better/cleaner practise for this? – Kilian Schefer Sep 02 '13 at 09:45
  • 1
    It is a good approach when you expect reference changes to something external. As you discovered, one option is to change the content of the array directly (push, splice, etc..) or you can move the function logic to the factory (ie `Foo.foo` is a function returning an array) but you will still have to use `foo()` in your template. Another option is to re-fetch `Foo.foo` after a call to `getDataForFoo` like: `$scope.click = function() { Foo.getDataForFoo(); $scope.foo = Foo.foo; };`. At this point, it's really a matter of preference whether you like using computed properties (function) or not. – Simon Belanger Sep 02 '13 at 09:49
  • 1
    I'd genuinely appreciate the rest of the options on how to do this – user2422960 Mar 09 '14 at 20:14
  • @user2422960 My previous comments briefly explains the other options I could think of at that time – Simon Belanger Mar 09 '14 at 20:21
1

@Simon-Belanger answer is correct and he presents a viable solution.

Another option would be to just empty the array and push new items into it (e.g. for a refresh event), rather than resetting the reference by assigning a new array to it. You can truncate an array by assigning to the length: myArray.length = 0 and then you can iterate over the new collection to populate new entries via array.push()

AJ.
  • 3,062
  • 2
  • 24
  • 32