Search code examples
javascriptangularjsnested-loops

orderBy in inner loop on nested ng-repeat


I want to filter websites that are in the filtered categories and display them in order of their rank.The code snippet is:

<div ng-repeat="category in categories | filter:{will return more than one category}">
    <div ng-repeat="website in websites | orderBy:'rank'| filter:{ parent_id : category.id }:true">
      {{website.title}},{{website.rank}}
   </div>
</div>

But in the nested loop, since orderby is in inner loop, it works for each iteration of outer loop but the overall result is not sorted according to rank. Say there are three categories and filter gives cat1 &cat2. If websites with rank 6,2,5 are is cat1 and 9,1 in cat2 then the result will be 2,5,6,1,9.I want the result to be 1,2,5,6,9.How should I do that ?

Should I pass the category in some function and write the js code to get the array of filtered website and then sort them and return them to template or is there any other better way to do that in template itself?


Solution

  • I think what you want to do, can not be done as is. Anyway you could use a custom filter.

    New Answer

    This approach gives you a category selection mechanism as another example of how you could use this custom filter.

    angular.module('app',[])
    
    // categories
    .value('categories',  [ { id: 0, title:"first" }, { id: 1, title:"second" }, { id: 2, title:"third" } ])
    
    // websites
    .value('websites',  [   { rank: 3, parent_id: 2, title: "Alice" },
                            { rank: 1, parent_id: 1, title: "Bob" },
                            { rank: 9, parent_id: 1, title: "Carol" },
                            { rank: 2, parent_id: 0, title: "David" },
                            { rank: 4, parent_id: 0, title: "Emma" },
                            { rank: 5, parent_id: 0, title: "Foo" } ])
    
    // controller, 
    .controller('ctrl', ['$scope', 'categories', 'websites', function($scope, categories, websites) {
        // categories injected
        $scope.categories = categories;
        // websites injected
        $scope.websites = websites;  
        // categories selection helper, useful for preselection
        $scope.selection = { 0: true, 1:false }  // 2: false (implicit)
      
    }])
    
                         
    // categories filter, categories injected.
    .filter('bycat', ['categories', function(categories) {
    
      // websites is the result of orderBy :'rank', selection helper passed as paramenter.
      return function(websites, selection) {
        
        // just an Array.prototype.filter
        return websites.filter(function(website) {
              // for each category                    
              for(var i=0; i < categories.length; i++) {
                  var cat = categories[i];
                  // if category is selected and website belongs to category
                  if (selection[cat.id] && cat.id == website.parent_id) { 
                      // include this website
                      return true;
                  } 
              }
              // exclude this website
              return false;
         });
      };
    }]);
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
    
    <div ng-app="app" ng-controller="ctrl">
      <span ng-repeat="category in categories">
          <label class="checkbox" for="{{category.id}}">
            <input type="checkbox" ng-model="selection[category.id]" name="group" id="{{category.id}}" />
            {{category.title}}
          </label>
        </span>
      <div ng-repeat="website in websites | orderBy:'rank'| bycat: selection">
        <p>Rank:{{website.rank}} - {{website.title}} ({{categories[website.parent_id].title}})</p>
      </div>
    </div>

    Old Ansewer

    Se code comments.

    angular.module('app',[])
    
    // categories will be injected in custom filter.
    .value('categories',  [ { id: 1, title:"first" }, { id: 2, title:"second" } ])
    
    .controller('ctrl', function($scope) {        
        // sample websites
        $scope.websites = [ { rank: 1, parent_id: 2, title: "Site w/rank 1" },
                            { rank: 9, parent_id: 2, title: "Site w/rank 9" },
                            { rank: 2, parent_id: 1, title: "Site w/rank 2" },
                            { rank: 4, parent_id: 1, title: "Site w/rank 4" },
                            { rank: 5, parent_id: 1, title: "Site w/rank 5" } ];
      
    })
    
    // custom filter, categories injected.
    .filter('bycat', ['categories', function(categories) {
    
      // websites is the result of orderBy :'rank'
      return function(websites, catText) {    
        
        // just an Array.prototype.filter
        return websites.filter(function(website) {
              // if no filter, show all.
              if (!catText) return true;
             
              for(var i=0; i < categories.length; i++) {
                  var cat = categories[i];
                  // if matches cat.title and id == parent_id, gotcha!
                  if (cat.title.indexOf(catText) != -1 && cat.id == website.parent_id) { 
                      return true;
                  } 
              }
              // else were 
              return false;
          });
      };
    }]);
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
    
    <div ng-app="app" ng-controller="ctrl">
      <input type="text" ng-model="filterText">
      <p>Try "first" and "second"</p>
      <div ng-repeat="website in websites | orderBy:'rank'| bycat: filterText ">
        {{website.title}},{{website.rank}}
      </div>
    </div>