Extending AngularJs Directive

JavascriptAngularjsAngularjs DirectiveAngular Ui-BootstrapBower

Javascript Problem Overview


I'd like to make a minor modification to a 3rd party directive (specifically Angular UI Bootstrap). I simply want to add to the scope of the pane directive:

angular.module('ui.bootstrap.tabs', [])
.controller('TabsController', ['$scope', '$element', function($scope, $element) {
  // various methods
}])
.directive('tabs', function() {
  return {
    // etc...
  };
})
.directive('pane', ['$parse', function($parse) {
  return {
    require: '^tabs',
    restrict: 'EA',
    transclude: true,
    scope:{
      heading:'@',
      disabled:'@' // <- ADDED SCOPE PROPERTY HERE
    },
    link: function(scope, element, attrs, tabsCtrl) {
      // link function
    },
    templateUrl: 'template/tabs/pane.html',
    replace: true
  };
}]);

But I also want to keep Angular-Bootstrap up to date with Bower. As soon as I run bower update, I'll overwrite my changes.

So how do I go about extending this directive separately from this bower component?

Javascript Solutions


Solution 1 - Javascript

Probably the simplest way to solve this is to create a directive on your app with the same name as the third party directive. Both directives will run and you can specify their run order using the priority property (higher priority runs first).

The two directives will share scope and you can access and modify the scope of the third party directive via your directive's link method.

Option 2: You can also access a third party directive's scope by simply putting your own arbitrarily named directive on the same element with it (assuming neither directive uses isolate scope). All non-isolate scope directives on an element will share scope.

Further Reading: https://github.com/angular/angular.js/wiki/Dev-Guide%3A-Understanding-Directives

Note: My previous answer was for modifying a third party service, not a directive.

Solution 2 - Javascript

TL;DR - gimme tha demo!


     Big Demo Button     
 


Use $provide's decorator() to, well, decorate the third party's directive.

In our case, we can extend the directive's scope like so:

app.config(function($provide) {
    $provide.decorator('paneDirective', function($delegate) {
        var directive = $delegate[0];
        angular.extend(directive.scope, {
            disabled:'@'
        });
        return $delegate;
    });
});

First, we request to decorate the pane directive by passing its name, concatenated with Directive as the first argument, then we retrieve it from the callback parameter (which is an array of directives matching that name).

Once we got it, we can obtain its scope object and extend it as needed. Notice that all of this has to be done in the config block.

Some notes
  • It has been suggested to simply add a directive with the same name, then set its priority level. Aside from being unsemantic (which's not even a word, I know…), it poses issues, e.g. what if the third-party directive's priority level changes?

  • JeetendraChauhan has claimed (I haven't tested it though) that this solution will not work in version 1.13.

Solution 3 - Javascript

While this is not the direct answer to your question you might want to know that the latest version (in master) of http://angular-ui.github.io/bootstrap/ added support for disabling tabs. This feature was added through: https://github.com/angular-ui/bootstrap/commit/2b78dd16abd7e09846fa484331b5c35ece6619a2

Solution 4 - Javascript

Another solution where you create a new directive that extends it without modifying the original directive

The solution is similar to the decorator solution:

Create a new directive and inject as dependency the directive you wish to extend

app.directive('extendedPane', function (paneDirective) {

  // to inject a directive as a service append "Directive" to the directive name
  // you will receive an array of directive configurations that match this 
  // directive (usually only one) ordered by priority

  var configExtension = {
     scope: {
       disabled: '@'
     }
  }

  return angular.merge({}, paneDirective[0], configExtension)
});

This way you can use the original directive and the extended version in the same app

Solution 5 - Javascript

Here is another solution for a different scenario of extending bindings to a directive that has the bindToController property.

Note: this is not an alternative to other solutions that were offered here. It solves only a specific case (not covered in other answers) where the original directive was set-up with bindToController.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKyle CureauView Question on Stackoverflow
Solution 1 - JavascriptDanView Answer on Stackoverflow
Solution 2 - JavascriptEliran MalkaView Answer on Stackoverflow
Solution 3 - Javascriptpkozlowski.opensourceView Answer on Stackoverflow
Solution 4 - JavascriptkidrocaView Answer on Stackoverflow
Solution 5 - Javascriptgilad905View Answer on Stackoverflow