8

I was wondering, if it is possible, to extend Angular's input directive? I want to attach some listeners to all input fields on a page. I think you can decorate existing modules with $provide.decorate, but I have no idea how to do this with a directive (and more precisely the input directive).

So, can anyone push me in the right direction? Some examples?

EDIT

Here is my directive that I have so far:

angular.module('onFocusBlur').
directive('onFocusBlur', ["$rootScope", function($rootScope) {
  return {
    restrict: "A",
    link: function(scope, elem, attrs) {
        elem.bind('focus',function() {
          scope.$apply(function() {
            $rootScope[attrs.onFocusBlur] = true;
          });
        });
        elem.bind('blur',function() {
          scope.$apply(function() {
            $rootScope[attrs.onFocusBlur] = false;
          });
        });
    }
  };
}]);

In my view, I can add this to an input field like this:

<input type="email" ng-model="email" on-focus-blur="repositionNavBar">

The downside is, that for every input field, I have to attach this listener manually in my code. Therefore, it would be useful, to alter the existing input directive, to include this listeners.

tanguy_k
  • 8,999
  • 4
  • 46
  • 50
23tux
  • 12,371
  • 11
  • 76
  • 158

3 Answers3

18

Here's a short gist of how you could decorate the built in Angular input directive, with $provide.decorator as you yourself suggested.

app.config(function ($provide) {

  // Suffix is "Directive" for decorating directives.
  // $delegate refers to the original directive definition. 
  $provide.decorator('inputDirective', function ($delegate) {

    // When decorating a directive, $delegate is an array containing 
    // all of the directives of the same name. 
    // We're interested in the first in this case.
    var directive = $delegate[0];

    // Store a reference to the original directive linking function. 
    var link = directive.link;

    // Hook into the compile phase of the directive.
    directive.compile = function () {

      // The function returned by compile is your new link function.
      // Or 'postLink', to be exact. 
      return function (scope, el, attrs) {

        // Run the original link function. 
        link.apply(this, arguments);

        // Your new listener function
        function listener (e) {
          console.log(e);
        }

        // Set up your listener(s).
        el.bind('blur', listener);
        el.bind('focus', listener);
      };
    };

    // Return the decorated original ($delegate).
    return $delegate;
  });

});

Benefits of this approach, as I see it:

  1. It's more DRY than adding another directive to augment behaviour.
  2. It is an actual extension of third-party behaviour.
  3. For the most part, you don't need to know about the internal implementation of the directive (or service, for that matter) that you are decorating.

Here's a jsBin: http://jsbin.com/yafecinu/2/edit

I'd recommend you take a look at this article, that goes in-depth on decorating directives. The article covers extending not just linking phases, but controllers and pre-linking DOM manipulation aswell. A very good read!

Kasper Lewau
  • 7,407
  • 1
  • 30
  • 40
  • This is the way to go but since Angular 1.3 you need to use pre link: `var link = directive.link.pre;` – Scraph Nov 19 '15 at 14:13
  • I ran into an issue using angular-bootstrap 1.1.1, with datepickers and I applied a custom decorator using this code. Instead of saving/calling the old pre-link function, I think a better solution would be to use the old compile function. It fixed my issues anyway. – Joseph Helfert Feb 01 '16 at 18:07
  • @JosephHelfert That wholly depends on how far you want to deviate from the original directive. If it's something simple, you can probably go with link/controller. If you need more fine-grain control, then using the old compile function works just dandy! – Kasper Lewau Feb 03 '16 at 07:17
  • @KasperLewau Can you show an example of how to do this with the ng-submit directive? – theB3RV Nov 30 '16 at 21:10
3

See answer to this question. They talk about a couple different options to extend a directive from using $provide to making a directive with the same name.

Also, this link explains a few techniques (under "Extending Directives"): https://github.com/angular/angular.js/wiki/Understanding-Directives

Community
  • 1
  • 1
Patrick
  • 6,518
  • 3
  • 19
  • 31
0

You can easily extend any directive - including the input directive. Here is a quick example of adding a class to a directive.

SnapShot
  • 5,124
  • 4
  • 39
  • 36
  • 9
    You are not extending anything, you are adding a seperate directive. Effectively creating the code OP is trying to avoid. – André Snede Mar 12 '14 at 13:27