Search code examples
angularjsangularjs-ng-repeatwatchangular-filterschecklist-model

How to uncheck a checkbox that gets filtered in ng-repeat


I've been scratching my head on this one for hours worth of troubleshooting and I can't seem to figure it out so was wondering if any of you could help.

I have an array of objects in a json file, and I'm making a filtering menu based on different properties in the file that one can check/uncheck in view to filter the results. The issue I have is to be able to uncheck any items in the menu that hide as a result of not being available in the current results being displayed.

I have a plunker example here: https://plnkr.co/edit/KZmMiSisA1gKyahG5rHF

Sample from plunker:

$scope.list = [
    { parent : 'fruit', type : 'orange' },
    { parent: 'fruit', type : 'apple' },
    { parent : 'fruit', type : 'kiwi' },
    { parent : 'vegetable', type : 'kale' },
    { parent : 'vegetable', type : 'cabbage' }
];
$scope.filtered = $scope.list;

$scope.selectedType = [];
$scope.selectedParent = [];

$scope.$watch(function () {
    return {
    selectedType: $scope.selectedType,
    selectedParent: $scope.selectedParent,
}
}, function (value) {
      var filterType = {
          parent : $scope.selectedParent,
          type : $scope.selectedType,
      };

      var startFilter = $scope.list;

      for (var i in filterType) {
        startFilter = filter(startFilter, filterType[i], i);
      }

      $scope.filtered = startFilter;

}, true);

Basically, if someone selects "fruit" and then "orange", but then unchecks "fruit", I would want "orange" to uncheck as well.


Solution

  • I just checked your plunker. The code on the bottom is very complicated, but I might be able to help you with these snippets.

    Add a ng-change attribute to the parents:

    <input type="checkbox" 
           checklist-model="selectedParent"
           checklist-value="key"
           data="{{::key}}" 
           ng-change="checkParent(key, checked)"/>
    

    Now you can detect the changes in your controller:

    $scope.checkParent = function(parent, checked) {
      if (!checked) {
        $scope.list.filter(function(fruit) {
          return fruit.parent === parent;
        }).forEach(function(fruit) {
          $scope.selectedType = $scope.selectedType.filter(function(_selectedType) {
          return _selectedType != fruit.type;
          });
        });
      }
    };
    

    Plunkr

    Beware, that this is inefficient, as it filters Selected type for every fruit to be unselected, it can be refactored with some nice functional tools.

    But in general I'd change the controller if possible, and create a map with this structure:

    {
       parent: {
         name: "fruit"
         selected: false,
         children: [{
           type: "organge"
           selected: false
         }]
         ...
    }
    

    This way you can make your controller code much more readable.

    Edit:

    I was checking the two filter what you wrote. I couldn't come up with a better code as I still think that you should change the data structure. Iterating over and over lists is an expensive process, and both of your filters has two nested for loops. I cannot think of an easy way of getting rid of them with your data structure.

    I spent some time on refactoring your code, getting rid of the watches and utilizing lodash. Check the updated Plunk, I hope it helps.