Using setTimeout to get scrolling chat window to work, but doesn't feel like the ideal solution My friend and I are working on a bare-bones chat web app, using Angular on the front end. He's using Swampdragon for some of the real-time stuff.

My task that I set out to achieve was to get the chat window to scroll to the bottom when a chat room is loaded (most relevant bit is $dragon.onChannelMessage):

app.controller('ChatRoomCtrl',
        ['$scope', '$dragon', 'ChatStatus', 'Users', function (
            $scope, $dragon, ChatStatus, Users) {
    $scope.channel = 'messages';
    $scope.ChatStatus = ChatStatus;

    $scope.idToUser = function(id) {
        var user;
        user = $scope.users.filter(function(obj) {
            return obj.pk == id;
        });
        return user[0].display_name;
    };
    Users.getList().then(function(users) {
        $scope.users = users;
    });

    $dragon.onReady(function() {
        $dragon.subscribe('messages', $scope.channel).then(function(response) {
            $scope.dataMapper = new DataMapper(response.data);
        });
    });

    $dragon.onChannelMessage(function(channels, message) {
        if (indexOf.call(channels, $scope.channel) &gt; -1) {
            if (ChatStatus.messages[message.data.room].indexOf(message.data) == -1) {
                message.data.posted = new Date(message.data.posted);
                $scope.$apply(function() {
                    ChatStatus.messages[message.data.room].push(message.data);

                    setTimeout(function() {
                        scrollToBottom();
                    }, 30);
                });
            }
        }
    });
}]);


Or, when a new message is pushed:

app.controller('RoomCtrl', ['$scope', 'Rooms', 'Messages', 'ChatStatus', function($scope, Rooms, Messages, ChatStatus) {
    $scope.changeRoom = function(room) {
        ChatStatus.selectedRoom = room;
        Messages.getList({'room': room.id}).then(function(messages) {
            angular.forEach(messages, function(message, key) {
                message.posted = new Date(message.posted);
            });
            ChatStatus.messages[room.id] = messages;

            setTimeout(function() {
                scrollToBottom();
            }, 30);
        });
    }
    $scope.rooms = Rooms.getList()
    .then(function(rooms) {
        ChatStatus.rooms = rooms;
        ChatStatus.selectedRoom = rooms[0];
        $scope.rooms = rooms;
    })
}]);


In both controllers, I refer to the scrollToBottom function:

function scrollToBottom() {
    var chatWindow = document.querySelector('.the-chats');
    var mostRecent = chatWindow.querySelector('li:last-child');
    var mostRecentDimensions = mostRecent.getBoundingClientRect();
    var chatWindowScrollY = chatWindow.scrollTop;

    chatWindow.scrollTop = mostRecentDimensions.bottom + chatWindowScrollY;
}


If I remove the setTimeout from the first controller, it'll scroll to what is the last item in the list before the new message is pushed, while the second controller will error out.

If the setTimeout is in place, this does what I want it to do. However, it feels like a bad solution; it certainly doesn't feel like an 'Angular' way.

I've read a bit about promises, deferred objects, $q, etc., but the examples always seem to use it in the context of AJAX-types of calls, so I don't know if that applies here. But that's really what I'm looking for, right? Push the new message, then do the scroll?
If you find yourself using setTimeout and want to be more angular, check out the $timeout service in the AngularJS API.

In addition, for your purposes, you might be better off using $evalAsync.

$evalAsync waits for at least one digest cycle before executing the expression, which might be enough for the DOM to have the new elements added (though not yet rendered). This can prevent the "flicker" that you otherwise get from waiting for elements to render before manipulating them. The only reason to not use $evalAsync would be if you're dependent on the DOM rendering for accuracy.