Friday, November 22, 2013

Get current directive controller in AngularJS

You can pass information and functionalut in to Directives in a number fo ways. One of those ways is the controller. Most directives will have a controller associated with it which acts as a way to organize the logic you need to call upon and is a good way to add functionality to the scope. Directives can also inherit a controller from higher up in the chain which is great, however it may make it harder to use the controller you define. For an exmaple:


var aControl = function() {};
var bControl = function() {};

var a = function() {
	return {
		link: function(scope, element, attrs, ctrl) {
			// ctrl references aControl
		},
		controller: aControl
	};
};

var b = function() {
	return {
		require: '^a',
		link: function(scope, element, attrs, ctrl) {
			// ctrl references aControl

			// but how do I get a reference to bControl??
		},
		controller: bControl
	};
};


So your control will be passed in unless you define require as can be seen in AngularJS code:


directive.require = directive.require || (directive.controller && directive.name);

So how do you get a handle to the current control? Well looking through the code we can see that when we have a directive on an element it will be attached:

$element.data('$' + directive.name + 'Controller', controllerInstance);

So to get at the current controller we just have to check the data on the current element like so:

var b = function() {
	var myDirectiveName = 'b';

	return {
		require: '^a',
		link: function(scope, element, attrs, ctrl) {
			var myParentCtrl = ctrl;

			var myCurrentControl = element.data('$' + myDirectiveName + 'Controller');
		},
		controller: bControl
	};
};

Flexible directive names in AngularJS

AngularJS is an HTML compiler. Because of this the most powerful component in Angular is the directive.

The first thing I noticed when using directives in Angular was how they were made available to the application. To use a directive they have to be named and added to a module under that name. You can then use that name to call the directive. When using a directive you usually want to pass in some information to be used. This can be done by setting the directive name as an attribute on the element and then passing an expression to it. This means you have code like the following:


<div my-directive="myExpression" />


So all I need to do is parse the expression and I have my information. This leads to directive code like the following:


var myDirective = function($parse) {
	return {
		link: function(scope, element, attrs) {
			var myInfo = $parse(attrs['myDirective'])(scope);
		}
	}
};


I've hard coded in 'myDirective', but what happens if that directive wants to be used under another name? Well the easy fix is to attach that directive to a module and enforce the name of the directive like so:


var myDirectiveModule = angular.module('myDirectiveModule', [])
	.directive('myDirective', myDirective);


now the end user needs to pull in the module as a dependency and the directive wil be saved under 'myDirective'.

But what if we want the user to define what the directive is called?

Well it turns out we can find out how the directive was registered because of this code in angular js:


  /**
   * @ngdoc function
   * @name ng.$compileProvider#directive
   * @methodOf ng.$compileProvider
   * @function
   *
   * @description
   * Register a new directive with the compiler.
   *
   * @param {string|Object} name Name of the directive in camel-case (i.e. ngBind which
   *    will match as ng-bind), or an object map of directives where the keys are the
   *    names and the values are the factories.
   * @param {function|Array} directiveFactory An injectable directive factory function. See
   *    {@link guide/directive} for more info.
   * @returns {ng.$compileProvider} Self for chaining.
   */
   this.directive = function registerDirective(name, directiveFactory) {
    assertNotHasOwnProperty(name, 'directive');
    if (isString(name)) {
      assertArg(directiveFactory, 'directiveFactory');
      if (!hasDirectives.hasOwnProperty(name)) {
        hasDirectives[name] = [];
        $provide.factory(name + Suffix, ['$injector', '$exceptionHandler',
          function($injector, $exceptionHandler) {
            var directives = [];
            forEach(hasDirectives[name], function(directiveFactory, index) {
              try {
                var directive = $injector.invoke(directiveFactory);
                if (isFunction(directive)) {
                  directive = { compile: valueFn(directive) };
                } else if (!directive.compile && directive.link) {
                  directive.compile = valueFn(directive.link);
                }
                directive.priority = directive.priority || 0;
                directive.index = index;
                directive.name = directive.name || name;
                directive.require = directive.require || (directive.controller && directive.name);
                directive.restrict = directive.restrict || 'A';
                directives.push(directive);
              } catch (e) {
                $exceptionHandler(e);
              }
            });
            return directives;
          }]);
      }
      hasDirectives[name].push(directiveFactory);
    } else {
      forEach(name, reverseParams(registerDirective));
    }
    return this;
  };


This is how a directive is registered and where a name is passed in. You can also see this line:


directive.name = directive.name || name;


Which basically says that if no name is provided on the directive object (the object we return from the directive factory) then we set it to the one it is registered with. This means that if we keep a reference to that object then we can use that name variable like so:


var myDirective = function($parse) {
  var directiveObj = {
    link: function(scope, element, attrs) {
      var myInfo = $parse(attrs[directiveObj.name])(scope);
    }
  };

  return directiveObj;
};


Now we have a directive that can be assigned to a module under any name.