Reputation: 32680
I'm working on an Angular (1.2.1) app, and I've got a directive with an ng-repeat
in the template like so:
<div ng-repeat="item in list | filter: filterList"></div>
<button ng-click="setColorMode('red')">Show only red items</button>
<button ng-click="setSizeMode('small')">Show only small items</button>
<!-- etc... -->
something like this in a directive controller:
$scope.list = [
{ color: 'red', size: 'large' },
{ color: 'blue', size: 'small' }
//...
]
$scope.colorMode = null;
$scope.sizeMode = null;
$scope.setColorMode = function(value){$scope.colorMode = value;}
$scope.setSizeMode = function(value){$scope.sizeMode = value;}
$scope.filterList = function(item){
if ($scope.colorMode){
if ($scope.colorMode != item.color) return false;
}
if ($scope.sizeMode){
if ($scope.sizeMode != item.size) return false;
}
return true;
}
colorMode
and sizeMode
are initialized to null, meaning that all items of all colors and sizes are displayed in the repeat (because both of the conditionals in the filter function evaluate to false, so true is returned).
The UI allows the user to filter the list to show only items of a given size and/or color, in which case the appropriate $scope.___mode
variable is set to the value given, causing the filter function to check and return false on items whose field values do not match the value given.
My problem is that toggling between modes changes variables on the $scope, but it does not change the data that the ng-repeat is bound to. As a result, the filter function isn't re-executed when the mode changes, and the contents of the ng-repeat remain unchanged until something in the data does change.
Intuitively, I had expected that, because those $scope fields are used to determine the results of a filter function explicitly mentioned in the ng-repeat, that angular would $watch those values and update on changes to them. Alas, that does not seem to be the case, so I'm left to ask:
What's the cleanest way to do what I'm trying to do?
I could hack around this by altering something on the data array in my mode setter functions, but (contrary to my simplified example here), the data doesn't belong to the controller - it's accessed through a service that shares it with other controllers simultaneously, so I'd really rather not do that. And in any case, surely there's a better solution... right?
Upvotes: 2
Views: 938
Reputation: 486
You could instead bind your ng-repeat
to a new $scope.filteredList
list which you initialize to have the same data as $scope.list
. New div:
<div ng-repeat="item in filteredList"></div>
EDIT: In order to optimize this a little better and make this a single $watch
function, you should add each mode variable to a single object $scope.modes
. Then your modes are stored as $scope.modes.colorMode
, $scope.modes.sizeMode
, etc.
Then you can use a $watchCollection
on the $scope.modes
variable to watch for changes in any of the modes. Within that watch you can run the built-in filter on the $scope.list
, something like this:
$watchCollection('modes', function(newValue, oldValue){
angular.copy($scope.list, $scope.filteredList);
if($scope.modes.colorMode){
$scope.filteredList = $filter('filter')($scope.filteredList, {color: $scope.modes.colorMode});
}
if($scope.modes.sizeMode){
$scope.filteredList = $filter('filter')($scope.filteredList, {size: $scope.modes.sizeMode});
}
}
I created a simple JSFiddle here
Upvotes: 0
Reputation: 9476
You have several options: 1. Do filtering manually (Create array filteredList, fill it, in methods setXXXMode recompute it manually) 2. Write custom filter, in js:
app.filter('myfilter', function() {
return function(itemlist, mode, value) {
if (mode...)
}
})
in html:
ng-repeat="item in list | myfilter:mode:value"
<button ng-click="mode='color';value='red'">Show only red items</button>
2nd approach looks nicer and here i'd prefer it; but if your array is huge and contain complex objects sometimes 1 approach is better, cause u can always control how often filtering launches.
and you can use filter like this:
P.S. if your example is exact, then u can just use:
ng-repeat="item in list | filter:{mode:value}"
<button ng-click="mode='color';value='red'">Show only red items</button>
Upvotes: 0