Handle open/collapse events of Accordion in Angular

Solution 1:

Accordion groups also allow for an accordion-heading directive instead of providing it as an attribute. You can use that and then wrap your header in another tag with an ng-click.

<accordion-group ng-repeat="group in groups" heading="{{group.title}}" is-open="group.open">
  <accordion-heading>
    <span ng-click="opened(group, $index)">{{group.content}}</span>
  </accordion-heading>
</accordion-group>

Example: http://plnkr.co/edit/B3LC1X?p=preview

Solution 2:

Here's a solution based on pkozlowski.opensource solution.
Instead of adding a $watch on each item of the collection, you can use a dynamically defined Property. Here, you can bind the IsOpened property of the group to the is-open attribute.

<accordion-group ng-repeat="group in groups" heading="{{group.title}}" is-open="group.IsOpened">
   {{group.content}}
</accordion-group>

So, you can dynamically add the IsOpened property on each item of the collection in the controller :

$scope.groups.forEach(function(item) {
  var isOpened = false;
  Object.defineProperty(item, "IsOpened", {
    get: function() {
      return isOpened;
    },
    set: function(newValue) {
      isOpened = newValue;
      if (isOpened) {
        console.log(item); // do something...
      }
    }
  });
});

Using properties instead of watches is better for performances.

Solution 3:

There is the is-open attribute on the accordion-group which points to a bindable expression. You could watch this expression and execute some logic when a given accordion group is open. Using this technique you would change your markup to:

<accordion-group ng-repeat="group in groups" heading="{{group.title}}" is-open="group.open">
   {{group.content}}
</accordion-group>

so that you can, in the controller, prepare a desired watch expression:

$scope.$watch('groups[0].open', function(isOpen){
    if (isOpen) {
      console.log('First group was opened'); 
    }    
  });

While the above works it might be a bit cumbersome to use in practice so if you feel like this could be improved open an issue in https://github.com/angular-ui/bootstrap

Solution 4:

Here's a solution inspired by kjv's answer, which easily tracks which accordion element is open. I found difficult getting ng-click to work on the accordion heading, though surrounding the element in a <span> tag and adding the ng-click to that worked fine.

Another problem I encountered was, although the accordion elements were added to the page programmatically, the content was not. When I tried loading the content using Angular directives(ie. {{path}}) linked to a $scope variable I would be hit with undefined, hence the use of the bellow method which populates the accordion content using the ID div embedded within.

Controller:

    //initialise the open state to false
    $scope.routeDescriptors[index].openState == false

    function opened(index) 
    {
        //we need to track what state the accordion is in
        if ($scope.routeDescriptors[index].openState == true){   //close an accordion
            $scope.routeDescriptors[index].openState == false
        } else {    //open an accordion
            //if the user clicks on another accordion element
            //then the open element will be closed, so this will handle it
            if (typeof $scope.previousAccordionIndex !== 'undefined') {
                $scope.routeDescriptors[$scope.previousAccordionIndex].openState = false;
            }
            $scope.previousAccordionIndex = index;
            $scope.routeDescriptors[index].openState = true;
    }

    function populateDiv(id)
    {
        for (var x = 0; x < $scope.routeDescriptors.length; x++)
        {
            $("#_x" + x).html($scope.routeDescriptors[x]);
        }
    }

HTML:

        <div ng-hide="hideDescriptions" class="ng-hide" id="accordionrouteinfo" ng-click="populateDiv()">
            <accordion>
                <accordion-group ng-repeat="path in routeDescriptors track by $index">
                    <accordion-heading>
                        <span ng-click="opened($index)">route {{$index}}</span>
                    </accordion-heading>
                    <!-- Notice these divs are given an ID which corresponds to it's index-->
                    <div id="_x{{$index}}"></div>
                </accordion-group>
            </accordion>
        </div>