12

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.

Russia Must Remove Putin
  • 374,368
  • 89
  • 403
  • 331
Mimu
  • 393
  • 2
  • 3
  • 13
  • 3
    I do not believe you can use `Array.push()` this way. Try `concat` instead: `$scope.posts.concat(data);` – PM 77-1 Jun 29 '14 at 01:13
  • 3
    Wow, so many time on this and it's because I don't know my 101. It is that 100%, I needed to use `$scope.posts = $scope.posts.concat(data)` to concatene both arrays. Thanks dude. – Mimu Jun 29 '14 at 01:19
  • Check out [this example](http://www.java2s.com/Tutorials/AngularJS/AngularJS_Example/Scope/Push_value_to_array_in_scope.htm). They seem to be using Array#push, and it seems to be working. – Chris Bouchard Jan 23 '15 at 02:12
  • Also `Array.prototype.push.apply($scope.posts, data)` works, as data is an array, you want to add them all, no add the data array as a single element in the posts array. – Julien Palard Sep 19 '16 at 08:53

2 Answers2

10

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 = [];
        };
    });
})();
Chris Bouchard
  • 806
  • 7
  • 12
  • Thanks Chris, what happens if you need to updating an existing element of the array? For example, the list may have changed on the server and you want to pull it down and update the array with both new distinct items and updated existing one. – Leo Moore May 13 '15 at 06:59
7

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>
Geoff Oslund
  • 73
  • 1
  • 4