在这篇文章中 angular学习笔记(三十)-指令(10)-require和controller 说到了经过require属性和controller参数来让指令与指令之间互相交互.css
本篇主要介绍的是指令与ngModel指令的交互.也就是说,ngModel指令虽然是内置的,但它也有本身的controller属性,其它指令也能够经过require来获得ngModel指令的controller属性的实例来与ngModel指令进行交互.html
ngModelController用在什么场合呢?咱们知道,ngModel提供了数据绑定,验证,样式更新,数据格式化,编译功能,可是它故意没有提供和逻辑相关的处理,好比视图的从新渲染和监听dom事件.这些和逻辑处理相关的dom,就应该使用ngModelController来进行数据绑定.git
ngModelController的方法和属性不少...没法一一列举,有些也不多用,这里会重点讲一下经常使用的几个(带有*的):angularjs
方法:github
*1. $render()json
这个方法会在视图须要被更新的时候调用. 好比如下这些场景:bootstrap
$rollbackViewValue()
被调用. 若是咱们把视图值回滚到数据模型的值时,$render()会被调用.关于$rollbackViewValue()这个方法,详见此文:angular-1.3 之ng-model-options指令因为ng-model没有深度对比模型的变化.什么叫没有深度对比模型的变化: 也就是angular学习笔记(十四)-$watch(1)这里提到的第三个参数ifDeep,ng-model内置的对比机制,至关于这里的ifDeep是false,不进行深度对比.因此$render()只在$modelValue和$viewValue都发生了实际的改变, 才会会被调用.什么叫实际的改变? 就是说,若是$modelValue或者$viewValue是一个对象,而不是一个字符串或者数字,那么,这个对象中的一个属性值发生了变化,这不算真正的变化,由于它对象的引用地址没有发生变化,它指向的仍是同一个对象.api
*2. $isEmpty(value) 数组
当咱们须要判断input的value值是否为空的时候,可使用这个方法.promise
value是必需要传的,注意它判断的是value值是否为空,而不是ngModel绑定的那个数据模型的值.其实能够就当它是个判断是否为空的方法,传入一个参数,判断这个参数是否为空,你传入任何值均可以.只是说,通常状况下都会把ngModel绑定的那个值传给它.
你能够本身在指令里重写这个方法,来定义本身所须要的'是否为空'的概念.好比用在一个类型为checkbox的input元素上,由于当checkbox的值为false的时候,$isEmpty()的结果就是empty.
若是值是undefined,null,'',或者NaN,则返回true,不然返回false.
3. $setValidity(validationErrorKey, isValid);
4. $setPristine()
把元素设置到原始状态.移除元素的ng-dirty类名,添加ng-pristine类名.
5. $setDirty()
把元素设置到脏值模式.移除元素的ng-pristine类型,添加ng-dirty类名。
6.$setUntouched()
把元素设置到没有触碰过的状态.移除ng-touched类名.添加ng-untouched类名.
7.$setTouched()
把元素设置到触碰过的状态.移除ng-untouched类名,添加ng-touched类名.
8.$rollbackViewValue()
参考:angular-1.3 之ng-model-options指令
9.$validate()
10.$commitViewValue()
把一个未发生的更新提交给$modelValue.
在使用ng-model-options指令的时候,input元素可能正在等待某个事件的触发,来同步一个将要发生的更新.这个方法不多用,由于ngModelController一般在事件响应中自动处理了这件事.
*11.$setViewValue(value, trigger)
更新视图值.当一个input的指令元素想要改变视图值的时候,这个方法会被调用.这一般是dom元素内部的事件来处理的.最典型的例子就是在input中输入值,会改变Hello后面的视图的值.缘由就是input的输入事件会调用$setViewValue方法.相似的还有select元素.
若是value是一个对象,而不是一个字符串或者数值,那咱们应该在传入$setViewValue以前先拷贝一份.由于ngModel不会深度监测对象的变化,它只看对象的引用地址是否发生了变化.若是你仅仅改变了对象的某个属性,ngModel不会意识到它已经改变了,也不会去通过$parsers和$validators管道.
所以,当对象被传入到$setViewValue函数里之后,你不能再改变它的属性值,不然可能引发当前scope下的模型值被错误地改变.
当$setViewValue被调用时,新的value将会经过$parsers和$validators管道后被提交. 若是没有配置ngModelOptions,那么value直接进入处理流程,最后它被应用到$modelValue和ng-model绑定的属性表达式上.
还有一点,全部添加在$viewChangeListeners这个数组里的函数,都会被执行.
在使用了ngModelOptions的状况下,上面的说法不适用.上面说到的这些行为都会被等待直到dom元素的updateOn事件触发.一样,若是定义了debounce延迟,那么这些行为也会在延迟时间到了之后才发生.
须要注意,执行$setViewValue()方法,不会触发$digest.
这里的trigger是作什么的,不太明白...
属性:
*1.$viewValue
指令元素的视图中实际的值.注意,它必定等于 $setViewValue(value)的value值
*2.$modelValue
ngModel绑定的数据模型的模型值.它不必定等于 $setViewValue(value)的value值,在$setViewValue(value, trigger)里面提到的使用了ngModelOptions时,好比虽然调用了$setViewValue,可是由于设置了ngModelOptions的debounce属性,因此它会延迟,等到同步的时候,value值才会被设置到$modelValue上.
3.$parsers
一个数组.数组里的元素是函数. $setViewValue(value)被赋值给$modelValue以前,value值首先会通过$parsers里的全部函数,每次将返回值传递给下一个函数.最后才被赋值到$modelValue.在这个过程当中就包括了验证和转换.对于验证这个步骤,它会使用$setValidity这个方法,验证失败的将返回undefined.
4.$formatters
一个数组.数组里的元素是函数. 和$parsers同样,它也是管道.当模型值发生变化的时候被调用.模型值会倒着调用数组中的函数,而后把返回值传给下一个函数,最后返回的值就会被传递给dom元素.用来在视图中格式化模型值:
一个将小写转换为大写的格式化方法:
function formatter(value) { if (value) { return value.toUpperCase(); } } ngModel.$formatters.push(formatter);
*5. $validators
一个json对象.
{ validateName: function(modelValue,viewValue){ return ... } }
当$setViewValue(value)被赋值给$modelValue以前,会通过$parsers管道,通过$parsers管道时,就会通过这个$validators管道.其中validateName是验证的名字,函数是这个验证的方法,其中的参数modelValue和viewValue就是$modelValue和$viewValue,若是返回值是true,则经过validateName
验证,若是返回值是false,则没有经过validateName验证,若是没有经过validateName验证,$error.validateName就会为true.这就是angular内部验证表单项的原理.
eg: 自定义一个验证规则,输入内容中必须包含数字
<div class="alert alert-danger" role="alert" ng-show="myForm.myWidget.$error.validCharacters"> <strong>Oh!</strong> 不符合自定义的验证规则! </div>
ngModel.$validators.validCharacters = function(modelValue, viewValue) { var value = modelValue || viewValue; return /[0-9]+/.test(value); };
*6.$asyncValidators
一个json对象.用来处理异步验证(好比一个http请求).
{ validateName: function(modelValue,viewValue){ return promise } }
其中validateName是验证的名字,函数是这个验证的方法,其中的参数modelValue和viewValue就是$modelValue和$viewValue,返回值必须是一个promise对象,若是这个promise对象传递给它下一个.then方法失败通知,则不经过validateName验证,若是这个promise对象传递给它下一个.then方法成功通知,则表示经过validateName验证.当异步验证开始执行的时候,全部的异步验证都是平行并发的.只有当全部的验证都经过时,数据模型才会被同步更新.只要有一个异步验证没有完成,这个验证名就会被放到ngModelController的$pending属性中.另外,全部的异步验证都只会在全部的同步验证经过之后才开始.
核心代码:
<input validate-name type="text" name="myWidget" ng-model="userContent" ng-model-options="{updateOn:'blur'}" class="form-control" required uniqueUsername> <div class="alert alert-danger" role="alert" ng-show="myForm.myWidget.$error.uniqueUsername"> <strong>Oh!</strong> 已经存在的用户名! </div>
app.directive('validateName',function($http,$q){ return { restrict:'A', require:'?^ngModel', link:function(scope,iele,iattr,ctrl){ ctrl.$asyncValidators.uniqueUsername = function(modelValue, viewValue) { var value = modelValue || viewValue; // 异步验证用户名是否已经存在 return $http.get('/api/users/' + value). then(function resolved(res) { if(res.data){ //用户名已经存在,验证失败,给下一个promise传递失败通知. return $q.reject('res.data'); } else { //用户名不存在,验证成功. return true } }, function rejected() { //请求失败 }) }; } } });
异步验证比较重要,因此我会另外开一篇文章来举例详解:angular中的表单数据异步验证
7.$viewChangeListeners
一个数组,数组中的元素都是函数.这些函数在视图发生改变的时候被执行,不带有什么参数,也不须要返回值.在第11条方法里说到,在不使用ngModelOptions延迟时调用$setViewValue的时候,他们就会执行.
*8.$error
json对象. 这个很简单,用到不少次了.就是全部验证失败的验证名和失败信息组成的json对象.
*9.$pending
json对象. 第6个属性里提到过的,正在进行中的异步验证会被放在这个对象里
10.$untouched
布尔值.若是元素尚未失去过焦点,那这个值就是true.
11.$touched
布尔值.若是元素已经失去过焦点,那这个值就是false.
12.$pristine
布尔值.若是元素尚未和用户发生过交互,那这个值就是true.
13.$dirty
布尔值.若是元素已经和用户发生过交互,那这个值就是true.
*14.$valid
布尔值.这个也很经常使用,就是当全部验证(异步同步),都经过的时候,它就是true
*15.$invalid
布尔值.这个也很经常使用,就是当全部验证(异步同步),其中有一个或一个以上验证失败,它就是true.
16.$name
字符串.很简单,就是获取元素的name属性.
注意上面说到的这些属性:
咱们这篇文章说的是ngModelController,因此说这些属性是ngModelController的属性,可是其中有一部分通常不在ngModelController里面用,好比:$error,$pending,$valid,$invalid,等,这些属性,咱们一般是这样用的:
<div class="alert alert-danger" role="alert" ng-show="myForm.myWidget.$error.uniqueUsername"> <strong>Oh!</strong> 已经存在的用户名! </div> <div class="panel-body"> {{myForm.myWidget.$pending}} </div>
可是,你内心要知道,其实他们也是ngModelController的属性哦~
ngModelController就所有讲完了,可能看起来比较混乱...我尽可能按照理解的总结一下:
两个核心的属性:
$viewValue: 视图里的值,也就是input输入框的值,这个值就是$setViewValue(value)中的value.
$modeValue: 数据模型的值
$viewValue会在input事件触发的时候,被同步到$modelValue.什么叫input事件触发? 若是我什么都没有定义,那么,就是ng默认的事件,也就是一边输入,就会一边触发.若是是定义了ngModelOptions,那就是在本身指定的事件触发的时候,$viewValue被同步到$modelValue.
$viewValue被同步到$modelValue时,并非直接就赋值了,而是通过了下面说到的三个核心管道.
双向数据绑定的那个表达式,好比ng-model='name', 这个scope下的name值,它是和$modelValue保持一致的.因此,当input事件触发的时候,$modelValue被赋值,name也就在这时被改变为这个值.
两个核心方法:
$render: 若是模型值被改变,须要同步视图的值(后台改变了模型值,或者使用了$rollbackViewValue()).也就是说,$render函数负责将模型值同步到视图上.
$setViewValue: 用于设置视图值,也就是将input的value值赋值给$viewValue.
须要注意: $render是同步模型值到视图值,那么同步视图值到模型值是什么方法呢? 注意angular并无为咱们提供这样一个接口.而是在两个核心属性里面提到的,当input事件触发时候,就会把视图值同步到模型值,可是咱们能够自定义视图值同步到模型值的过程当中的三个管道.
三个核心管道:
$parsers: 用于改变视图值的格式.
$validators: 用于添加自定义的同步验证.
$asyncValidators: 用于添加自定义的异步验证.
这个三个管道具体怎么用,看例子.
四个经常使用属性:
$error: 用来存储验证错误
$pending: 用来存储正在异步验证中的验证内容
$valid: 用来存储表单项是否都经过了验证.
$invalid: 用来存储表单项是否都经过了验证.
这些都是用在html里面,使用myFrom.myWidget...来获取的...
最后我用一个例子来把这个流程给顺一遍:
'请输入内容'这个文本框实际上是个div,可编辑的的div,而后咱们经过ngModelController来让它实现和input同样的双向数据绑定的效果.
为了清楚的看到效果,我经过ngModelOptions给它添加了1000毫秒的延迟.
另外,把输入的内容经过$parsers属性来进行格式转换,把小写的转换为大写.
下面来看代码:
html:
<!DOCTYPE html> <html ng-app="customControl"> <head> <title>ngModelController</title> <meta charset="utf-8"> <script src="../angular-1.3.2.js"></script> <script src="script.js"></script> <link type="text/css" href="../bootstrap.css" rel="stylesheet" /> <style> *{font-family: 'MICROSOFT YAHEI'} </style> </head> <body> <div class="container" ng-controller="ctrl"> <div class="page-header"> <h1>ngModelController- <small>建立一个实现了双向数据绑定的可编辑文本区域</small></h1> </div> <form role="form" name="myForm"> <div class="form-group"> <div contenteditable name="myWidget" ng-model="userContent" ng-model-options="{debounce:1000}" class="form-control" required default-text="请输入内容"></div> </div> <div class="form-group"> <button type="button" class="btn btn-default btn-primary" ng-click="setNone()">设置为'抱歉,我没有想输入的内容'</button> </div> <div class="alert alert-danger" role="alert" ng-show="myForm.myWidget.$error.required"> <strong>Oh!</strong> 必填! </div> </form> <div class="panel panel-primary"> <div class="panel-heading"> <h3 class="panel-title">用户输入的内容为:</h3> </div> <div class="panel-body"> {{userContent}} </div> </div> </div> </body> </html>
它和普通的双向数据绑定的惟一区别就是,它是一个可编辑div.
而后咱们看angularjs是如何处理contenteditable指令的:
var app = angular.module('customControl',[]); app.controller('ctrl',function($scope){ $scope.setNone = function(){ $scope.userContent = '抱歉,我没有想输入的内容' } }); app.directive('contenteditable',function(){ return { restrict:'A', require:'?^ngModel', link:function(scope,element,attrs,ngModel){ if(!ngModel){ return } //一开始scope.userContent是空 console.log(ngModel.$isEmpty(scope.userContent)); ngModel.$setViewValue(attrs.defaultText); //这里其实不须要调用的,只是为了演示$isEmpty,不是demo须要 //调用了$setViewValue之后就不为空了,可是若是设置了ngModelOptions,则没用,由于$setViewValue没有被赋值给$modelValue. console.log(ngModel.$isEmpty(scope.userContent)); ngModel.$render = function(){ element.html(ngModel.$viewValue || attrs.defaultText) }; element.bind('focus',function(){ if(element.html()==attrs.defaultText){ element.html('') } }); element.bind('focus blur keyup change',function(){ console.log(scope.userContent);
ngModel.$setViewValue(element.html()); console.log('$viewValue为:'+ngModel.$viewValue); console.log('$modelValue为:'+ngModel.$modelValue); }); ngModel.$parsers.push(function(value){ return value.toUpperCase() }) } } });
$isEmpty(value):
这里把userContent传入,判断它是否为空,若是这里没有使用ngModelOptions,那么在调用了$setViewValue之后,userContent就会有值了.可是这里使用了ngModelOptions,因此$setViewValue之后,$viewValue值不会立刻被赋值给$modelValue,而模型值应该是等于$modelValue的,
因此这里获得的两次结果都是true.
$render():
当模型值变化的时候,这个方法会被调用,也就是当我点击 "设置为'抱歉,我没有想输入的内容'" 按钮的时候,userContent发生了变化,会调用$render()方法.注意一点,当直接改变userContent的值的时候,$viewValue和$modelValue都会被异步的改变为这个值.改变之后,再调用$render().
$setViewValue():
当用户输入的时候,经过$setViewValue改变$viewValue的值, 默认的input ng-model它本身处理了这件事,这里div元素咱们手动处理.
$viewValue和$modelValue和绑定值:
这里我ngModelOptions设置延迟了1000毫秒,当我很慢很慢的输入时,结果以下:
能够看到,当尚未开始输入时,$viewValue是'',由于div里面的内容就是'',而$modelValue是undefined
当我开始输第一个字,$viewValue会马上变成我输入的内容(这是$setViewValue的做用),可是$modelValue不会发生变化
当我延迟了1000毫秒之后,再输入下一个字,$viewValue固然实时同步了,而能够看到,$modelValue也同步了上一次输入的值.由于已通过了1000毫秒了.
...
值得注意的是,userContent始终是和$modelValue一致的.或者说$modelValue是和userContent一致的.我也不清楚是谁先变化.但能够知道他俩是一致的.
最后延迟1000毫秒后让鼠标失去焦点,这样,三个值是彻底一致的了.
若是输的快一点,那就是这样:
$parsers:
这个很简单,就是让$viewValue被赋值给$modelValue的时候通过这个管道,把小写变成了大写.
点击查看效果: http://plnkr.co/edit/CbOS1nFosPDfQXvsGTyR?p=preview
相关阅读:
ngModelOptionsng(包含ngModel中的$rollbackViewValue方法):
angular-1.3 之ng-model-options指令
ngModel自定义验证:
参考文献: https://docs.angularjs.org/api/ng/type/ngModel.NgModelController
完整代码: https://github.com/OOP-Code-Bunny/angular/tree/master/ngModelController