AngularJS - hide parent element if children loop is empty (filtered)

Problem

I have a case in which I have nested loops in which the child one is constructed by a filter function that takes parent as the argument. I also have another filter that just does a text comparison. Here is the example

<div ng-repeat="group in groups">
  {{group.name}}
  <div ng-repeat="material in materials | filter:filterByGroup(group) | filter:search ">
    {{material.name}}
  </div>
</div>

Now, my problem is that when filter:search is applied and it filters out all the results in specific group, I would like to hide the group (and not leave the empty group.name hanging without child elements).

I don't have the materials in the group it self, so I don't have that information in the parent ng-repeat scope. The question is if there is a way I can access the nested ng-repeat and see its count from the parent and hide the parent if that count is 0.

UPDATE

Here is a fiddle that better explains the situation: fiddle

The main problem is that I don't want to associate my materials with groups. I could do that if nothing else works, but it sounds like an overload (since I would then need to basically filter the results twice) if I could do it by just checking the nested loop.

Thanks

Problem courtesy of: Filip Kis

Solution

A much cleaner solution was suggested HERE.

What you need to do is wrap the relevant area with an ng-show / ng-if based on an expression that applies the filter on the data structure and extracts length. Here is how it works in your example:

  <div ng-show="(materials | filter:filterByGroup(group)).length">
    <div ng-repeat="group in groups">
      {{group.name}}
      <div ng-repeat="material in materials | filter:filterByGroup(group) | filter:search ">
        {{material.name}}
      </div>
    </div>
  </div>

This allows you to hide complex structures once they are empty due to filtering, e.g. a table of results.

Solution courtesy of: Omri Spector

Discussion

If you can ask for the materials by group and you expect to do that for each group anyway, why not do that right away when you initialize the view and build a model with groups that have materials? If you can do that you can use ng-show to only hide groups that have materials.

You seem to need to know that a group has materials or not somehow? Here's a fiddle without knowing much of the background story:

<div ng-controller="MyCtrl">

  <div ng-repeat="group in groups">
      <div ng-show="groupHasMaterials(group)">{{group.name}}</div>
      <div ng-repeat="material in materialsByGroup(group)">
          <div>{{material.name}}</div>
      </div>
  </div>

</div>


<script>
var myApp = angular.module('myApp',[]);

function MyCtrl($scope) {

    var groups = [
        {'name':'group one'},
        {'name':'group two'}
    ];

    var materials = [
        {'name':'material1'},
        {'name':'material2'},
        {'name':'material3'}
    ];

    $scope.groups = groups;
    $scope.materials = materials;

    $scope.groupHasMaterials = function(group){
        return $scope.materialsByGroup(group).length > 0;    
    }

    $scope.materialsByGroup = function(group){
        return group.name === 'group one' 
            ? [materials[0], materials[1]] 
            : [];
    }
}
</script>

fiddle with groups

Discussion courtesy of: marko

I've seen this use-case a few times, here's my solution:

<div ng-repeat="group in groups">
    <div ng-repeat="material in materials | filter:filterByGroup(group) | filter:search ">
        <span ng-show="$first">
            {{group.name}}<br/>
        </span>
        {{material.name}}
    </div>
</div>

You can use $first or $last within the scope of the ng-repeat to show only for the first and last of each group. if there is no $first, it won't show the group name.

I just implemented this on my blog and updated your fiddle here: http://jsfiddle.net/ke793/1/

I'm not sure if this is the most elegant solution, but it seems fairly simple and it works. I'd love to see how others solved this.

Update: just realized you can use ng-if to prevent the group name from hitting the dom at all outside of the $first element. Little bit cleaner than ng-hide/ng-show, which sets display: none to the extra header every time.

Discussion courtesy of: Russ Matney

This recipe can be found in it's original form on Stack Over Flow.