2

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
4
  • 1
    if the transcluded content needs it's own controller, why would you not add ng-controller on the element that holds this content? e.g. <myList><div ng-controller="insideController">some content that needs insideController</div></mylist> Commented Jul 13, 2016 at 12:16
  • i want this inside controller been send from outside. But even when i used exact controller - ng-click not works anyway. So 2 problems in fact. Commented Jul 13, 2016 at 12:21
  • 1
    in scope, = is two way bound data exchange; you can't pass functions in this way. if you really want to do it this way, you would need & to pass ctrl as a function. However, it's still not really clear why you would do this, and it feels like you are asking for help with a workaround for a problem you aren't fully explaining, and there may be a better way to solve the problem if you explained it more fully. Commented Jul 13, 2016 at 12:40
  • Thank you for your help. The real point that i want to implement is to have directive for "smart" table. When directive itself handle most of work on show rows, paginate, search etc. But i want to give user possibility to pass full template of a row as transclude block. So user can add some actions on every row + can handle all rows with external controller (e.g. to make single selection) Commented Jul 13, 2016 at 12:45

1 Answer 1

1

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>
Sign up to request clarification or add additional context in comments.

9 Comments

Is all elements will share same controller instance?
Is it possible to connect this controller to ul element for example?
They won't share the same scope because you're doing a $new(). I thought that's what you wanted. In fact, because you're setting the item to the element property on each one, they NEED to have separate scopes. If you want to share the same controller instance and want to actually pass in the controller itself instead of the name, then you should try using the ControllerAs syntax.
I made them not to share same scope, because every element need its own scope.element object. Is there is a workaround to have different objects to show and to have same controller?
Why do you think they need the same controller instance though? Could you provide a hypothetical situation?
|

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.