0

I am trying to write component-style AngularJS, similar to the practice put forward by this article.

However, I have come to realize there are various ways to pass functions to directives from an associated controller. The directive I'm working on is quite complex and I was passing each function in by binding to the directive in the template, but I now see I could just implicitly inherit the $scope object or reference the Controller object directly.

Here is an example of what I mean:

app.js

var app = angular.module('plunker', [])

app

  .controller('myCtrl', function($scope) {
    $scope.output = '';
    // fn foo is passed into directive as an argument
    $scope.foo = function () {
      $scope.output = 'foo';
    }
    // fn inherited from controller
    $scope.bar = function () {
      $scope.output = 'bar';
    }
    // fn attached to ctrl object and referenced directly
    this.baz = function () {
      $scope.output = 'baz';
    }

  })

  .directive('myDirective', function() {
    return {
      scope: {
        output: '=',
        foo: '&',
      },
      templateUrl: 'template.html',
      replace: true,
      controller: 'myCtrl',
      controllerAs: 'ctrl'
    };
  }) 

index.html

<body ng-controller="myCtrl">
  <my-directive
    output="output"
    foo="foo()">
  </my-directive>
</body>

template.html

<div>
  <button ng-click="foo()">Click Foo</button>
  <button ng-click="bar()">Click Bar</button>
  <button ng-click="ctrl.baz()">Click Baz</button>
  <p>You clicked: <span style="color:red">{{output}}</span></p>
</div>

Plunkr: http://plnkr.co/edit/1JzakaxL3D2L6wpPXz3v?p=preview

So there are three functions here and they all work, yet are passed to the directive in different ways. My question is what are the merits of each and which is the best from a code and testability perspective?

4
  • the whole point of that article you linked was to eliminate the use of $scope. in the Componentized code in the example, there is no reference to ng-controller or $scope anywhere in the entire app. between the use of isolate scopes and the controller-as syntax, objects are explicitly assigned to the components that need them. Commented Feb 4, 2015 at 21:56
  • Use bindToController:true in the directive setting to get everything on the controller instance., provided you have 1.3.x Commented Feb 4, 2015 at 22:00
  • I understand the article goes far beyond what I am detailing here. To be clear I don't have to ng-controller in the html at all if I set the controller in the directive - see updated plunkr. My point was more that there are various methods whick work, I and I was looking for advice on which was the best. Do you think explicitly 'passing' the functions via the & is best practice? What about the controller as approach? Commented Feb 4, 2015 at 22:37
  • I also understand that 2.0 will do away with $scope all together so maybe using this from the controller is a move in that direction? Commented Feb 4, 2015 at 23:23

1 Answer 1

1

You're not really passing anything to the directive, as it's using the same controller as the file containing it...

For instance, if you delete the following:

scope: {
  output: '=',
  foo: '&',
}

from your directive, everything still works the same. I can't think of a reason to use the same controller for a directive and and the containing application like this. I would never recommend this approach.

If you also remove

controller: 'myCtrl',
controllerAs: 'ctrl'

only foo and bar work. This is because the directive inherits the scope it's contained in. This is only recommended if your directive is pretty simple and tightly coupled to the view using it. Usually this approach is OK when you're just doing some visual modifications that repeat themselves in the page. Just notice that when you change something in the controller, the directive will probably break, and that goes against the encapsulation principle.

Finally, the correct way to pass a function to a directive is indeed using '&' modifier. This lets your directive keep an isolated scope, which means it won't break if some code on the containing controller changes. This makes your directive truly an encapsulated, independent module that you can "drag and drop" anywhere.

Here's a fork of your plunkr.

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

5 Comments

OK, it seems you are advocating passing all functions explicitly to the directive using & modifier. This is how I have been doing it up to now, and allows me to pass in different functions to the same, re-suable directive. Though the declarations can get lengthy when I have a complex directive. Are you of the opinion of the three methods I show which work, this is the best?
Yes, if you want an isolated scope, which is recommended. It's officially considered the best practice by the angular team as well: https://docs.angularjs.org/guide/directive
Really? This plunkr on that page declares a directive but doesn't pass in the $scope.customer explicitly but gets the object inherited from the controller implicitly. This seems to contradict your recommendation to pass all variables in explicitly. plnkr.co/edit/?p=preview
It's a gradual explanation of how directives work. They start with basic stuff first. Get to the part titled "Isolating the Scope of a Directive".
OK. Fair enough. I see where they go with it.

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.