Reputation: 1928
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
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