30

I might be thinking about this completely backwards, but I'm trying to make three nested directives, lets call them: screen, component and widget. I want widget to be able to trigger some behavior in component, which in turn triggers some behavior in screen. So:

.directive('screen', function() {
    return {
        scope: true,
        controller: function() {
            this.doSomethingScreeny = function() {
                alert("screeny!");
            }
        }
    }
})

.directive('component', function() {
    return {
        scope: true,
        controller: function() {
            this.componentFunction = function() {
                WHAT.doSomethingScreeny();
            }
        }
    }
})

.directive('widget', function() {
    return {
        scope: true,
        require: "^component",
        link: function(scope, element, attrs, componentCtrl) {
            scope.widgetIt = function() {
                componentCtrl.componentFunction();
            };
        }
    }
})

<div screen>
    <div component>
        <div widget>
            <button ng-click="widgetIt()">Woo Hoo</button>
        </div>
    </div>
</div>

I can require parent components in a widget's link fn using require: "^component", but how do I further give components controller access to its containing screen?

What I need is the WHAT in component so when you click the widget's button it alerts "screeny!".

Thanks.

4 Answers 4

36

Here are two ways you could solve your problem:

  1. Since you are using scope: true, all scopes prototypically inherit. So if you define your methods on $scope instead of on this in the screen controller, then both component and widget will have access to function doSomethingScreeny.
    Fiddle.
  2. Define a link function on component and require: '^screen'. In the link function, save the screenCtrl to a scope property, then you can access it in the directive's controller (inject $scope).
    Fiddle.
Sign up to request clarification or add additional context in comments.

4 Comments

Thanks. I've ended up going with your opt 1. But either way it seems you have to drop everything onto the scope, so I'm not sure I'm fully understanding the point of the controller. Or rather what it would be used for. Granted, I've only spent a couple of hours with AngularJS. Any useful tutorials you might point me towards that explain the relationship between services, controllers and directives? Thanks.
@nicholas, whatever you want to show in a view has to be on the associated $scope. So, the controller is sort of the glue between your models (which are often in services) and your view. Controllers also add behavior to the view via functions. See also stackoverflow.com/a/13482008/215945 and stackoverflow.com/questions/14994391/…
@MarkRajcok, I've updated the fiddle that demonstrates your second option to more closely match the fiddle for your first option. I did this by including both the screen and the component directives' controllers into the widget's link function. This change demonstrates how a directive can require more than one controller by using an array of strings.
Controllers allow your directives to interact (require only gets you a reference to the controller) and controllers are ALWAYS easier to test than directives because they don't require you to mock the DOM or template when testing.
5

Most of this stuffs fails when you want to directly access properties or methods from the parent controller on controller creation. I found another solution by using dependency injection and using the $controller service.

.directive('screen', function ($controller) {
    return {
       require: '^parent',
       scope: {},
       link: function (scope, element, attr, controller) {
           $controller('MyCtrl', {
                $scope: scope,
                $element: element,
                $attr, attr, 
                controller: controller
           });
       }
    }
})

.controller('MyCtrl, function ($scope, $element, $attr, controller) {});

This method is better testable and does not pollute your scope with unwanted controllers.

Comments

0

return { scope: true } or return { scope: false } is not affect to $scope variable in controller: function($scope) {} in each directive, but directive tag have to putted into the ng-controller or ng-app tag.

JSFiddle

JSFiddle

Comments

0

var myApp = angular.module('myApp', [])

.directive('screen', function() {
  return {
    scope: true,
    controller: function() {
      this.doSomethingScreeny = function() {
        alert("screeny!");
      }
    }
  }
})

.directive('component', function() {
  return {
    scope: true,
    controller: function($element) {
      this.componentFunction = function() {
        $element.controller('screen').doSomethingScreeny();
      }
    }
  }
})

.directive('widget', function() {
  return {
    scope: true,
    controller: function($scope, $element) {
      $scope.widgetFunction = function() {
        $element.controller('component').componentFunction();
      }
    }
  }
})

.controller('MyCtrl', function($scope) {
  $scope.name = 'Superhero';
})
<body ng-app="myApp">

  <div ng-controller="MyCtrl">
    <div screen>
      <div component>
        <div widget>
          <button ng-click="widgetFunction()">Woo Hoo</button>
        </div>
      </div>
    </div>
  </div>

</body>

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.9/angular.min.js"></script>

If you want to access a function defined in screen directive controller from component directive controller (not a link function), you can use $element.controller('screen').doSomethingScreeny() (from component directive).

JSFiddle

Angular documentation:

  • controller(name) - retrieves the controller of the current element or its parent. By default retrieves controller associated with the ngController directive. If name is provided as camelCase directive name, then the controller for this directive will be retrieved (e.g. 'ngModel').

Comments

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.