I'm trying to learn AngularJS and there is this thing that I don't understand, which seems like all the internet solved by using $scope.$apply
, but I already use it and it does nothing.
Basically, I use Twitter API to retrieve a timeline, and when we scroll from the bottom, it loads more tweets. This part works, I'm using a factory to do it, but I can display the object receive in the console, I don't have issues here.
I have a view like this, to display the data:
<div class='timeline' ng-controller='TimelineCtrl' is-scrolled='loadData()'>
<div class='tweet' ng-repeat='p in posts'>
<img class='portrait' src='{{p.user.profile_image_url}}' />
<p>{{p.text}}</p>
<p class='date'>{{p.created_at}}</p>
</div>
</div>
My controller looks like this:
$scope.posts = [];
// Load the original tweets list
TwitterAPI.timeline($scope.count, function(data) {
$scope.$apply(function() {
$scope.maxId = data[data.length-1].id;
$scope.sinceId = data[0].id;
$scope.posts.push(data);
});
});
data is legit.
The thing I don't understand at all, and make me think that it's something very easy to solve and I just don't see it, is that if I use '= data' instead of 'push(data)' the view is updated. Even when I load more tweets, if I use '=' the view is updated (with the content being replaced of course which is not what I want).
Note: maxId, sinceId and count are initialized earlier, I didn't put it there since I don't think it matters.
Even though the array’s content changed, the view is not updated because the times array has not changed from Angular’s change detection perspective, as the array is still in the same memory address (an array variable is a actually a reference).
How to Use the Scope? When you make a controller in AngularJS, you pass the $scope object as an argument: When adding properties to the $scope object in the controller, the view (HTML) gets access to these properties. In the view, you do not use the prefix $scope, you just refer to a property name, like { {carname}}.
The reason behind this is that your Array dataItems is an Object. Objects store references to that actual data. When you push a new item to an array you aren't changing your reference and therefore Angular doesn't see a change so it isn't redrawing the view even though the items in the array have changed.
Technically, according to Angular, the variable never changed so change detection doesn't run. We can change this by the way we return the list of objects each time one is added or edited.
The trouble seems to be that Angular's NgRepeat
stops if it iterates over the same object more than once. I've created a jsFiddle to demonstrate.
In the first section, you can add strings to an array. The first button always add the same string object, while the second creates a fresh string object each time. Notice that as soon as you click the first button twice, it doesn't matter what you add to the list.
In the second section, we always add a fresh object, even though those objects all contain a reference to the same string object. This works as you would expect.
So, to make this an explicit answer, make sure the things you add to your list are distinct objects, and use object literals to enforce this if needed. I would prefer Array#push
over Array#concat
because the latter creates a new array object each time, and if you have a lot of items, that will be a lot of churn and a lot of garbage collection.
The HTML:
<div ng-controller="Controller1">
<button ng-click="addLine()">Add Line</button>
<button ng-click="addNumber()">Add Number</button>
<button ng-click="reset()">Reset</button>
<div>{{lines}}</div>
<div ng-repeat="line in lines">
{{line}}
</div>
</div>
<hr />
<div ng-controller="Controller2">
<button ng-click="addObject()">Add Object</button>
<button ng-click="reset()">Reset</button>
<div>{{objects}}</div>
<div ng-repeat="obj in objects">
{{obj.data}}
</div>
</div>
The JavaScript:
(function () {
var myApp = angular.module('myApp', []);
myApp.controller('Controller1', function ($scope) {
$scope.lines = [];
$scope.addLine = function () {
$scope.lines.push('Hello world!');
};
$scope.addNumber = function () {
$scope.lines.push('Line ' + $scope.lines.length);
};
$scope.reset = function () {
$scope.lines = [];
};
});
myApp.controller('Controller2', function ($scope) {
$scope.objects = [];
$scope.addObject = function () {
var obj = { data: 'Hello world!' };
$scope.objects.push(obj);
};
$scope.reset = function () {
$scope.objects = [];
};
});
})();
I believe if you structure your ng-repeat as such (with track by $index), it will not stop on dupes:
<div class='tweet' ng-repeat='p in posts track by $index'>
...
</div>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With