📜 ⬆️ ⬇️

Expansion and layout of directives

Much has been written about directives, but there is little about how to write them correctly. I will share the experience.

Well written directive should


Let us analyze each item using the example of a password field (I think everyone knows the field with a small eye)
')
image

<input ng-model="user.password" ng-minlength="6" form-password form-error="  6 "> 


How many directives are used?
In fact, there are 7 of them.

  1. ng-model
  2. ng-minlength
  3. form-password - replaces the input with its own template and changes the field type (password / text) when clicking on the eye
  4. form-error - shows error error if the password is not long enough
  5. form-error-tooltip - shows a tooltip with the specified text above the element
  6. form-error-tooltip-template - ancillary directive with a tooltip template
  7. tooltip is a directive from the Angular Bootstrap library that we will expand


Override


Consider the tooltip from angular-ui.imtqy.com/bootstrap/#/tooltip and its source code

The first thing that catches your eye is the object of the directive definition, which is placed in a separate service $tooltip . Because of what the directive is defined as:

 .directive( 'tooltip', [ '$tooltip', function ( $tooltip ) { return $tooltip( 'tooltip', 'tooltip', 'mouseenter' ); }]) 


The second is the lack of a tooltip template. Instead, we see an element on which a directive is created and configured with the name directiveName +'-popup ' (directiveName = tooltip in this case):

 '<div '+ directiveName +'-popup '+ 'title="'+startSym+'tt_title'+endSym+'" '+ 'content="'+startSym+'tt_content'+endSym+'" '+ 'placement="'+startSym+'tt_placement'+endSym+'" '+ 'animation="tt_animation" '+ 'is-open="tt_isOpen"'+ '>'+ '</div>'; 


Find this directive below (let's call it a template):

 .directive( 'tooltipPopup', function () { return { restrict: 'EA', replace: true, scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, templateUrl: 'template/tooltip/tooltip-popup.html' }; }) 


At template / tooltip / tooltip-popup.html we finally find a tooltip template.

As a reader, I probably guessed, such cotovasia is needed so that we can make as many as necessary tooltip directives with different templates using one functional code.

Make a directive to display error hints with your template. A hint will appear if an open event on the element is clipped and disappear, if - close

 app.directive('formErrorTooltip', function($tooltip) { return $tooltip('formErrorTooltip', 'formErrorTooltip', 'open'); }); 


One line. Cool?

As a template, a directive named form-error-tooltip-popup will be taken (remember directiveName + '- popup'). Create it in advance by copying the tooltip-popup, replacing the name and path to the template.

Events open and close not supported, but it does not matter. Developers made it possible to add their own events.
 app.config(function($tooltipProvider) { $tooltipProvider.setTriggers({'open': 'close'}); }); 


Expansion


So we have a form-error-tooltip showing the form-error-tooltip over the element. Let's make it so that it shows errors if few characters are entered (in this project it is not difficult to teach how to handle any errors, including server ones). We write a form-error directive that expands the first:

 app.directive('formError', function($compile, $interpolate, $injector) { return { terminal: true, priority: 100, scope: true, compile: function compile(tElement, tAttrs) { var startSym = $interpolate.startSymbol(), endSym = $interpolate.endSymbol(), self = this; tElement.attr('form-error-tooltip', startSym + 'message' + endSym); angular.forEach(tAttrs.$attr, function(value, attr) { var ddo = $injector.has(attr + 'Directive') ? $injector.get(attr + 'Directive')[0] : {}; if (ddo.terminal && ddo.priority >= self.priority) { tElement.removeAttr(value); } }); return function(scope, element, attrs, controller) { $compile(element)(scope); var ngModelCtrl = element.controller('ngModel') element.on('input blur change', checkValidity); function checkValidity() { if (ngModelCtrl.$error.minlength) { setError(attrs.formError); } else if (element.scope() && element.scope().tt_isOpen) { //avoid tooltip bug element.triggerHandler('close'); } } function setError(message) { scope.message = message; scope.$digest(); //set message to tooltip element.triggerHandler('open'); } }; } }; }); 


As can be seen from the code, it adds the form-error-tooltip directive to the element and triggers open and close events on it, depending on whether there is an error or not. But how does she do it? It's all about priorities.

Because of the installed terminal: true, priority: 100 no other non-terminal directives or directives with lower priority will be executed. In other words, before this directive, only ng-if, ng-repeat, ng-include , etc., are executed, i.e. directives that determine whether to generally show the element.

Next, add the form-error-tooltip={{message}} attribute, delete the attributes of directives with higher priority (which have already been executed), including the directive itself, so that it does not perform itself indefinitely, and recompile the element. Now all remaining directives will be executed, including the tooltip display directive, and there will be no conflicts.

Layout


In a similar way, we proceed with the password directive, which should replace the element with its template:

 app.directive('formPassword', function($compile, $injector) { return { restrict: 'AE', terminal: true, priority: 200, templateUrl: 'passwordTemplate.html', replace: true, scope: true, compile: function(tElement, tAttrs) { var input = tElement.find('input')[0], self = this; angular.forEach(tAttrs.$attr, function(value, attr) { var ddo = $injector.has(attr + 'Directive') ? $injector.get(attr + 'Directive')[0] : {}; if (attr !== 'type' && attr !== 'class' && attr !== 'formPassword' && (!ddo.terminal || ddo.priority < self.priority)) { input.setAttribute(value, tAttrs[attr]); } if (attr !== 'class') { tElement.removeAttr(value); } }); return function(scope) { scope.show = false; } } }; }); 

Of course, you could use tranclude , but then you would have to wrap the element in a container or to tranclude such a garden, which is better.

The priority of this directive is greater than that of the previous one, which means it will create a field with an eye and only after that the error handling directive will be applied to it. On the other hand, the priority is less than that of ng-if or ng-repeat , therefore, this whole construct will be hidden, shown or reproduced as many times as desired and the directives will be friends with each other.

A live example: plnkr.co/edit/BSVN7zZb0vNEAXo7iWhM?p=preview

Source: https://habr.com/ru/post/218385/


All Articles