Last Updated: November 05, 2017
·
7.774K
· joseym

Cleanly Extend 3rd Party AngularJS 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!

1 Response
Add your response

nice simply explanation.. wondered if you had encountered 3rd party code where you needed to override a function nested in the link function..

(function () {
'use strict';
function UmbTabsNavDirective($timeout) {
function link(scope, el, attr) {
function activate() {
$timeout(function () {
//use bootstrap tabs API to show the first one
el.find('a:first').tab('show');
//enable the tab drop
el.tabdrop();
});
}
var unbindModelWatch = scope.$watch('model', function (newValue, oldValue) {
activate();
});
scope.$on('$destroy', function () {
//ensure to destroy tabdrop (unbinds window resize listeners)
el.tabdrop('destroy');
unbindModelWatch();
});

}
var directive = {
restrict: 'E',
replace: true,
templateUrl: 'views/components/tabs/umb-tabs-nav.html',
scope: {
model: '=',
tabdrop: '=',
idSuffix: '@'
},
link: link
};
return directive;
}
angular.module('umbraco.directives').directive('umbTabsNav', UmbTabsNavDirective);
}());

I want to override fnct active to change to el.find('a:not(:hidden):first').tab('show');

but can only seem to do it by having to completely override the link function.. eg don't return link.apply(this, arguments);
?????

over 1 year ago ·