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.

4
  • 3
    I do not believe you can use Array.push() this way. Try concat instead: $scope.posts.concat(data); Commented Jun 29, 2014 at 1: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. Commented Jun 29, 2014 at 1:19
  • Check out this example. They seem to be using Array#push, and it seems to be working. Commented Jan 23, 2015 at 2: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. Commented Sep 19, 2016 at 8:53

2 Answers 2

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 = [];
        };
    });
})();
Sign up to request clarification or add additional context in comments.

1 Comment

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.