Last Updated: February 25, 2016
·
436
· joseym

Extending 3rd Party Directives

There have been a number of times that a directive written by some other brilliant mind gets me almost where I need to go, but not quite.

In the past I handled this in one of two ways:

  1. Fork the project and retrofit my requirements atop it.
  2. Write a solution from scratch to solve my problem.

While these options are okay, they're hardly flexible, and the 2nd of them can needlessly pollute the community and waste valuable time.

Enter Decorators

Angular gives us the ability to actually modify existing directives in or own app config, the problem is it doesn't seem to be super well documented...

Example:

Recently I wanted the ability to dynamically set an active tab for AngularUI Bootstrap's tabset directive.

It provides an active attribute but its not writable making it fail to be set by something like, say, ui-router

My Config

angular.module('myApp')
  .config(function($provide) {

    /**
     * Hijacking the AngularUI Tabs directive
     * We need a way to set an active tab based on current router state
     * By default this is only supported when using tabs via an `ngRepeat` loop.
     *
     * @example
       <tab isactive="vm.test(isTrue)">tab contents</tab>
    */
    $provide.decorator('tabDirective', function($delegate) {
      var directive, link;
      directive = $delegate[0];
      link = directive.link;
      directive.compile = function() {
        return function Link(scope, element, attrs, ctrls) {
          scope.active = scope.$parent.$eval(attrs.isactive);
          return link.apply(this, arguments);
        };
      };
      return $delegate;
    });

}));

Basically what's happening here is I'm grabbing the directive before my app loads, pulling up the tabDirective and then extending the link method via compile (the return of compile is always Link).

Link allows us to add to the scope, so inside of my custom Link method i'm evaluating a new attribute I've applied to my tabs called isactive.

scope.$parent.$eval will actually fire the method with the existing scope.

I wrap it all up by returning the original link method with my updates by simply applying my link and all arguments to it: return link.apply(this, arguments);

All done!

Using this same style you can essentially overwrite/extend any aspect of a directive (or factory, provider, service). I've also used this to change directives templates to that of my own design.

directive.templateUrl = 'app/path/to/directive/template.html';

Happy Hacking!