Ahmad Baktash Hayeri
Ahmad Baktash Hayeri

Reputation: 5880

Require a directive's controller that is nested in a parent directive, AngularJS

I'm wondering if there is a way to require the controller of a directive that exists/is nested somewhere as a common parent's child directive in AngularJS.

Directive Structure

Suppose I have the following structure for my directives:

<parent-directive>
    <ul>
        <li some-nested-directive ng-repeat="dir in directives"></li>
    </ul>
    <settings-menu></settings-menu>
</parent-directive>

Directive Definition

/* 
* some-nested-directive directive definition
*/
.directive('someNestedDirective', function(){
    // ...
    return {
       restrict: 'A',
       controller: someNestedDirectiveController
    };
});

/* 
* settings-menu directive definition
*/
.directive('settingsMenu', function(){
    return {
        restrict: 'AE',
        require: [], // how to require the nested-directive controller here?
        link: function(scope, iElement, attrs, ctrls){
            // ...
        }
    };
})

I've already checked out this SO question which states how to require controllers of directives that exist along the same line in a hierarchy.

But my question is regarding a way to do the same in a hierarchy of directives that NOT necessarily exist along the same line. And if this is not possible, what is a proper workaround for it. Any help would be appreciated.

EDIT

Also, can any of the prefixes for require (or a combination of them) be used to achieve the same?

Upvotes: 2

Views: 1194

Answers (2)

ibrahimb
ibrahimb

Reputation: 170

I aggree with miensol's reply and I recommend that approach but in some cases you may need something like that;

<parent-directive>
<ul>
    <some-nested-directive id="snd1" ng-repeat="dir in directives"></some-nested-directive>
</ul>
<settings-menu some-nested-directive-id="snd1"></settings-menu>

You can access the scope of some-nested-directive using its id from the settings-menu;

$("#" + scope.someNestedDirectiveId).scope()

Once I used this approach to cascade the values of a dropdown according to the choise of another independent dropdown.

Upvotes: 0

miensol
miensol

Reputation: 41608

One approach is to use parent directive as a way to pass references between controllers:

var mod = angular.module('test', []);

mod.directive('parent', function() {
  return {
    restrict: 'E',
    transclude: true,
    template: '<div>Parent <div ng-transclude=""></div></div>',
    controller: function ParentCtrl() {}
  }
});

mod.directive('dirA', function() {
  return {
    restrict: 'E',
    template: '<div>Dir A <input type="text" ng-model="name"></div>',
    require: ['dirA', '^^parent'],
    link: function(scope, element, attrs, ctrls) {
      //here we store this directive controller into parent directive controller instance
      ctrls[1].dirA = ctrls[0];
    },
    controller: function DirACtrl($scope) {
      $scope.name = 'Dir A Name';
      this.name = function() {
        return $scope.name;
      };
    }
  }
});

mod.directive('dirB', function() {
  return {
    restrict: 'E',
    template: '<div>Dir A <button ng-click="click()">Click</button></div>',
    require: ['dirB', '^^parent'],
    link: function(scope, element, attrs, ctrls) {
      //let's assign parent controller instance to this directive controller instance
      ctrls[0].parent = ctrls[1];
    },
    controller: function DirBCtrl($scope) {
      var ctrl = this;
      $scope.click = function() {
        //access dirA controller through parent
        alert(ctrl.parent.dirA.name());
      };
    }
  }
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>


<div ng-app='test'>
  <parent>
    <dir-a></dir-a>
    <dir-b></dir-b>
  </parent>
</div>

When using above approach it also makes sense to encapsulate how the dirA controller is stored inside parent controller i.e. by using a getter property or by exposing only the required properties of dirA controller.

Upvotes: 2

Related Questions