Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
626 views
in Technique[技术] by (71.8m points)

angularjs - Example of ngAnimate for smoothly sorting an ng-repeat?

I would like to see a functional example of using angular-animate (1.2x) to sort a list. (I have only come across broken fiddles etc on the interwebs):

An ng-repeat given an array [A,B,C] and later [C, B, A] should:

  • Move A to the bottom
  • Move C to the top
  • Keep B's position

(Using CSS absolute top positioning or similar.)

An example using staggering (transition-delay) is a bonus.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The problem

Achieving what you want can be a bit tricky.

A common attempt is to use ng-style to calculate the element′s position based on its index in the list:

<div ng-repeat="c in countries | orderBy:q" ng-style="{ 'top': $index * 20 + 'px' }">

Demo: http://plnkr.co/edit/anv4fIrMxVDWuov6K3sw?p=preview

The problem is that only some elements are animated, and only towards the bottom.

Why is that?

Consider the following list sorted by name (similar to the one from the demo above):

  • 2 - Denmark
  • 3 - Norway
  • 1 - Sweden

When you sort this list by id instead only one element will move - Sweden from bottom to top. What actually happens is that the Sweden element is removed from the DOM and inserted again at its new position. However, when an element is inserted into the DOM the associated CSS transtions will normally not occur (I say normally as it ultimately depends on how the browser in question is implemented).

The other two elements remain in the DOM, get new top positions and their transitions are animated.

So with this strategy the transitions are only animated for the elements that didn't actually move in the DOM.

Another strategy is to include the ngAnimate module and use that CSS class ng-move. Almost all examples of animated ng-repeats use this.

However, this will not work because of two reasons:

  1. The ng-move class would only be applied to the elements that move (so only to the Sweden element in the example above)

  2. The ng-move class is applied to the element after it has been inserted into its new position in the DOM. You can have CSS that says "animate from opacity 0 to 1", but you can't have "animate from old position to new" since the old position is not known and each element would have to move a different distance.

A solution

A solution I've used myself in the past is to use ng-repeat to render the list but never actually resorting the underlying data. This way all the DOM elements will remain in the DOM and can be animated. To render the elements correctly use ng-style and a custom property, for example:

ng-style="{ 'top': country.position * 20 + 'px' }"

To update the position property do the following:

  1. Create a copy of the underlying data

    You could use angular.copy to copy the entire array, but with large arrays this wouldn't be good for performance. It would also be unnecessary since each object in the copied array would only need a property that is unique and the property to sort by:

    var tempArray = countries.map(function(country) {
      var obj = {
        id: country.id
      };
      obj[property] = country[property];
      return obj;
    });
    

    In the example above id is the unique property and property is a variable containing the name of the property to sort by, for example name.

  2. Sort the copy

    To sort the array use Array.prototype.sort() with a compare function:

    tempArray.sort(function(a, b) {
      if (a[property] > b[property])
        return 1;
      if (a[property] < b[property])
        return -1;
      return 0;
    });
    
  3. Set position to the element's index in the sorted copy

    countries.forEach(function(country) {
      country.position = getNewPosition(country.id);
    });
    
    function getNewPosition(countryId) {
      for (var i = 0, length = tempArray.length; i < length; i++) {
        if (tempArray[i].id === countryId) return i;
      }
    }
    

There is room for improvement, but that is the basics of it.

Demo: http://plnkr.co/edit/2Ramkg3sMW9pds9ZF1oc?p=preview

I implemented a version that used staggering, but it looked a bit weird since elements would overlap each other momentarily.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...