directive二
本章主要是讲指令与ngModel的交互。
在angular有一个内置指令叫ngModel,它是angular用来处理表单的最重要的指令。在源码中,页面上的model值的格式化、解析、验证都是由ngModel指令所对应的控制器ngModelController来实现的。ngModelController提供了很多方法和属性,我们可以通过自定义指令的require:'ngModel’来获取内置指令ngModel的ngModelController,进而在link函数里对操作ngModelController的方法和属性。
接下来具体讲一下ngModelController的方法和属性,以及它们的具体用法。
1.两个核心属性$viewValue和$modelValue
$viewValue是指令渲染模板所用的值,而$modelValue是在控制器中流通的值。很多时候这两个值是不一样的。比如说,当页面上展示了一个日期,显示的可能是"2018/04/23",但是这个字符串在控制器中对应的值可能是一个js的Date对象的实例。
2.三个核心管道$formatters $parses $validators 和$asyncValidators
angular除了提供$viewValue和$modelValue之外,还提供了两个用来处理它们的方法,分别是$parses和$formatters。
**$parsers:**由view值到model值的转换器,用户输入的变化会触发其中的管道函数,页面上的model值由管道函数中的return值决定。parsers属性有事件监听,当ng-model数据在dom端发生变化时候,就会触发事件,执行管道函数。比较常见的就是用户输入/编辑值。
**$formatters:**和$parsers的作用相反,它是model值到view值的转化器,其view值由对应的管道函数中的return值决定。formatters属性会有实时监听事件,当我们ng-model的数据以编程的方式改变时候,就会触发事件,此时会以管道形式执行所有函数,包括传参及其返回值操作。比如我们在通过js改变了值,此时就会触发事件。
$validators: 是一个json对象
{
validateName: function(modelValue,viewValue){
return ...
}
}
当$setViewValue(value)被赋值给$modelValue之前,会经过$parsers管道,经过$parsers管道时,就会经过这个$validators管道。其中,validateName是验证器的名称,其中的参数modelValue和viewValue就是$modelValue和$viewValue,如果返回值是true,则通过validateName。
自定义一个验证器:
<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);
};
$asyncValidators: 也是一个json对象,不过可以用来处理异步验证。
<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() {
//请求失败
})
};
}
}
});
3.两个核心方法$setViewValue()和$render()
$setViewValue(): 更新视图值。原因是每个input的输入事件都会调用这个方法。但是如果value是一个对象,而不是字符串或数值,那么我们在用该方法传入值之前应该先拷贝一份,因为ngModel不会深入检测对象的变化,它只看对象的引用是否发生变化。如果只是改变了对象的某个属性,也不会去经过$parsers和$validators管道。执行$setViewValue()方法,不会触发render()????*在视图需要被更新的时候调用。由于ngModel的对比机制,$render()只在$modelValue和$viewValue都发生了实际的改变, 才会会被调用。也就是说,$render函数负责将模型值同步到视图上。
来看一下指令和控制器及其内部运作如图:
这里讲一下具体的过程。
一般而言,html上通过ng-model绑定后,就会传入到$modelValue,如下面的ng-model=“data”。
<div ng-app="myApp" ng-controller="myCtrl">
<input ng-model="data" type="text" test/>
<span ng-click="changeModel()">{{data}}</span>
</div>
看一下js代码:
.controller('myCtrl', function($scope){
$scope.data = 'yellow';
$scope.changeModel = function() {
$scope.data = 'yellow';
}
})
.directive('test', function(){
return {
require: 'ngModel',
link: function(scope, elem, attrs, ngModel){
ngModel.$formatters.push(function(value){
//formats the value for display when ng-model is changed
return 'brown';
});
ngModel.$parsers.push(function(value){
//formats the value for ng-model when input value is changed
return 'green';
});
}
};
});
看一下页面效果:
初始页面:
一旦修改input框的值,就能被$parsers捕捉到,$parsers返回值就对应了$modelValue的值,从而改变{{data}},但是并没有改变页面上input框的值。效果:
一旦点击了span的值,触发changeModel()之后,$scope.data发生了变化,就能被$formatters捕捉到,$formatters的返回值对应了$viewValue的值,此时页面就会变成初始页面。
还有一些常用属性
| 属性 | 说明 |
|---|---|
| $error | json对象。就是所有验证失败的验证名和失败信息组成的json对象。 |
| $pending | json对象。正在进行中的异步验证会被放在这个对象里。 |
| $untouched | 布尔值。如果元素还没有失去过焦点,那这个值就是true。 |
| $touched | 布尔值。如果元素已经失去过焦点,那这个值就是false。 |
| $pristine | 布尔值。如果元素还没有和用户发生过交互,那这个值就是true。 |
| $dirty | 布尔值。如果元素已经和用户发生过交互,那这个值就是true。 |
| $valid | 布尔值。这个也很常用,就是当所有验证(异步同步),都通过的时候,它就是true。 |
| $invalid | 布尔值。这个也很常用,就是当所有验证(异步同步),其中有一个或一个以上验证失败,它就是true。 |
| $name | 字符串。很简单,就是获取元素的name属性。 |
| $isEmpty | 当我们需要判断input的value值是否为空的时候,可以使用这个方法。 |