Strange behaviour of angular bootstrap collapse

2019-07-13 00:37发布

问题:

I faced with strange behaviour of uib-collapse. Let's assume I have a list of elements and i want each of them to be collapsed. Also i want to refresh its content periodically depend on something.

For example: i have some items and each of them have description which consists of some sections. I can pick item and description sections should be populated with item's description content. The problem is that each time i refresh its content, some sections are collapsing (despite the fact i set uib-collapse to false)

My controller:

var i = 0;
$scope.sections = [0,1,2];
$scope.next = function(nextOffset) {
    i+=nextOffset;
    $scope.sections = [i, i+1, i+2]
}

My template:

<button ng-click="next(1)" style="margin-bottom: 10px">Next item</button>
<button ng-click="next(2)" style="margin-bottom: 10px">Next next item</button>
<button ng-click="next(3)" style="margin-bottom: 10px">Next next next item</button>

<div ng-repeat="section in sections">
  <div uib-collapse="false">
    <div class="well well-lg">{{ section }}</div>
  </div>
</div>

So when i click first button, only one section does transition. When i click second, 2 section do transition and click to third button leads to all section transition.

See plunkr

Any ideas?

UPD: if $scope.sections is array of object, not of primitives, then all sections have transition in each of 3 cases. It is so ugly...

回答1:

You are not refreshing the existing content, you are adding new arrays each time, which will make ng-repeat remove the old DOM elements and insert new ones.

If you try with track by $index you will see the difference:

<div ng-repeat="section in primitiveSections track by $index">

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

Note that track by $index might not be the solution you want in your real application, I just used it for demonstration purposes.

What you probably need is to just modify the existing objects in the array.

For example:

$scope.nextObject = function(nextOffset) {

  j += nextOffset;

  $scope.objectSections.forEach(function (o, i) {
    o.content = j + i;
  });
};

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


Update

From the collapse source code:

scope.$watch(attrs.uibCollapse, function(shouldCollapse) {
  if (shouldCollapse) {
    collapse();
  } else {
    expand();
  }
});

When a new item is added the watch listener will execute, shouldCollapse will always be false in your case so it will execute the expand function.

The expand function will always perform the animation:

function expand() {
  element.removeClass('collapse')
    .addClass('collapsing')
    .attr('aria-expanded', true)
    .attr('aria-hidden', false);

  if ($animateCss) {
    $animateCss(element, {
      addClass: 'in',
      easing: 'ease',
      to: {
        height: element[0].scrollHeight + 'px'
      }
    }).start().finally(expandDone);
  } else {
    $animate.addClass(element, 'in', {
      to: {
        height: element[0].scrollHeight + 'px'
      }
    }).then(expandDone);
  }
}

If this is the intended behavior or not I don't know, but this is the reason why it happens.



回答2:

this is a comment on the original ui-bootstrap library: (and the new uib prefixed directive doesn't comply this comment.)

// IMPORTANT: The height must be set before adding "collapsing" class. Otherwise, the browser attempts to animate from height 0 (in collapsing class) to the given height here.

use the deprecated "collapse" directive instead of new "uib-collapse" until it gets fixed.