32

I want to write a directive with isolated scope but also want to make that scope available for the parent scope's controller. I found this solution:

<div ng-controller="Main">
  <popupbutton directive-scope="popup"></popupbutton>
</div>

app.directive('popupbutton', [function() {
  return {
    restrict:   "E",
    scope:      {    
      directiveScope: "="
    },
    link:       function(sc, el, attrs) {
      sc.directiveScope = sc;
      sc.testvalue = 'foo';
    }
  };  
}]);

app.controller('Main', function($scope) {
  alert($scope.popup.testvalue);  // Where did the property 'popup' come from?!?
});

See Plunker.

I find this a bit ugly because it involves writing an attribute in HTML and in controller's code you can't tell where a scope property came from. Is there a better way to do this?

Edit:

Besides, it seems that $scope.popup isn't even available when controller 'Main' is run. The directive's linking function isn't executed yet?

2
  • 1
    You shouldn't be accessing the directive's scope at all from your controller. There is probably a better way to do this - what's your use case? Commented Jan 27, 2013 at 0:11
  • The directive creates a button and a popup box that can be opened by clicking the button. I'd like the parent controller to see if the box is open or not. The directive's scope also contains the methods to open and close the box and the parent controller should have access to these also. Commented Jan 27, 2013 at 5:48

1 Answer 1

47

To maintain proper separation of concerns, you should not mix scopes. Not to mention that it will be hard to synchronize. To summarize: your directive should not know anything about the parent scope (or its controller) and your controller should not know anything about a directive's internals. They are separate components in separate layers.

The proper way to communicate between a controller and a directive is through directive attributes. In the case of a popup, say, this can be done with a simple boolean value.

The controller and directive:

app.directive('popupbutton', [function() {
  return {
    restrict: "E",
    scope: { isOpen: "=" },
    template: '<a ng-click="isOpen = !isOpen">Toggle</a><div>Open? {{isOpen}}'
  };
}]);

app.controller('MainCtrl', function($scope) {
  $scope.isOpen = false;
});

And the markup:

<popupbutton is-open="isOpen"></popupbutton>

This method requires no logic, works out of the box, and maintains clean separation of concerns. Here's an updated plunker: http://plnkr.co/edit/otIaGCLmiNdGcYEgi60f?p=preview

Sign up to request clarification or add additional context in comments.

8 Comments

Thanks! With your help I wrote this directive: plnkr.co/edit/vqkPBDkYT2zVVEGXBZVQ?p=preview. But if I omit the 'is-open' attribute in HTML, I get an error: "Error: Non-assignable model expression: undefined (directive: popupbutton)". How can I make the attribute 'is-open' optional?
Yes, for simplicity I kept it all the same, whereas in reality you would not do it this way. In the real world, the directive would have its own internal variable that represents the "true state" and a separate attribute with a $watch that will update the true state when it changes. I just collapsed them into one to make the functionality here clear.
I forked your plunker (see plnkr.co/edit/vh6yOigpHd7E5ijJ3QEa?p=preview) to explore a strange problem I encountered. If I plase a tag with ng-click attribute inside my directive tag (with 'transclude' set to true) it won't work as excepted. The lower left button does nothing while the lower right works. I can't understand the reason or the difference between these two buttons.
This will seem counter-intuitive, but the reason is scope inheritance. With transclusion, the contents are evaluated in a child scope; i.e. the ng-clicks are in a scope that prototypically inherits from your controller's scope. So objects (including functions) are references to the values in the parent scope, but primitives (strings, nums, bools) are copies. That is, it's not the same isOpen anymore. This article talks about this more and is a must-read.
@Esa, I just now updated a SO answer related to transcluded scope with some pictures. (The article Josh mentions references this answer, but it is kind of buried, so I thought it might be useful to point you right to it.)
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.