Let’s say you have a third party angularjs directive that you want to extend or simply access the api defined by its controller.
We could use require but that means that we have to put the 2 directives on the same element, or that the extended directive should be contained inside the first one (looks weird), because require will look up the chain of html.
Well… this is not always possible as we do not have control on the code defining the first directive. It could restrict its usage to ‘E’, meaning that our extended directive cannot be anymore retricted to ‘E’.
How can we easily do that, and in a more natural way meaning the extended directive should wrap the first directive ?
We are defining a counter directive with some minimal css.
Our counter directive should display a number, starting at 0, and increment it each time we click on it.
Most of the chances are that this third party directive will use an isolate scope.
Again this is pretty simple, and here is the code.
The directive presents a circle with a number in it. Each time you click it will increment the number.
Now we want to use this existing directive but extend its behavior.
For the matter of the explanation, let’s create a new directive called wrappercounter that wraps it into a wider circle, and when will click on it, it should log a console message.
Depending on how the first directive counter was written we have 3 ways to achieve this.
1st way : element.isolateScope
If the first directive controller uses $scope (like above) we have to retreive the inner element, and the api declared by the scope will be available through element.isolateScope().
scripts/app.js
12345678910111213141516171819202122232425
myApp.directive('wrapcounter',function(){return{restrict:'E',transclude:true,template:'<div class="circle wrapcounter" ng-transclude></div>',link:function(scope,iElm,iAttrs,controller){// retreive the inner directive elementvarcounter=iElm.find('counter')[0];varinnerScope=angular.element(counter).isolateScope();iElm.on('click',function(e){e.stopPropagation();scope.$apply(function(){// decorating the increment function with a console log.console.log('click wrapper');// accessing the inner directive apiinnerScope.increment();});});}};});
2nd way : element.controller(name)
This only works if the controller of the first directive uses the this (or controllerAs) syntax.
Let’s modify the first directive
myApp.directive('wrapcounter',function(){return{restrict:'E',transclude:true,template:'<div class="circle wrapcounter" ng-transclude></div>',link:function(scope,iElm,iAttrs,controller){// retreive the inner directive elementvarcounter=iElm.find('counter')[0];// retreive the inner controllervarinnerController=angular.element(counter).controller('counter');iElm.on('click',function(e){e.stopPropagation();scope.$apply(function(){// decorating the increment function with a console log.console.log('click wrapper');// accessing the inner directive apiinnerController.increment();});});}};});
3rd way : element.data
Looking at the angluarjs source code we see this piece of code:
So we can use this to get access to the controller:
scripts/app.js
1234567891011121314151617181920212223242526
myApp.directive('wrapcounter',function(){return{restrict:'E',transclude:true,template:'<div class="circle wrapcounter" ng-transclude></div>',link:function(scope,iElm,iAttrs,controller){// retreive the inner directive elementvarcounter=iElm.find('counter')[0];// retreive the inner controllervarinnerController=angular.element(counter).data('$'+'counter'+'Controller');iElm.on('click',function(e){e.stopPropagation();scope.$apply(function(){// decorating the increment function with a console log.console.log('click wrapper');// accessing the inner directive apiinnerController.increment();});});}};});
As you see in any case we have access to the api defined by the inner directive. We could change it, decorate or override existing functions, add our own functions if some are missing from the inner directive, etc…