1

I want to create a component that displays itself as a collapsible box. When it is expanded, it should show the transcluded content; when it is collapsed it should only show its label.

myApp.directive('collapsingBox', function() {

  return {
    restrict: 'E',
    transclude: true,
    require: '^ngModel',
    scope: {
      ngModel: '='
    },
    template: '<div ng-controller="CollapseController" class="collapsingBox"><div class="label">Title: {{ ngModel.title }}</div><br/><div ng-transclude ng-show="expanded">Test</div></div>',
    link: function($scope, element, attr) {
      element.bind('click', function() {
        alert('Clicked!');
        $scope.toggle();
      });
    }
  };

});

This component should be reusable and nestable, so I wanted to manage the values (like "title" and "expanded") in a controller that gets instantiated for every use of the directive:

myApp.controller('CollapseController', ['$scope', function($scope) {
  $scope.expanded = true;

  $scope.toggle = function() {
    $scope.expanded = !$scope.expanded;
  };
}]);

This "almost" seems to work: http://plnkr.co/edit/pyYV0MAikXThvMO8BF69

The only thing that does not work seems to be accessing the controller's scope from the event handler bound during linking.

link: function($scope, element, attr) {
  element.bind('click', function() {
    alert('Clicked!');
    $scope.toggle(); // this is an error -- toggle is not found in scope
  });
}
  1. Is this the correct (usual?) way to create one instance of the controller per use of the directive?
  2. How can I access the toggle-Function from the handler?

1 Answer 1

1

Rather than using ng-controller on your directive's template, you need to put the controller in your directive's controller property:

return {
  restrict: 'E',
  transclude: true,
  require: '^ngModel',
  scope: {
    ngModel: '='
  },
  template: '<div class="collapsingBox"><div class="label">Title: {{ ngModel.title }}</div><br/><div ng-transclude ng-show="expanded">Test</div></div>',
  controller: 'CollapseController',
  link: function($scope, element, attr) {
    element.bind('click', function() {
      alert('Clicked!');
      $scope.toggle();
    });
  }
};

As it is CollapseController's scope will be a child scope of your directive's scope, which is why toggle() isn't showing up there.

Sign up to request clarification or add additional context in comments.

2 Comments

This does what I want, mostly - although the visibility does not update, so I probably am missing some watch/binding. After reading the documentation and some tutorials I had been under the impression that the controller property is intended for sharing one controller for all instances (for communication between instances).
I had to use $scope.$apply() to update the values in .toggle().

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.