Directives are a key feature of
AngularJS . With the help of directives, you can add new behavior to existing HTML elements, you can create new components. Examples of directives that add new behavior for existing HTML elements are
input ,
select ,
textarea in conjunction with
ngModel , required, etc. The listed directives are mainly related to the
validation of forms in AngularJS. But the topic of validation deserves a separate article.
Directives can and should be used to increase the modularity of your application, to isolate separate functionality into components, including for reuse.
If you are developing an application on AngularJS and do not create directives, then this alone is a little alarming. Either your application is fairly simple and fit into the standard features of AngularJS, or, most likely, something is wrong with the architecture of your application. And if at the same time you have work with DOM in controllers or services, then you definitely need to deal with the topic of creating directives, since manipulations with DOM should not be anywhere except directives.
')
In this article I will try to consider the process of creating your own directives on a few examples.
A good example of the creation of directives can serve as a
repository team AngularUI . This team includes developers who are not employees of Google, but very well-established on
the mailing list and on
stackoverflow . As far as I can tell, they create production-ready components with settings that cover most of the use cases. I also have a
repository in which I post some of my work. But I have a slightly different approach. I prefer to make directives for specific use cases. AngularJS is very laconic. Less code => better readability => easier support and change. Why then create “monstrous” components with a bunch of settings? Therefore, consider these guidelines as a starting point for creating your own for specific needs. You can also go to the
ngmodules.org website for examples, perhaps he can become a catalog of various components for AngularJS.
So, the basic document for developing your directives is the
Directives article from the
Developer Guide . There everything is painted very well and in detail. This document will have to come back more than once.
Tooltip wrapper directive from Twitter Bootstrap
Source Code Directive |
Source code demo |
Demoangular.module("ExperimentsModule", []) .directive("tbTooltip", function(){ return function(scope, element, iAttrs) { iAttrs.$observe('title', function(value) { element.removeData('tooltip'); element.tooltip(); }); } });
It will be used like this:
<span class="label label-warning" tb-tooltip title="You should pay order before {{order.cancelDateTime | date:'dd.MM.yyyy HH:mm'}}" > {{order.cancelDateTime | date:'dd.MM.yyyy HH:mm'}} </span>
This example creates a new
module, ExperimentsModule
. It has no dependencies (empty dependency list) - no modules should be loaded before it. In this module, the
tbTooltip
directive is
tbTooltip
. Creation directives are always named using lowerCamelCase. When using a directive, you must name it in lower case using one of the special characters as a delimiter
:
,
-
, or
_
. Optionally, to obtain a valid code, you can use the prefixes
x-
or
data-
. Examples:
tb:tooltip
,
tb-tooltip
,
tb_tooltip
,
x-tb-tooltip
and
data-tb-tooltip
.
The name of the directive is the factory function, which should return the description of the directive. In general, the description is an object, a complete list of fields in which is given in the documentation. But there is a simplified version when you can return only the postLink function. In this case, the directive can later be used only as an attribute of any HTML element. In this example, a simplified directive is used.
What is the postLink function? When a directive is executed for a specific DOM element, its work consists of 3 phases:
- compile: the phase during which it is possible to transform the template DOM structure of the element to which the directive applies. A template structure means either the internal structure described in the HTML code of the page itself, or the template specified by the template or templateUrl fields of the configuration object. The next example is a directive based on the compile function;
- preLink: phase executed before linking all child elements. It is not recommended to do any DOM transformations here;
- postLink: the phase to be executed after binding all the children. The most commonly used phase. Here it is recommended to perform all necessary DOM transformations, hang event handlers, etc.
The sequence of the phases for the hierarchical structure is illustrated
here .
In this example, there are still two key points.
- In use, it is necessary that the title attribute can be not only static text, but also that it supports interpolation (substitution) of data. The
iAttrs.$observe('title', function(value) { ... })
code is responsible for this iAttrs.$observe('title', function(value) { ... })
As soon as the interpolation is completed, i.e. the final text string is obtained, or when any data involved in the interpolation has changed, we apply the changes using the Twitter Bootstrap component of the tooltip. - The second point, probably, is still not very key and is not related to AngularJS, but concerns the logic of the tooltip component. To apply the changes to the previously created tooltip, you need to clean up the old data. Which is done by the
element.removeData('tooltip');
code element.removeData('tooltip');
Directive to highlight the code
Source Code Directive |
Source code demo |
Demo .directive('uiSource', function () { return { restrict: 'EA', compile: function (elem) { var escape = function(content) { return content .replace(/\&/g, '&') .replace(/\</g, '<') .replace(/\>/g, '>') .replace(/"/g, '"'); }; var pre = angular.element('<pre class="prettyprint linenums"></pre>'); pre.append(prettyPrintOne(escape(elem.html().slice(1)), undefined, true)); elem.replaceWith(pre); } }; });
Using:
<ui-source> <ui-source> <div> <label>Name:</label> <input type="text" ng-model="yourName" placeholder="Enter a name here"> <hr> <h1>Hello {{yourName}}!</h1> </div> </ui-source> </ui-source>
Directive to highlight code using google-code-prettify.
It is necessary that the internal content of this directive is not compiled and not linked, but simply processed by google-code-prettify.
This directive has already been implemented through the configuration object. Consider the directive line by line.
restrict: 'EA',
The directive can be used as an element and as an attribute. In general, use cases are encoded as 'EACM'. You can create a directive that can be used as an 'E' element, an attribute 'A', a class 'C', a comment 'M'.
terminal: true,
It means that the priority on which this directive is declared will be the last execution priority. Those. only directives with the priority above and with the same will be executed. All directives will be executed with the same priority, since within one priority, the order of execution of directives is not defined.
compile: function (elem) { ... }
At the compilation stage, we extract the contents of the element, process special characters, replacing them with
mnemonics , process the result with google-code-prettify, frame it all with the pre tag and replace the original element with the resulting one.
Here are some more interesting variants of directives involving the compilation stage:
ng-if ,
transclude into an attribute . Leave more examples in the comments, add to the post.
uiPagination
Source Code Directive |
Source code demo |
DemoThe code is quite long, so I will not insert it here.
Using:
<ui-pagination cur="pagination.cur" total="pagination.total" display="9"></ui-pagination>
Classical directive with a visual component.
The key feature here is the use of an isolated scope.
scope: { cur: '=', total: '=', display: '@' },
The article is already quite large, so I will not dwell on the details and all possible options. They are well documented. In addition, I recommend reading the article
The Nuances of Scope Prototypal Inheritance (there are good visualizations).
In this case, cur and total will be bidirectionally tied through attributes of the same name to the scope in which the directive is used, and the display will receive updates through the attribute of the same name from the same scope.
The only thing I would like to note is that if a directive is created with active use of
NgModelController , then, most likely, it will be better not to use an isolated scope (there are
certain problems with it), but a new child scope, declared through
scope: true
. True, in this case it will be necessary to specify an object property (
ng-model="pagination.cur"
) in ng-model, and not just a variable (
ng-model="curPage"
). But just variables and not recommended for ng-model (
proof , see the comment Miško Hevery).
uiGrid
Source Code Directive |
Source code demo |
DemoHonestly, I kept putting off writing this article until I write such a directive :-) I wrote an article, I look, but she doesn’t really solve anything here. But once written, let it be as proof of concept. You can, of course, pass all settings through a large object in an attribute as in the
ng-grid , but AngularJS can be “cooler”, more declarative. Therefore, this approach, it seems to me, is more in the spirit of AngularJS.
Using:
$scope.data = [ { column1: 'aaa', column2: '333', column3: 'aaa', column4: 'sdf' }, { column1: 'bbb', column2: '222', column3: 'zzz', column4: 'sdf' }, { column1: 'ccc', column2: '111', column3: 'ddd', column4: 'sdf' } ] <ui-grid source="data"> <column name="column1"></column> <column name="column2" sortable="true"></column> <column name="column3" sortable="true"></column> </ui-grid>
The key here is in the interaction of the directives through the controller. This
require: '^uiGrid'
code here
require: '^uiGrid'
provides a search for the required controller on the parent elements and sends it to the
link: function (scope, element, attrs, uiGridCtrl) { ... }
.
Conclusion
The article turned out to be rather big, but I did not consider everything in it. Read the
Developer Guide - they have a good and detailed. Join the
community in Google+ - there are no avalanches of posts there, but interesting moments come up quite often.