文档地址:https://docs.angularjs.org/api/ng/type/ngModel.NgModelController
简介
ngModelController是为ng-model指令提供的API。
这个控制器包含了为数据绑定(data-binding)、校验(validation)、CSS更新(CSS updates)、值的格式化和解析(value formatting and parsing)等功能。它不包含任何处理DOM渲染或者DOM事件监听的逻辑。这些与DOM相关的逻辑应该由那些使用ngModelController进行数据绑定来控制元素的指令提供。
自定义控件的例子
这个例子演示了一个自定义控件怎么使用ngModelController实现数据绑定。
注意不同指令(contenteditable、ng-model、required)之间是怎么共同合作实现了预想中的结果。
contenteditable是一个HTML5属性,告诉浏览器,元素的内容可以被用户编辑。
我们使用$sce服务和$sanitize服务来自动移除”bad”内容,比如行内事件监听器(e.g. ‘<span onclick=”…”>’)。
However, as we are using $sce the model can still decide to provide unsafe content if it marks that content using the $sce service.
CSS:
[contenteditable] { border: 1px solid black; background-color: white; min-height: 20px; } .ng-invalid { border: 1px solid red; }
关于ngSanitize:angular-ngSanitize模块-$sanitize服务详解
JS:
angular.module(‘customControl‘, [‘ngSanitize‘]).directive(‘contenteditable‘, [‘$sce‘, function($sce) { return { restrict: ‘A‘, // only activate on element attribute require: ‘?ngModel‘, // get a hold of NgModelController link: function(scope, element, attrs, ngModel) { if (!ngModel) return; // do nothing if no ng-model // Specify how UI should be updated ngModel.$render = function() { element.html($sce.getTrustedHtml(ngModel.$viewValue || ‘‘)); }; // Listen for change events to enable binding element.on(‘blur keyup change‘, function() { scope.$evalAsync(read); }); read(); // initialize // Write data to the model function read() { var html = element.html(); // When we clear the content editable the browser leaves a <br> behind // If strip-br attribute is provided then we strip this out if (attrs.stripBr && html === ‘<br>‘) { html = ‘‘; } ngModel.$setViewValue(html); } } }; }]);
HTML:
<form name="myForm"> <div contenteditable name="myWidget" ng-model="userContent" strip-br="true" required>Change me!</div> <span ng-show="myForm.myWidget.$error.required">Required!</span> <hr> <textarea ng-model="userContent" aria-label="Dynamic textarea"></textarea>
</form>
ngModelcontroller的属性说明
$viewValue
控件视图中的实际值。对于input元素,这个值是字符串。(参考$setViewValue了解什么时候设置$viewValue的值)
$modelValue
控件所绑定的模型中的值。
$parsers
一个函数组成的数组。作为一个管道(pipeline),每当控件用一个来自DOM的新的$viewValue(通常是通过用户输入)更新ngModelcontroller时执行$parsers中的函数。参考$setViewValue了解详细的生命周期说明。
注意,当绑定到ngModel的表达式是通过编程的方式改变时,$parses中的函数不会被调用。
这些函数按照在数组中的顺序被调用,前一个函数的返回值传递给下一个。最后一个函数的返回值被转发给$validators集合(验证器集合)。
解析器($parsers)用于消除或者转换$viewValue的值。
如果从一个解析器($parsers中的某一个函数)返回了undefined,那就意味着出现了一个解析错误。这种情况下,没有$validators会运行。而且,除非ngModelOptions.allowInvalid被设置为true,否则ngModel将会被设置为undefined。这个解析错误被存储在ngModel.$error.parse中。
下面是一个解析器将输入值(input value,或者说view value)转换为小写的例子:
function parse(value) { if(value) { return value.toLowerCase(); } } ngModelController.$parsers.push(parse);
$formatters
一个函数组成的数组。作为一个管道,每当绑定到ngModel上的表达式的值通过编程的方式改变时,数组中的函数将会被执行。当控件中的值是通过用户交互改变的时候,$formatters中的函数将不会被执行。
$formatters用于在控件(模型)中格式化或者转换$modelValue。
$formatters中的函数按照在数组中的倒序被调用,每一个函数执行后的返回值会传递给下一个函数。最后一个函数的返回值被作为最终的DOM值(DOM value)使用。
下面是一个formatter(不知道怎么翻译)将model value转换为大写的例子:
function format(value) { if(value) { return value.toUpperCase(); } } ngModelController.$formatters.push(format);
$validators
一个验证器(validators)的集合(相当于一个字面量对象),当model value需要被更新时会被应用。这个对象的键值(key value)是验证器(validator,是一个函数,可以进行相应的验证运算)的名字。model value传入验证器,作为验证器的一个参数。验证器根据验证结果返回一个Boolean值。
例子
NgModelcontroller.$fvalidators.validCharacters = function(modelValue,viewValue) { var value = modelValue || viewValue; return /[0-9]+/.test(value) && /[a-z]+/.test(value) && /[A-Z]+/.test(value) && /\W+/.test(value); };
$asyncValidators
异步验证。一个校验的集合,用来进行异步验证(e.g. 一个HTTP请求 )。
当校验函数在模型校验期间(model validation process)运行时,校验函数会返回一个promise。一旦这个promise被delivered,当满足校验时,那么校验的状态(validation status)将会被设为true;当不满足时,被设为false。
当异步校验器(asynchronous validators)被触发时,每一个校验器都会并行运行,并且只有当所有校验器都满足后,model value才会被更新。只要任一校验器没有被满足,那么对应的键(key,可以理解为这个校验器的名字)将会被添加到ngModelcontroller的$pending属性中。
当然,只有当所有的同步校验器(synchronous validators)都校验通过之后,所有的异步校验器才会运行。
注意:如果使用了$http服务,那么为了校验通过,服务器返回一个成功的HTTP响应状态码是非常重要的。为了使其不满足校验,那么就返回‘4xx’状态码。
例子
ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) { var value = modelValue || viewValue; // Lookup user by username return $http.get(‘/api/users/‘ + value). then(function resolved() { //username exists, this means validation fails return $q.reject(‘exists‘); }, function rejected() { //username does not exist, therefore this validation passes return true; }); };
$viewChangeListeners
一个函数组成的数组。当view value发生变化时,其中的函数将被执行。函数被调用的时候没有参数,而且函数的返回值也会被忽略。这可以被用于代替额外的用于监听model value的$watches。
$error
An object hash with all failing validator ids as keys.
(如果$validators中的某一个验证函数返回了false,那么这个验证函数的名字就成为$error的一个键,这个键被赋值为true)。
其他的一些属性:
$pending – object -- An object hash with all pending(待定) validator ids as keys(validator ids是验证函数的名字).
$untouched -- boolean -- True if control has not lost focus yet.
$touched -- boolean -- True if control has lost focus.
$pristine -- boolean -- True if user has not interacted with the control yet.
$dirty -- boolean -- True if user has already interacted with the control.
$valid -- boolean -- True if there is no error.
$invalid -- boolean -- True if at least one error on the control.
$name -- string -- The name attribute of the control.
ngModelcontroller的方法说明
$render();
当视图(view)需要被更新时调用。使用ng-model的那个指令负责实现这个方法。
在下列情形时,$render()方法才会被调用:
- $rollbackViewValue()被调用的时候。如果我们把view value回滚到最后提交的那个值,那么$render()被调用来更新这个输入控件(input control)。
- 通过编程方式改变ng-model指令所引用的那个值并且$modelValue和$viewValue都与上次的值不同时。
因为ng-model不进行深检测(watch),$render()只在$modelValue和$viewValue的值确实与之前的值不同时才调用。如果$modelValue或者$viewValue的值为对象(object)而不是字符串(string)或者数字(number),并且你只更改了这个对象的一个属性的值时,那么$render()将不会被调用。
$isEmpty(value);
当我们要判断一个输入值是否为空时,可以调用这个函数。
比如,有些指令根据输入的值是否为空而决定是否执行。
默认情况下,$isEmpty函数检测输入值是否是‘undefined’、‘’’’、‘null’或者‘NaN’。
对于输入指令,空值的概念可能与默认定义的那些不同,此时你可以重写这个方法。指令‘checkboxInputType’就是这么做的,因为对于这个指令来说‘false’意味着空值。
这个函数的参数是要检测的那个输入的值。
该函数返回一个boolean值,true意味着检测的那个值是空。
$setPristine();
把控件重置到它最初的状态(pristine state)。
调用这个方法用于移除控件class中的‘ng-dirty’并且把这个控件重置到最初的状态(添加‘ng-pristine’class)。当一个控件自从被第一次编译之后没有再被更改,那么可以认为这个模块(原文中用的是model)是最初的状态。
$setDirty();
将控件设置为脏状态(dirty state)。
调用这个方法用于移除控件的‘ng-pristine’class,并把这个控件设置为脏状态(添加‘ng-dity’class)。如果一个控件自从被第一次编译之后被更改,那么可以认为这个模型(原文中用的是model)处于脏状态。
$setUntouched();
把控件设置为untouched state。
调用这个方法用于移除控件的‘ng-touched’class,并且把这个控件设置为untouched(添加‘ng-untouched’class)。在编译时,一个模型默认被设为untouched。然而,如果这个模型已经被用户touched,这个函数可以被用来恢复模型的状态。
$setTouched();
把控件设置为touched state。
调用这个方法用于移除‘ng-untouched’class,并且把这个控件设置为touched state(添加‘ng-touched’class)。如果用户第一次将焦点定位到了控件元素上,然后又将焦点从控件上移开,那么就认为这个模型已经被touched。
$rollbackViewValue();
取消更新(update)并且重置(reset)输入元素的值,阻止对$modelValue更新。
这个方法会把数据模型的值返回给视图,同时取消所有的将要发生的延迟更新事件。
如果有一个输入控件使用了ng-model-options,并且给ng-model-options设置了去抖动更新(debounced updates)或者设置了基于特殊事件的更新(比如blur事件),那么就存在一段时期,$viewValue与$modelValue不是同步的。
在这种情况下,你可以用$rollbackViewValue()手动取消这种去抖动更新或者说基于事件的更新(debounced / future update),并且重置这个输入(控件的)值为最后提交的那个视图值(view value)(实际上是将视图值与当前的$modelView同步)。
如果你试图在这些去抖动更新函数执行之前或者基于事件更新的事件发生之前(before these debounced/future events have resolved/occurred)通过编程的方式更新ngModel指令的$modelValue的值,那么你可能遇到困难。因为AngularJS的脏检测机制不能分清数据模型到底有没有确实发生改变。
$rollbackViewValue方法应该在通过编程方式改变一个输入控件的数据模型之前调用,这个控件可能有被挂起的事件(debounced/future events)。这对于确保输入控件的值能顺利地与新的数据模型同步并且同时取消那些挂起的事件是很重要的。
举例子之前脑海中要先有这么一个想法:ng-model-options的作用是延迟view value立即同步给model value(可以通过事件、时间或者自定义的去抖动函数实现);$rollbackViewValue的作用是将当前的model value立即同步给view value,并且取消挂起的延迟同步函数或事件等。而且一般情况下是用不到$rollbackViewValue方法的,这个方法一般在使用了ng-model-options的情形下才可能用得到。