Angular: Getting list with ng-repeat with dividers

2019-05-28 09:22发布

Still pretty new with Angular, just finding my way around.

I'm using ng-repeat to output an alphabetised list of names. I'd like to add dividers within this list that act as labels.

Example:

--------
A
--------
Author 1
Author 2

--------
B
--------
Author 3
Author 4
etc

My thinking is to use nested ng-repeats to loop through the alphabet, getting an object with the authors for that specific letter with a second ng-repeat. Here's what I have so far:

<div data-ng-repeat="letter in alphabet">
    <div class="item item-divider">
        {{letter}}
    </div>
    <ul>
        <li data-ng-repeat="speaker in GetSpeakers(letter)" type="item-text-wrap" href="#/speaker/{{speaker.ID}}">
            {{speaker.title}}
        </li>
    </ul> 
</div>

Controller code:

.controller('SpeakersCtrl', function($scope, $routeParams, StorageHandler) {

    $scope.GetSpeakers = function(letter) {
        // Get list of authors for that letter
        console.log('test '+letter);
    };

    $scope.alphabet = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'];
})

Fiddle: http://jsfiddle.net/t6Xq8/

I have a couple of questions.

  1. In general, is using a nested ng-repeat a good approach for this problem, or does Angular have built-in specifically for this purpose? Some sources also say using a function in ng-repeat is a bad idea. But it does work so I'm confused as to why I shouldn't use this.
  2. When looking at the console, GetSpeakers gets called twice in this example and I can't figure out why?
  3. How should I return an object to the scope within the GetSpeakers function, while preventing overloading the $scope?

3条回答
Summer. ? 凉城
2楼-- · 2019-05-28 09:46

Likely better to map the data into a single object where the object keys are the letter. I'll assume you have objects like:

{id:123, firstName:'Frank',lastName :'Enstein'} 

and want the letter to represent last names

var tmp={};
for(i=0;i<authourArray.length;i++){
    var letter=authourArray[i].lastName.charAt(0);
   if( tmp[ letter] ==undefined){
        tmp[ letter]=[]
   }
     tmp[ letter].push( authourArray[i] );
}

/* likely want to loop over all the arrays now and sort unless already sorted from server*/

$scope.repeaterObject=tmp;

Now in markup will ng-repeat all the letters in the object keys, and within that loop, do an ng-repeat of the authors array for that letter

<div data-ng-repeat="(letter, authors) in repeaterObject">
    <div class="item item-divider">
        {{letter}}
    </div>
    <ul>
        <li ng-repeat="author in authors">{{author.firstName}} {{author.lastName}}</li>
    </ul>
</div>

Resulting object will look like:

{
   A:[.....],
   ......
   E:[ {id:123, firstName:'Frank',lastName :'Enstein'}, /* other E author objects*/ ],
   ......
}
查看更多
beautiful°
3楼-- · 2019-05-28 09:47

I believe that using nested ng repeats is perfectly fine. Im not familiar with a better way that angularjs provides to iterate over multi dimensional arrays/data structures.

The reason you should avoid using functions in ng repeats is that angularjs will call that function each time it will create the element in the repeat directive. As charlie suggested above it would be better to order the authors once and use the resulting array each time, rather than ordering the authors each time you display them. This has the added benefit of being able to reuse the array

查看更多
小情绪 Triste *
4楼-- · 2019-05-28 10:02

I think you can do this in a much simpler way without:

  • having to do a nested ng-repeat
  • writing out the whole alphabet
  • needing to handle letters which have no author
  • manipulating your original array of objects or copying it
  • writing much fewer lines of code

by using $index.

so that would look something like this:

<div ng-repeat="speaker in speakers | orderBy : 'name'">
    <div class="item item-divider" ng-if="firstLetter(speaker.name) != firstLetter(speakers[$index-1].name)">
      {{firstLetter(speaker.name)}}
    </div>
    {{speaker.name}}
</div>

This would need a simple function to get the first letter such as:

function firstLetter(name) {
  return name && name.charAt(0);
}

So what this does is it compares the first letter of whatever you passed to the previous object's first letter and if they are different it adds the divider with that letter. Pretty clean and simple :)

Check out this working JsFiddle

You can obviously improve on that code to handle upper/lowercase (i.e. always capitalize before comparing) as well as extract the comparison into a function for cleaner code.

查看更多
登录 后发表回答