celsomtrindade
celsomtrindade

Reputation: 4671

Angularjs controller function vs directive function

Lately I've been building some modules and in some of them I only used controllers (controller is set within an existing directive I already need to use to load template) to have this comunnication between services and the view, for example:

$scope.callFunction = function(data) {
    factRequest = saveData(data);
};

I also noticed I could do this from within a directive, like this:

link:function(scope) {
    scope.callFunction = function(data) {
        factRequest.saveData(data);
    }
}

//or..

link:function(scope, element, attr) {
    attrValue = attr.myValue;
    element.bind('click', function(attrValue) {
        factRequest.saveData(attrValue);
    });
}

//or even..

link:function(scope, element, attr) {
    attrValue = attr.myValue;
    element.bind('click', function(attrValue) {
        factRequest.saveData(attrValue);
    });

    var elButton = element.fin('span'); //for example
    elButton.bind('click', function(attrValue) {
        factRequest.saveData(attrValue);
    });
}

Considering a scenario where this a reusable object, for example, a product where it display on multiple pages and have a commom function, such as addFavorite, addCart, addWishList, etc.. And also considering performance.

What is the difference between those call methods? And what is the best option to use as a call Function?

Upvotes: 1

Views: 333

Answers (1)

Shaun Scovil
Shaun Scovil

Reputation: 3987

To restate, you are calling a service method on a click event and want to know where the best place to put that logic is.

Let's look at each of your examples:

Controller

angular.module('myApp').controller('MyController', function($scope, factRequest) {
  $scope.callFunction = function(data) {
    factRequest.saveData(data);
  };
});

First of all, whenever I find myself injecting $scope into a controller I question my approach. This is because adding variables to the current scope creates hidden dependencies if you are relying using those variables in a child controller -- and is unnecessary if you are not.

Instead, you should be using the controllerAs syntax and adding the function to the controller itself. Something like this:

angular.module('myApp').controller('MyController', function(factRequest) {
  var vm = this;
  vm.callFunction = function(data) {
    factRequest.saveData(data);
  };
});

...and you would access it in your template like this:

<div ng-controller="MyController as vm">
  <input ng-model="vm.data">
  <button ng-click="vm.callFunction(vm.data)">
    Click Me!
  </button>
</div>

This is a perfectly good approach utilizing native Angular directives.

Directive w/ Link Function

angular.module('myApp').directive('myDirective', function(factRequest) {
  return {
    link: function(scope) {
      scope.callFunction = function(data) {
        factRequest.saveData(data);
      }
    }
  };
});

Again, I don't like this because you are adding the function to scope. If you have a directive and want to expose some functionality to the template, you should use a controller. For example:

angular.module('myApp').directive('myDirective', function() {
  return {
    controller: 'MyDirectiveController',
    controllerAs: 'myDir',
    template: '<input ng-model="myDir.data">' +
      '<button ng-click="myDir.callFunction(myDir.data)">' +
      'Click Me!' +
      '</button>'
  };
}).controller('MyDirectiveController', function(factRequest) {
  var myDir = this;
  myDir.callFunction = function(data) {
    factRequest.saveData(data);
  }
});

This is essentially the same as the first example, except that it is now a reusable component.

Directive w/ Click Event Handler

angular.module('myApp').directive('myDirective', function(factRequest) {
  return {
    link: function(scope, element, attr) {
      element.on('click', function() {
        factRequest.saveData(scope.$eval(attr.myValue));
      });
    }
  };
});

Notice I took a few liberties here. For one thing, an event handler function gets the event object as its first argument, so trying to pass attr.myValue wouldn't work. Also, I call scope.$eval(), which is a best practice that enables the use of Angular expressions in the myValue attribute.

I like this approach best, because it doesn't rely on the use of other directives like ng-click. In other words, this directive is more self-contained.

One thing I should add is that Angular will not remove this event listener when the element is removed from the DOM. It is a best practice to clean up after your directive, like this:

angular.module('myApp').directive('myDirective', function(factRequest) {
  return {
    link: function(scope, element, attr) {
      function onClick() {
        factRequest.saveData(scope.$eval(attr.myValue));
      }
      element.on('click', onClick);
      scope.$on('$destroy', function() {
        element.off('click', onClick);
      });
    }
  };
});

Conclusion

From a performance perspective, all of these approaches are roughly equivalent. The first two don't add any watchers themselves, but ng-click and ng-model do so its six of one, half a dozen of the other.

Upvotes: 2

Related Questions