dror
dror

Reputation: 3936

How to access parent's controller function from within a custom directive using *parent's* ControllerAs?

I'm in need of building a transformation directive that transforms custom directives into html.

linkClicked should be called on the parent controller of the directive.

It would have been very easy if I was the one responsible for the html holding the 'link' directive (using isolated scope), but I'm not. It's an as-is input and I have to figure a way to still do it.

There are countless examples on how to do similar bindings using the default scope of the directive, but I'm writing my controllers using John Papa's recommendations with controllerAs, but don't want to create another instance on the controller in the directive.

This is what I have reached so far:

(function () {
    'use strict';

    angular
        .module('app')
        .directive('link', link);

    link.$inject = ['$compile'];

    function link($compile) {
        return {
            restrict: 'E',
            replace: true,
            template: '<a class="someclass"></a>',
            terminal: true,
            priority: 1000,
            link: function (scope, element, attributes) {
                element.removeAttr('link'); // Remove the attribute to avoid indefinite loop.

                element.attr('ng-click', 'linkClicked(\'' + attributes.text + '\')');

                $compile(element)(scope);
        },
    };
}
})();

The problem is I don't know which controller will use my directive and can't hard-code the 'abc' name in it.

What do you suggest I should be doing?

Upvotes: 0

Views: 756

Answers (1)

New Dev
New Dev

Reputation: 49590

It's difficult to understand from your question all the constraints that you are facing, but if the only HTML you get is:

<link text="some text">

and you need to generate a call to some function, then the function must either be:

  1. assumed by the directive, or
  2. conveyed to the directive

#1 is problematic because the user of the directive now needs to understand its internals. Still, it's possible if you assume that a function name is linkClicked (or whatever you want to call it), and the user of your directive would have to take special care to alias the function he really needs (could be done with "controllerAs" as well):

<div ng-controller="FooCtrl as foo" ng-init="linkClicked = foo.actualFunctionOfFoo">
   ...

   <link text="some text">

   ...
</div>
app.directive("link", function($compile){
  return {
    transclude: "element", // remove the entire element
    link: function(scope, element, attrs, ctrl){
      var template = '<a class="someclass" ng-click="linkClicked(\'' + 
                      attrs.text +
                     '\')">link</a>';

      $compile(template)(scope, function(clone){
        element.after(clone);
      });
    }
  };
});

Demo

#2 is typically achieved via attributes, which isn't possible in your case. But you could also create a sort of "proxy" directive - let's call it onLinkClick - that could execute whatever expression you need:

<div ng-controller="FooCtrl as foo"
     on-link-click="foo.actualFunctionOfFoo($data)">
   ...

   <link text="some text">

   ...
</div>

The link directive now needs to require: "onLinkClick":

app.directive("link", function($compile){
  return {
    transclude: "element", // remove the entire element
    scope: true,
    require: "?^onLinkClick",
    link: function(scope, element, attrs, ctrl){
      if (!ctrl) return;
      var template = '<a class="someclass" ng-click="localClick()">link</a>';

      scope.localClick = function(){
        ctrl.externalFn(attrs.text);
      };

      $compile(template)(scope, function(clone){
        element.after(clone);
      });
    }
  };
});

app.directive("onLinkClick", function($parse){
  return {
    restrict: "A",
    controller: function($scope, $attrs){
      var ctrl = this;
      var expr = $parse($attrs.onLinkClick);

      ctrl.externalFn = function(data){
        expr($scope, {$data: data});
      };
    },
  };
});

Demo

Notice that having a link directive would also execute on <link> inside <head>. So, make attempts to detect it and skip everything. For the demo purposes, I used a directive called blink to avoid this issue.

Upvotes: 1

Related Questions