itchWhen.split(attrs.ngSwitchWhenSeparator).sort().filter( // Filter duplicate cases function(element, index, array) { return array[index - 1] !== element; } ); forEach(cases, function(whenCase) { ctrl.cases['!' + whenCase] = (ctrl.cases['!' + whenCase] || []); ctrl.cases['!' + whenCase].push({ transclude: $transclude, element: element }); }); } }); var ngSwitchDefaultDirective = ngDirective({ transclude: 'element', priority: 1200, require: '^ngSwitch', multiElement: true, link: function(scope, element, attr, ctrl, $transclude) { ctrl.cases['?'] = (ctrl.cases['?'] || []); ctrl.cases['?'].push({ transclude: $transclude, element: element }); } }); /** * @ngdoc directive * @name ngTransclude * @restrict EAC * * @description * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion. * * You can specify that you want to insert a named transclusion slot, instead of the default slot, by providing the slot name * as the value of the `ng-transclude` or `ng-transclude-slot` attribute. * * If the transcluded content is not empty (i.e. contains one or more DOM nodes, including whitespace text nodes), any existing * content of this element will be removed before the transcluded content is inserted. * If the transcluded content is empty (or only whitespace), the existing content is left intact. This lets you provide fallback * content in the case that no transcluded content is provided. * * @element ANY * * @param {string} ngTransclude|ngTranscludeSlot the name of the slot to insert at this point. If this is not provided, is empty * or its value is the same as the name of the attribute then the default slot is used. * * @example * ### Basic transclusion * This example demonstrates basic transclusion of content into a component directive. * * * *
*
*
* {{text}} *
*
* * it('should have transcluded', function() { * var titleElement = element(by.model('title')); * titleElement.clear(); * titleElement.sendKeys('TITLE'); * var textElement = element(by.model('text')); * textElement.clear(); * textElement.sendKeys('TEXT'); * expect(element(by.binding('title')).getText()).toEqual('TITLE'); * expect(element(by.binding('text')).getText()).toEqual('TEXT'); * }); * *
* * @example * ### Transclude fallback content * This example shows how to use `NgTransclude` with fallback content, that * is displayed if no transcluded content is provided. * * * * * * * * * Button2 * * * * it('should have different transclude element content', function() { * expect(element(by.id('fallback')).getText()).toBe('Button1'); * expect(element(by.id('modified')).getText()).toBe('Button2'); * }); * * * * @example * ### Multi-slot transclusion * This example demonstrates using multi-slot transclusion in a component directive. * * * *
*
*
* * {{title}} *

{{text}}

*
*
*
* * angular.module('multiSlotTranscludeExample', []) * .directive('pane', function() { * return { * restrict: 'E', * transclude: { * 'title': '?paneTitle', * 'body': 'paneBody', * 'footer': '?paneFooter' * }, * template: '
' + * '
Fallback Title
' + * '
' + * '' + * '
' * }; * }) * .controller('ExampleController', ['$scope', function($scope) { * $scope.title = 'Lorem Ipsum'; * $scope.link = 'https://google.com'; * $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...'; * }]); *
* * it('should have transcluded the title and the body', function() { * var titleElement = element(by.model('title')); * titleElement.clear(); * titleElement.sendKeys('TITLE'); * var textElement = element(by.model('text')); * textElement.clear(); * textElement.sendKeys('TEXT'); * expect(element(by.css('.title')).getText()).toEqual('TITLE'); * expect(element(by.binding('text')).getText()).toEqual('TEXT'); * expect(element(by.css('.footer')).getText()).toEqual('Fallback Footer'); * }); * *
*/ var ngTranscludeMinErr = minErr('ngTransclude'); var ngTranscludeDirective = ['$compile', function($compile) { return { restrict: 'EAC', compile: function ngTranscludeCompile(tElement) { // Remove and cache any original content to act as a fallback var fallbackLinkFn = $compile(tElement.contents()); tElement.empty(); return function ngTranscludePostLink($scope, $element, $attrs, controller, $transclude) { if (!$transclude) { throw ngTranscludeMinErr('orphan', 'Illegal use of ngTransclude directive in the template! ' + 'No parent directive that requires a transclusion found. ' + 'Element: {0}', startingTag($element)); } // If the attribute is of the form: `ng-transclude="ng-transclude"` then treat it like the default if ($attrs.ngTransclude === $attrs.$attr.ngTransclude) { $attrs.ngTransclude = ''; } var slotName = $attrs.ngTransclude || $attrs.ngTranscludeSlot; // If the slot is required and no transclusion content is provided then this call will throw an error $transclude(ngTranscludeCloneAttachFn, null, slotName); // If the slot is optional and no transclusion content is provided then use the fallback content if (slotName && !$transclude.isSlotFilled(slotName)) { useFallbackContent(); } function ngTranscludeCloneAttachFn(clone, transcludedScope) { if (clone.length && notWhitespace(clone)) { $element.append(clone); } else { useFallbackContent(); // There is nothing linked against the transcluded scope since no content was available, // so it should be safe to clean up the generated scope. transcludedScope.$destroy(); } } function useFallbackContent() { // Since this is the fallback content rather than the transcluded content, // we link against the scope of this directive rather than the transcluded scope fallbackLinkFn($scope, function(clone) { $element.append(clone); }); } function notWhitespace(nodes) { for (var i = 0, ii = nodes.length; i < ii; i++) { var node = nodes[i]; if (node.nodeType !== NODE_TYPE_TEXT || node.nodeValue.trim()) { return true; } } } }; } }; }]; /** * @ngdoc directive * @name script * @restrict E * * @description * Load the content of a ` Load inlined template
it('should load template defined inside script tag', function() { element(by.css('#tpl-link')).click(); expect(element(by.css('#tpl-content')).getText()).toMatch(/Content of the template/); }); */ var scriptDirective = ['$templateCache', function($templateCache) { return { restrict: 'E', terminal: true, compile: function(element, attr) { if (attr.type === 'text/ng-template') { var templateUrl = attr.id, text = element[0].text; $templateCache.put(templateUrl, text); } } }; }]; /* exported selectDirective, optionDirective */ var noopNgModelController = { $setViewValue: noop, $render: noop }; function setOptionSelectedStatus(optionEl, value) { optionEl.prop('selected', value); /** * When unselecting an option, setting the property to null / false should be enough * However, screenreaders might react to the selected attribute instead, see * https://github.com/angular/angular.js/issues/14419 * Note: "selected" is a boolean attr and will be removed when the "value" arg in attr() is false * or null */ optionEl.attr('selected', value); } /** * @ngdoc type * @name select.SelectController * * @description * The controller for the {@link ng.select select} directive. The controller exposes * a few utility methods that can be used to augment the behavior of a regular or an * {@link ng.ngOptions ngOptions} select element. * * @example * ### Set a custom error when the unknown option is selected * * This example sets a custom error "unknownValue" on the ngModelController * when the select element's unknown option is selected, i.e. when the model is set to a value * that is not matched by any option. * * * *
*
*
*
* * Error: The current model doesn't match any option
* *
*
*
*
* * angular.module('staticSelect', []) * .controller('ExampleController', ['$scope', function($scope) { * $scope.selected = null; * * $scope.forceUnknownOption = function() { * $scope.selected = 'nonsense'; * }; * }]) * .directive('unknownValueError', function() { * return { * require: ['ngModel', 'select'], * link: function(scope, element, attrs, ctrls) { * var ngModelCtrl = ctrls[0]; * var selectCtrl = ctrls[1]; * * ngModelCtrl.$validators.unknownValue = function(modelValue, viewValue) { * if (selectCtrl.$isUnknownOptionSelected()) { * return false; * } * * return true; * }; * } * * }; * }); * *
* * * @example * ### Set the "required" error when the unknown option is selected. * * By default, the "required" error on the ngModelController is only set on a required select * when the empty option is selected. This example adds a custom directive that also sets the * error when the unknown option is selected. * * * *
*
*
*
* Error: Please select a value
* *
*
*
*
* * angular.module('staticSelect', []) * .controller('ExampleController', ['$scope', function($scope) { * $scope.selected = null; * * $scope.forceUnknownOption = function() { * $scope.selected = 'nonsense'; * }; * }]) * .directive('unknownValueRequired', function() { * return { * priority: 1, // This directive must run after the required directive has added its validator * require: ['ngModel', 'select'], * link: function(scope, element, attrs, ctrls) { * var ngModelCtrl = ctrls[0]; * var selectCtrl = ctrls[1]; * * var originalRequiredValidator = ngModelCtrl.$validators.required; * * ngModelCtrl.$validators.required = function() { * if (attrs.required && selectCtrl.$isUnknownOptionSelected()) { * return false; * } * * return originalRequiredValidator.apply(this, arguments); * }; * } * }; * }); * * * it('should show the error message when the unknown option is selected', function() { var error = element(by.className('error')); expect(error.getText()).toBe('Error: Please select a value'); element(by.cssContainingText('option', 'Option 1')).click(); expect(error.isPresent()).toBe(false); element(by.tagName('button')).click(); expect(error.getText()).toBe('Error: Please select a value'); }); * *
* * */ var SelectController = ['$element', '$scope', /** @this */ function($element, $scope) { var self = this, optionsMap = new NgMap(); self.selectValueMap = {}; // Keys are the hashed values, values the original values // If the ngModel doesn't get provided then provide a dummy noop version to prevent errors self.ngModelCtrl = noopNgModelController; self.multiple = false; // The "unknown" option is one that is prepended to the list if the viewValue // does not match any of the options. When it is rendered the value of the unknown // option is '? XXX ?' where XXX is the hashKey of the value that is not known. // // Support: IE 9 only // We can't just jqLite(' * *
* *
*
*
* singleSelect = {{data.singleSelect}} * *
*
*
* multipleSelect = {{data.multipleSelect}}
* * * * * angular.module('staticSelect', []) * .controller('ExampleController', ['$scope', function($scope) { * $scope.data = { * singleSelect: null, * multipleSelect: [], * option1: 'option-1' * }; * * $scope.forceUnknownOption = function() { * $scope.data.singleSelect = 'nonsense'; * }; * }]); * * * * @example * ### Using `ngRepeat` to generate `select` options * * *
*
* * *
*
* model = {{data.model}}
*
*
* * angular.module('ngrepeatSelect', []) * .controller('ExampleController', ['$scope', function($scope) { * $scope.data = { * model: null, * availableOptions: [ * {id: '1', name: 'Option A'}, * {id: '2', name: 'Option B'}, * {id: '3', name: 'Option C'} * ] * }; * }]); * *
* * @example * ### Using `ngValue` to bind the model to an array of objects * * *
*
* * *
*
*
model = {{data.model | json}}

*
*
* * angular.module('ngvalueSelect', []) * .controller('ExampleController', ['$scope', function($scope) { * $scope.data = { * model: null, * availableOptions: [ {value: 'myString', name: 'string'}, {value: 1, name: 'integer'}, {value: true, name: 'boolean'}, {value: null, name: 'null'}, {value: {prop: 'value'}, name: 'object'}, {value: ['a'], name: 'array'} * ] * }; * }]); * *
* * @example * ### Using `select` with `ngOptions` and setting a default value * See the {@link ngOptions ngOptions documentation} for more `ngOptions` usage examples. * * * *
*
* * *
*
* option = {{data.selectedOption}}
*
*
* * angular.module('defaultValueSelect', []) * .controller('ExampleController', ['$scope', function($scope) { * $scope.data = { * availableOptions: [ * {id: '1', name: 'Option A'}, * {id: '2', name: 'Option B'}, * {id: '3', name: 'Option C'} * ], * selectedOption: {id: '3', name: 'Option C'} //This sets the default value of the select in the ui * }; * }]); * *
* * @example * ### Binding `select` to a non-string value via `ngModel` parsing / formatting * * * * * {{ model }} * * * angular.module('nonStringSelect', []) * .run(function($rootScope) { * $rootScope.model = { id: 2 }; * }) * .directive('convertToNumber', function() { * return { * require: 'ngModel', * link: function(scope, element, attrs, ngModel) { * ngModel.$parsers.push(function(val) { * return parseInt(val, 10); * }); * ngModel.$formatters.push(function(val) { * return '' + val; * }); * } * }; * }); * * * it('should initialize to model', function() { * expect(element(by.model('model.id')).$('option:checked').getText()).toEqual('Two'); * }); * * * */ var selectDirective = function() { return { restrict: 'E', require: ['select', '?ngModel'], controller: SelectController, priority: 1, link: { pre: selectPreLink, post: selectPostLink } }; function selectPreLink(scope, element, attr, ctrls) { var selectCtrl = ctrls[0]; var ngModelCtrl = ctrls[1]; // if ngModel is not defined, we don't need to do anything but set the registerOption // function to noop, so options don't get added internally if (!ngModelCtrl) { selectCtrl.registerOption = noop; return; } selectCtrl.ngModelCtrl = ngModelCtrl; // When the selected item(s) changes we delegate getting the value of the select control // to the `readValue` method, which can be changed if the select can have multiple // selected values or if the options are being generated by `ngOptions` element.on('change', function() { selectCtrl.removeUnknownOption(); scope.$apply(function() { ngModelCtrl.$setViewValue(selectCtrl.readValue()); }); }); // If the select allows multiple values then we need to modify how we read and write // values from and to the control; also what it means for the value to be empty and // we have to add an extra watch since ngModel doesn't work well with arrays - it // doesn't trigger rendering if only an item in the array changes. if (attr.multiple) { selectCtrl.multiple = true; // Read value now needs to check each option to see if it is selected selectCtrl.readValue = function readMultipleValue() { var array = []; forEach(element.find('option'), function(option) { if (option.selected && !option.disabled) { var val = option.value; array.push(val in selectCtrl.selectValueMap ? selectCtrl.selectValueMap[val] : val); } }); return array; }; // Write value now needs to set the selected property of each matching option selectCtrl.writeValue = function writeMultipleValue(value) { forEach(element.find('option'), function(option) { var shouldBeSelected = !!value && (includes(value, option.value) || includes(value, selectCtrl.selectValueMap[option.value])); var currentlySelected = option.selected; // Support: IE 9-11 only, Edge 12-15+ // In IE and Edge adding options to the selection via shift+click/UP/DOWN // will de-select already selected options if "selected" on those options was set // more than once (i.e. when the options were already selected) // So we only modify the selected property if necessary. // Note: this behavior cannot be replicated via unit tests because it only shows in the // actual user interface. if (shouldBeSelected !== currentlySelected) { setOptionSelectedStatus(jqLite(option), shouldBeSelected); } }); }; // we have to do it on each watch since ngModel watches reference, but // we need to work of an array, so we need to see if anything was inserted/removed var lastView, lastViewRef = NaN; scope.$watch(function selectMultipleWatch() { if (lastViewRef === ngModelCtrl.$viewValue && !equals(lastView, ngModelCtrl.$viewValue)) { lastView = shallowCopy(ngModelCtrl.$viewValue); ngModelCtrl.$render(); } lastViewRef = ngModelCtrl.$viewValue; }); // If we are a multiple select then value is now a collection // so the meaning of $isEmpty changes ngModelCtrl.$isEmpty = function(value) { return !value || value.length === 0; }; } } function selectPostLink(scope, element, attrs, ctrls) { // if ngModel is not defined, we don't need to do anything var ngModelCtrl = ctrls[1]; if (!ngModelCtrl) return; var selectCtrl = ctrls[0]; // We delegate rendering to the `writeValue` method, which can be changed // if the select can have multiple selected values or if the options are being // generated by `ngOptions`. // This must be done in the postLink fn to prevent $render to be called before // all nodes have been linked correctly. ngModelCtrl.$render = function() { selectCtrl.writeValue(ngModelCtrl.$viewValue); }; } }; // The option directive is purely designed to communicate the existence (or lack of) // of dynamically created (and destroyed) option elements to their containing select // directive via its controller. var optionDirective = ['$interpolate', function($interpolate) { return { restrict: 'E', priority: 100, compile: function(element, attr) { var interpolateValueFn, interpolateTextFn; if (isDefined(attr.ngValue)) { // Will be handled by registerOption } else if (isDefined(attr.value)) { // If the value attribute is defined, check if it contains an interpolation interpolateValueFn = $interpolate(attr.value, true); } else { // If the value attribute is not defined then we fall back to the // text content of the option element, which may be interpolated interpolateTextFn = $interpolate(element.text(), true); if (!interpolateTextFn) { attr.$set('value', element.text()); } } return function(scope, element, attr) { // This is an optimization over using ^^ since we don't want to have to search // all the way to the root of the DOM for every single option element var selectCtrlName = '$selectController', parent = element.parent(), selectCtrl = parent.data(selectCtrlName) || parent.parent().data(selectCtrlName); // in case we are in optgroup if (selectCtrl) { selectCtrl.registerOption(scope, element, attr, interpolateValueFn, interpolateTextFn); } }; } }; }]; /** * @ngdoc directive * @name ngRequired * @restrict A * * @param {expression} ngRequired AngularJS expression. If it evaluates to `true`, it sets the * `required` attribute to the element and adds the `required` * {@link ngModel.NgModelController#$validators `validator`}. * * @description * * ngRequired adds the required {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. * It is most often used for {@link input `input`} and {@link select `select`} controls, but can also be * applied to custom controls. * * The directive sets the `required` attribute on the element if the AngularJS expression inside * `ngRequired` evaluates to true. A special directive for setting `required` is necessary because we * cannot use interpolation inside `required`. See the {@link guide/interpolation interpolation guide} * for more info. * * The validator will set the `required` error key to true if the `required` attribute is set and * calling {@link ngModel.NgModelController#$isEmpty `NgModelController.$isEmpty`} with the * {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} returns `true`. For example, the * `$isEmpty()` implementation for `input[text]` checks the length of the `$viewValue`. When developing * custom controls, `$isEmpty()` can be overwritten to account for a $viewValue that is not string-based. * * @example * * * *
*
* * *
* *
*
* required error set? = {{form.input.$error.required}}
* model = {{model}} *
*
*
* var required = element(by.binding('form.input.$error.required')); var model = element(by.binding('model')); var input = element(by.id('input')); it('should set the required error', function() { expect(required.getText()).toContain('true'); input.sendKeys('123'); expect(required.getText()).not.toContain('true'); expect(model.getText()).toContain('123'); }); * *
*/ var requiredDirective = ['$parse', function($parse) { return { restrict: 'A', require: '?ngModel', link: function(scope, elm, attr, ctrl) { if (!ctrl) return; // For boolean attributes like required, presence means true var value = attr.hasOwnProperty('required') || $parse(attr.ngRequired)(scope); if (!attr.ngRequired) { // force truthy in case we are on non input element // (input elements do this automatically for boolean attributes like required) attr.required = true; } ctrl.$validators.required = function(modelValue, viewValue) { return !value || !ctrl.$isEmpty(viewValue); }; attr.$observe('required', function(newVal) { if (value !== newVal) { value = newVal; ctrl.$validate(); } }); } }; }]; /** * @ngdoc directive * @name ngPattern * @restrict A * * @param {expression|RegExp} ngPattern AngularJS expression that must evaluate to a `RegExp` or a `String` * parsable into a `RegExp`, or a `RegExp` literal. See above for * more details. * * @description * * ngPattern adds the pattern {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls. * * The validator sets the `pattern` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} * does not match a RegExp which is obtained from the `ngPattern` attribute value: * - the value is an AngularJS expression: * - If the expression evaluates to a RegExp object, then this is used directly. * - If the expression evaluates to a string, then it will be converted to a RegExp after wrapping it * in `^` and `$` characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`. * - If the value is a RegExp literal, e.g. `ngPattern="/^\d+$/"`, it is used directly. * *
* **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to * start at the index of the last search's match, thus not taking the whole input value into * account. *
* *
* **Note:** This directive is also added when the plain `pattern` attribute is used, with two * differences: *
    *
  1. * `ngPattern` does not set the `pattern` attribute and therefore HTML5 constraint validation is * not available. *
  2. *
  3. * The `ngPattern` attribute must be an expression, while the `pattern` value must be * interpolated. *
  4. *
*
* * @example * * * *
*
* * *
* *
*
* input valid? = {{form.input.$valid}}
* model = {{model}} *
*
*
* var model = element(by.binding('model')); var input = element(by.id('input')); it('should validate the input with the default pattern', function() { input.sendKeys('aaa'); expect(model.getText()).not.toContain('aaa'); input.clear().then(function() { input.sendKeys('123'); expect(model.getText()).toContain('123'); }); }); * *
*/ var patternDirective = ['$parse', function($parse) { return { restrict: 'A', require: '?ngModel', compile: function(tElm, tAttr) { var patternExp; var parseFn; if (tAttr.ngPattern) { patternExp = tAttr.ngPattern; // ngPattern might be a scope expression, or an inlined regex, which is not parsable. // We get value of the attribute here, so we can compare the old and the new value // in the observer to avoid unnecessary validations if (tAttr.ngPattern.charAt(0) === '/' && REGEX_STRING_REGEXP.test(tAttr.ngPattern)) { parseFn = function() { return tAttr.ngPattern; }; } else { parseFn = $parse(tAttr.ngPattern); } } return function(scope, elm, attr, ctrl) { if (!ctrl) return; var attrVal = attr.pattern; if (attr.ngPattern) { attrVal = parseFn(scope); } else { patternExp = attr.pattern; } var regexp = parsePatternAttr(attrVal, patternExp, elm); attr.$observe('pattern', function(newVal) { var oldRegexp = regexp; regexp = parsePatternAttr(newVal, patternExp, elm); if ((oldRegexp && oldRegexp.toString()) !== (regexp && regexp.toString())) { ctrl.$validate(); } }); ctrl.$validators.pattern = function(modelValue, viewValue) { // HTML5 pattern constraint validates the input value, so we validate the viewValue return ctrl.$isEmpty(viewValue) || isUndefined(regexp) || regexp.test(viewValue); }; }; } }; }]; /** * @ngdoc directive * @name ngMaxlength * @restrict A * * @param {expression} ngMaxlength AngularJS expression that must evaluate to a `Number` or `String` * parsable into a `Number`. Used as value for the `maxlength` * {@link ngModel.NgModelController#$validators validator}. * * @description * * ngMaxlength adds the maxlength {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls. * * The validator sets the `maxlength` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} * is longer than the integer obtained by evaluating the AngularJS expression given in the * `ngMaxlength` attribute value. * *
* **Note:** This directive is also added when the plain `maxlength` attribute is used, with two * differences: *
    *
  1. * `ngMaxlength` does not set the `maxlength` attribute and therefore HTML5 constraint * validation is not available. *
  2. *
  3. * The `ngMaxlength` attribute must be an expression, while the `maxlength` value must be * interpolated. *
  4. *
*
* * @example * * * *
*
* * *
* *
*
* input valid? = {{form.input.$valid}}
* model = {{model}} *
*
*
* var model = element(by.binding('model')); var input = element(by.id('input')); it('should validate the input with the default maxlength', function() { input.sendKeys('abcdef'); expect(model.getText()).not.toContain('abcdef'); input.clear().then(function() { input.sendKeys('abcde'); expect(model.getText()).toContain('abcde'); }); }); * *
*/ var maxlengthDirective = ['$parse', function($parse) { return { restrict: 'A', require: '?ngModel', link: function(scope, elm, attr, ctrl) { if (!ctrl) return; var maxlength = attr.maxlength || $parse(attr.ngMaxlength)(scope); var maxlengthParsed = parseLength(maxlength); attr.$observe('maxlength', function(value) { if (maxlength !== value) { maxlengthParsed = parseLength(value); maxlength = value; ctrl.$validate(); } }); ctrl.$validators.maxlength = function(modelValue, viewValue) { return (maxlengthParsed < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlengthParsed); }; } }; }]; /** * @ngdoc directive * @name ngMinlength * @restrict A * * @param {expression} ngMinlength AngularJS expression that must evaluate to a `Number` or `String` * parsable into a `Number`. Used as value for the `minlength` * {@link ngModel.NgModelController#$validators validator}. * * @description * * ngMinlength adds the minlength {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls. * * The validator sets the `minlength` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} * is shorter than the integer obtained by evaluating the AngularJS expression given in the * `ngMinlength` attribute value. * *
* **Note:** This directive is also added when the plain `minlength` attribute is used, with two * differences: *
    *
  1. * `ngMinlength` does not set the `minlength` attribute and therefore HTML5 constraint * validation is not available. *
  2. *
  3. * The `ngMinlength` value must be an expression, while the `minlength` value must be * interpolated. *
  4. *
*
* * @example * * * *
*
* * *
* *