johngull johngull - 4 months ago 20
AngularJS Question

AngularJS pass and use external controller

In short i have next question:

How correctly pass the controller to the directive and use it in transclude block.

What i want is to give directive user a chance to use some specific controller in transclude block. The purpose to implement some specific actions that not common for different directive usages.

I believe that it is possible to send controller as attribute and then somehow add its fields to the scope in link function, but i don't see correct way.

My current try is next.
Base html:

<mylist items="list" ctrl="internalCtrl1">
<li>
{{element}} <a ng-click="showAbc()">ShowAbc</a>
</li>
</mylist>
<h3>List2</h3>
<mylist items="list" ctrl="internalCtrl2">
<li>
{{element}} <a ng-click="showXyz()">ShowXyz</a>
</li>
</mylist>


Directive:

<div>
<p>Cool list</p>
<!--<ul ng-controller="ctrl">-->
<ul>
<ng-transclude></ng-transclude>
</ul>
<div>


directive script:

.........
scope: {
ctrl: "=",
items: '='
},
controller: function($scope){
//some common stuff
},
link: function($scope, $element, $attrs, controller, $transclude) {
$scope.$watch("items", function(items) {

var el = $element.find("ul");
el.empty();

if(items)
items.forEach(function(item) {
var childScope = $scope.$new();
childScope.element = item;

$transclude(childScope, function(content) {
el.append(content);
});

});

});
}


Full code: https://plnkr.co/edit/kPSu9s9xpHJdACFzofdP

Any help very appreciated.

Upd. I have 2 problems now:


  • when i use passed controller in directive - i got error 'ctrl' is
    not a function

  • when i use fixed controller in directive - there is no
    access to control functions from transclude block


Answer

I think this is what you want...

.directive('mylist', function($controller){
  return {
    restrict: 'E',
    transclude: true,
    replace: true,
    templateUrl: 'mylist.html',
    scope: {
      ctrl: "@",
      items: '='
    },
    controller: function($scope){
      //some common stuff
    },
    link: function($scope, $element, $attrs, controller, $transclude) {
      $scope.$watch("items", function(items) {

        var el = $element.find("ul");
        el.empty();
        if(items)
          items.forEach(function(item) {
            var childScope = $scope.$new();
            childScope.element = item;
            var theCtrl = $controller($scope.ctrl, {
              $scope: childScope
            });

            $transclude(childScope, function(content) {
              el.append(content);
            });

          });

      });
    }
  };
})

https://plnkr.co/edit/7CFcOAB8HjeGARtq3K1t?p=preview

Notice I changed your ctrl binding to "@" because you really just want the name of the controller. Then in your link you instantiate your controller using the $controller object and pass your newly created child scope. It'll add the controller's method to the childScope during instantiation.

Edit:

Based on your intentions, you may want to approach this differently. Perhaps you should break up your "smart" table into multiple directives. One can be the root, and one can be the row. The row can have function bindings '&' such as on-select="myCtrl.selectItem(item)" and the user can do something like this:

<smart-table items="items" on-paginate="paginateHandler()">
  <smart-table-row on-select="selectHandler(item)" ng-repeat="item in items"></smart-table-row>
</smart-table>