dutzi
dutzi

Reputation: 1928

View only updates on the next $digest, when modifying it inside of a directive's controller

I am trying to write a directive for something that will behave like an advanced combo-box, I've started with the skeleton and encountered some issues.

Here is the code for the directives:

/* Controller */
function MyCtrl($scope) {
    $scope.bigListSelectedItem = 0;
    $scope.bigComboItems = [
        {value: 'item1 value', label: 'Item 1 label'}, 
        {value: 'item2 value', label: 'Item 2 label'}, 
        {value: 'item3 value', label: 'Item 3 label'}
    ];

    $scope.addBigComboItem = function () {
        $scope.bigComboItems.push({value: 'new item', label: 'Item 1 label'});
    };

    $scope.removeBigComboItem = function () {
        console.log('removing');
        $scope.bigComboItems.splice($scope.bigComboItems.length - 1, 1);

    };
}

MyCtrl.$inject = ['$scope'];

/* Services*/
var services = angular.module('myApp', [])
    .directive('bigCombo', function () {
        return {
            restrict: 'C',
            transclude: true,
            scope: true,
            controller: function ($scope, $element, $attrs) {
                $scope.items = [];
                this.addItem = function (item) {
                    $scope.items.push(item);
                };

                this.removeItem = function (item) {
                    for (var i = 0; i < $scope.items.length; i++) {
                        if ($scope.items[i] === item) {
                            $scope.items.splice(i, 1);
                            break;
                        }
                    }
                };

                $scope.selectItem = function(item) {
                    $scope.selectedItem =  item;
                };
            },

            template:
                '<div>' +
                    '<div>Selected Item {{selectedItem.value}}</div>' +
                    '<ul class="">' +
                        '<li ng-repeat="item in items">' +
                            '<a ng-click="selectItem(item)">{{item.value}}</a>' +
                        '</li>' +
                    '</ul>' +
                    '<div ng-transclude></div>' +
                '</div>',

            replace: true

        };
    }).
    directive('bigComboItem', function() {
        return {
            require: '^bigCombo',
            restrict: 'C',
            scope: { 
                value: '@'
            },

            link: function(scope, element, attrs, controller) {
                controller.addItem(scope);
                scope.$on('$destroy', function () {
                    controller.removeItem(scope);
                });
            }
        };
    });

You can see it running here: http://jsfiddle.net/HP5tQ/

As you can see, the outer 'bigCombo' directive waits for 'bigComboItem' directives to call its 'addItem' function. That works fine. But, if I remove one of the items, the View won't update until (at least that's what I suspect) the next $digest occurs.

In the example above, clicking 'Remove Item' will remove the last item from the array, which will cause ng-repeat to remove it's 'bigComboItem' directive from the DOM, which will emit a '$destory' event, which will call 'bigCombo's 'removeItem' function. 'removeItem' will then remove it, but the view doesn't update unless I add/remove another item from the array, or force a $digest on the scope.

Any ideas what am I doing wrong here?

Upvotes: 0

Views: 138

Answers (1)

rewritten
rewritten

Reputation: 16435

Just use $timeout on the listener (inject it in the directive definition:

scope.$on('$destroy', function (ev) {
    $timeout(function() { controller.removeItem(scope); });
});

This will ensure that the removeItem is called inside an $apply block. The good thing of this approach is that 1. If you are already in an $apply block, nothing different happens 2. If there is an $apply scheduled, then the function is put at the end of the already scheduled block 3. Otherwise, a $digest will be scheduled for ASAP, with the function inside.

So it's a win-win :)

Upvotes: 2

Related Questions