Search code examples
angularjsangularjs-directiveangularjs-scope

AngularJS: Multiple ways to pass function from controller to directive


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?


Solution

  • 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.