2
votes

To illustrate what i'm trying to achieve, I will provide an incomplete accordion widget : http://plnkr.co/edit/aiUKTRmRk5qFqPKCy2Md?p=preview

I have 2 accordion-pane directives, sitting next to eachother in my HTML.

    <accordion-pane>
      <accordion-header>
        <h3>Pane 1</h3>
      </accordion-header>
    </accordion-pane>

    <accordion-pane>
      <accordion-header>
        <h3>Pane 2</h3>
      </accordion-header>
    </accordion-pane>

and the angular/js code :

  .directive('accordionPane', [function () {
    return {
      transclude: true, 
      replace: true,
      //scope: {}, // Isolating the scope breaks transclusion
      templateUrl: 'accordion-pane.html',
      link: function (scope) {
        scope.toggle = function () {
          scope.active = !scope.active;
        };
      }
    };
  }])
  .directive('accordionHeader', [function () {
    return {
      templateUrl: 'accordion-header.html',
      transclude: true,
      replace: true
    };
  }]);

These panes have transclude: true, so that what I add in my html, gets wrapped in their templates.

Each pane must also contain an accordion-header directive, and this directive, uses a scope variable to display itself in a collapsed or expanded state.

The problem, as you can see in this plunker is that, I cannot isolate the scope properly.

The default behaviour is that both panes share the same scope, so when I expand one, they both expand.

If I try to isolate the scope on the accordion-pane directive ( by uncommenting the scope: {} line in script.js ), then the scope of accordion-pane isn't transcluded down into it's accordion-header child.

This seems to me like a logical way of aranging things, but obviously I'm wrong. Any thoughts on how this could be achieved, and why isolating the scope on the parent, makes it unavaiable to the child ?

Edit: As Sean pointed out on gitter :

"Per angular docs:

"The transclude option changes the way scopes are nested. It makes it so that the contents of a transcluded directive have whatever scope is outside the directive, rather than whatever scope is on the inside. In doing so, it gives the contents access to the outside scope.""

This makes sense ok, but ... How can I isolate scope and use ngTransclude at the same time ?

Edit 2:

Jim's suggestion worked really well in the plunkr. So I have updated the plnkr, with a more complex example : http://plnkr.co/edit/sbiSF8OQ2Ang99Z0OaHB?p=preview

The problem here is that, the scope doesn't seem to be isolated from the accordion-pane down, but instead, it's isolated amongst the child siblings.

So by moving the toggle function onto the accordion-header, accordion-header and accordion-body can access that scope, but accordion-pane ( their parent ), does not have access.

Once again what I'm trying to achieve here is to isolate the scope underneath the accordion-pane, and have that scope shared amongst accordion-pane, accordion-body and accordion-header. Currently the scope is only shared between accordion-body and accordion-header.

2

2 Answers

2
votes

Why the isolated scope on the directive causes transclude to break, I am not sure.

But, in terms of finding a solution simply using the template you have setup. Add ng-if="1" to each accordion pane element.

Per ng-if documentation:

Note that when an element is removed using ngIf its scope is destroyed and a new scope is created when the element is restored. The scope created within ngIf inherits from its parent scope using prototypal inheritance

Therefore by adding that ng-if="1" attribute/directive to the element, you are isolating the scope and therefore $scope.active will be unique to each element.

You can look at this revised Plunker Example:

1
votes

It looks like your issue isn't with scoping, it's with where you've put the toggle. This will expand and collapse each pane individually (move toggle from accordionPane to accordionHeader link function):

angular.module('accordion-test', [])
  .directive('accordionPane', [function () {
    return {
      transclude: true, 
      replace: true,
      //scope: {}, // Isolating the scope breaks transclusion
      templateUrl: 'accordion-pane.html'
    };
  }])
  .directive('accordionHeader', [function () {
    return {
      templateUrl: 'accordion-header.html',
      transclude: true,
      replace: true,
      link: function(scope,elem,attr,ctrl){
        scope.toggle = function () {
          scope.active = !scope.active;
        };
      }
    };
  }]);

I'll work out an example for collapsing others if you'd like.