M Miller
M Miller

Reputation: 5642

Getting Angular to detect change in $scope

I am writing my first AngularJS app and I'm trying to get a directive to update its view when an array it received from the service changed.

My directive looks like this:

angular.module('Aristotle').directive('ariNotificationCenter', function (Notifications) {
    return {
        replace: true,
        restrict: 'E',
        templateUrl: 'partials/ariNotificationCenter.html',
        controller: function ($scope) {
            $scope.notifications = Notifications.getNotifications();

            $scope.countUnread = function () {
                return Notifications.countUnread();
            };
        }
    };
});

The partial is quite simply:

<p>Unread count: {{countUnread()}}</p>

While my Notifications service looks like this:

function Notification (text, link) {
    this.text = text;
    this.link = link;
    this.read = false;
}

var Notifications = {
    _notifications: [],

    getNotifications: function () {
        return this._notifications;
    },

    countUnread: function () {
        var unreadCount = 0;

        $.each(this._notifications, function (i, notification) {
            !notification.read && ++unreadCount;
        });

        return unreadCount;
    },

    addNotification: function (notification) {
        this._notifications.push(notification);
    }
};

// Simulate notifications being periodically added
setInterval(function () {
    Notifications.addNotification(new Notification(
        'Something happened!',
        '/#/somewhere',
        Math.random() > 0.5
    ));
}, 2000);

angular.module('Aristotle').factory('Notifications', function () {
    return Notifications;
});

The getNotifications function returns a reference to the array, which gets changed by the setInterval setup when addNotification is called. However, the only way to get the view to update is to run $scope.$apply(), which stinks because that removes all the automagical aspect of Angular.

What am I doing wrong?

Thanks.

Upvotes: 0

Views: 617

Answers (2)

Chandermani
Chandermani

Reputation: 42669

I believe the only problem with you code is that you are using setInterval to update the model data, instead of Angular built-in service $interval. Replace the call to setInterval with

$interval(function () {
    Notifications.addNotification(new Notification(
        'Something happened!',
        '/#/somewhere',
        Math.random() > 0.5
    ));
}, 2000);

And it should work without you calling $scope.$apply. Also remember to inject the $interval service in your factory implementation Notifications.

angular.module('Aristotle').factory('Notifications', function ($interval) {

$interval internally calls $scope.$apply.

Upvotes: 2

Chad
Chad

Reputation: 1549

I'm not an expert at Angular yet, but it looks like your problem may be in the partial.

<p>Unread count: {{countUnread()}}</p>

I don't think you can bind to a function's results. If this works, I believe it will only calculate the value once, and then it's finished, which appears to be the issue you are writing about.

Instead, I believe you should make a variable by the same name:

$scope.countUnread = 0;

And then update the value in the controller with the function.

Then, in your partial, remove the parentheses.

<p>Unread count: {{countUnread}}</p>

As long as $scope.countUnread is indeed updated in the controller, the changes should be reflected in the partial.

And as a side note, if you take this approach, I'd recommend renaming either the variable or the function, as that may cause issues, or confusion at the very least.

Upvotes: 1

Related Questions