Inheritance is a powerful way to extend directives functionality and at the same time, improve code reuse. Lets see how it works in Angular with a simple example. The first step is to create a directive called “outer” which is going to be our top-level directive in our hierarchy.
angular.module('myApp')
.directive('outer', function(){
return {
restrict: 'E',
controllerAs: 'outer',
require: 'outer',
template: '<p>outer</p>',
scope: true,
controller: function(){
this.sayHello = function(){
console.log('outer: hello');
};
},
link: function(scope, element, attrs, outerCtrl){
element.on('click', function(event){
outerCtrl.sayHello();
});
}
};
});
Our first directive defines a controller and a link function, this way we can separate concerns: the controller deals with the logic of the directive while the link function is in charge of DOM manipulation. Another benefit of this approach is that the controller of our directive could be shared to child directives.
Because we need to access the logic of the controller inside our link function, we need to set the directive property require: ‘outer’ defining that we are going to need to “inject” the controller of the directive called “outer”, that in this case happens to be the controller of the directive that we are creating. The injected controller is available in the link function as the fourth argument outerCtrl
At this point when we click our directive, the message “outer: hello” is shown in the browser console.
The HTML and CSS of this example are the following:
<body ng-app="myApp">
<outer></outer>
<!-- script tags -->
</body>
outer {
display: block;
background-color: yellow;
border: 5px solid yellow;
}
Now lets create another directive that we are going to call “inner”.
angular.module('myApp')
.directive('inner', function(){
return {
restrict: 'E',
template: '<p>inner</p>',
require: ['^outer', 'inner'],
scope: true,
controller: function(){
this.sayWelcome = function(){
console.log('inner: welcome');
};
},
link: function(scope, element, attrs, ctrls){
var outerCtrl = ctrls[0];
var innerCtrl = ctrls[1];
element.on('click', function(event){
innerCtrl.sayWelcome();
outerCtrl.sayHello();
event.stopPropagation();
});
}
};
});
Notice the line require: [‘^outer’, ‘inner’] , here the directive is requiring two different controllers: “outer” and “inner”. Because the “outer” string is prefixed with the symbol “^” our directive will look for that controller in his parents. Now, in the link function we are receiving an array as the fourth argument with both controllers in the same order that were defined in the require array.
When we click the inner directive, it’s going to call first the function “sayWelcome” that is defined in his controller and then will call the “sayHello” function defined in the parent controller. We are stopping the event propagation so the parent “click” method is not called, thus simplifying our example.
In order for this example to work, we need to change our outer directive’s template so it will be referencing the inner directive: template: ‘<p>outer</p><inner></inner>’ .
We also need to add a new CSS rule for our inner directive.
inner {
display: block;
color: white;
background-color: red;
border: 5px solid red;
}
It’s worth noting that the search of a parent controller using the prefix “^” is not limited to the immediate parent, it will search the DOM hierarchy searching for a controller o directive (with a controller defined) called “outer”.
This is how it looks having a 3-level hierarchy of directives:
angular.module('myApp')
.directive('outer', function(){
return {
restrict: 'E',
require: 'outer',
template: '<p>outer</p><middle></middle>',
scope: true,
controller: function(){
this.sayHello = function(){
console.log('outer: hello');
};
},
link: function(scope, element, attrs, outerCtrl){
element.on('click', function(event){
outerCtrl.sayHello();
});
}
};
});
angular.module('myApp')
.directive('middle', function(){
return {
restrict: 'E',
template: '<p>middle</p><inner></inner>',
require: ['^outer', 'middle'],
scope: true,
controller: function(){
this.sayBye = function(){
console.log('middle: bye');
};
},
link: function(scope, element, attrs, ctrls){
var outerCtrl = ctrls[0];
var middleCtrl = ctrls[1];
element.on('click', function(event){
middleCtrl.sayBye();
outerCtrl.sayHello();
event.stopPropagation();
});
}
};
});
angular.module('myApp')
.directive('inner', function(){
return {
restrict: 'E',
template: '<p>inner</p>',
require: ['^outer', '^middle', 'inner'],
scope: true,
controller: function(){
this.sayWelcome = function(){
console.log('inner: welcome');
}
},
link: function(scope, element, attrs, ctrls){
var outerCtrl = ctrls[0];
var middleCtrl = ctrls[1];
var innerCtrl = ctrls[2];
element.on('click', function(event){
innerCtrl.sayWelcome();
middleCtrl.sayBye();
outerCtrl.sayHello();
event.stopPropagation();
});
}
}
});
And the CSS files:
outer {
display: block;
background-color: yellow;
border: 5px solid yellow;
}
middle {
display: block;
color: white;
background-color: blue;
border: 5px solid blue;
}
inner {
display: block;
color: white;
background-color: red;
border: 5px solid red;
}
Now, when clicking the inner directive…
This is one way to implement a “classic” inheritance model in Angular, in the next post I will show how to emulate an abstract/concrete class inheritance model using directives.
I don’t understand why you would require the “inner” controller from inside the “inner” directive. It’s not necessary
Hi Tom. In this case is just an illustrative example but if you are in a situation where you need to use multiple controllers inside your link function, thus using the “require” keyword in the directive definition, you need to put the “inner” controller in the require list in order to have access to it in the link function. Lately I’m using a completely different approach using typescript in a more Object Oriented way. I will create a new blog post soon showing that new approach.
This is NOT classical inheritance at all. The directive itself, is not subclassed, it’s behavior is injected into a seperate class. In this case, outer, is injected into inner, but that is not traditional inheritance in any way shape or form. It forces a certain architecture of your code + DOM that may have a purpose (indeed it does), but it is far from a good inheritance model.
When I was looking for directive inheritance I was more seeking a method to reuse the behavior of an existing directive entirely, for example when implementing a directive that does ng-if with a custom (auto-generated) condition. I don’t think your blog actually addresses this kind of inheritance, but rather touches more on the inter-directive communication part of Angular’s directive system.
An actual way to inherit the behavior of another directive in the making of your custom derived directive is to access the original directive definition through the Angular dependency injection system, and copying/use the original directive implementation in your new directive. See http://stackoverflow.com/a/24065378/636197 for an example of inheriting ngIf.
Thank you for the link. Lately I’ve been using typescript and putting all the logic of the directives in models (typescript classes), then I do a normal inheritance between both models.
It’s not quite match the inheritance in my understand.
First the each layer of controller is not inheriting parent’s controller, you just make the parent controller visible in current layer.
Second, In your outer controller, you have specifically set the outer directive’s template as ”outer”, which mean ‘outer’ (the parent) needs to know existence of ‘middle’ (the child) directive.
Apparently the outer directive is not really open for a brand new directive to inherit.
I am sorry to tell you that this example is not inheritance, it is just nesting and unnecesary coupling… Cause inheritance would allow to rewrite parent’s class methods, you are just calling them from the inner directive.. which if it is not inside the outer directive it will not work, thus the coupling.
Thanks for sharing this article. It solves my problems. I don’t really care if it is inheritance.
One goal of inheritance is to reduce amount of code.
You have 3 almost same files (inner.js, middle.js, outer.js).
Conclusion: Don’t use “inheritance” with angularjs.
I don’t care if it is inheritance or not. The fact is there is so much crap which is hidden in angular and not readily available, until , someone like you, David, tries to explain it.
Your solution works for me and so u rock in atleast trying to put an effort into explaining these. I didnt see any of the gurus who posted in the comments section share a fiddle/plunker, if they were so caustic in there comments.
Thanks !
hello, usefull
hi, where your html for inner?
http://plnkr.co/edit/DEifV1?p=preview
“Error: [$compile:ctreq] Controller ‘outer’, required by directive ‘inner’, can’t be found!
http://errors.angularjs.org/1.4.9/$compile/ctreq?p0=outer&p1=inner
minErr/<@https://code.angularjs.org/1.4.9/angular.js:68:12
getControllers@https://code.angularjs.org/1.4.9/angular.js:8396:19
getControllers@https://code.angularjs.org/1.4.9/angular.js:8403:24
……