Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS: $scope.array.push() does not update the view, even with $apply

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.

like image 526
Mimu Avatar asked Jun 29 '14 01:06

Mimu


People also ask

Why doesn’t the view update when an array changes in angular?

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 object in AngularJS?

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}}.

Why is my array data items not working in angular?

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.

Why change detection does not work in angular?

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.


Video Answer


2 Answers

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 = [];
        };
    });
})();
like image 184
Chris Bouchard Avatar answered Sep 29 '22 19:09

Chris Bouchard


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>
like image 40
Geoff Oslund Avatar answered Sep 29 '22 20:09

Geoff Oslund