第一个点,要了解下:
DOM value
$viewValue
$modelValue
scope上挂载的属性的值
一般有2个流程:
$viewValue -->> $modelValue -->> scope
上挂载的属性的值scope上挂载的属性的值
-->> $modelValue -->> $viewValue
ngModel
常用的场景就是如果你使用第三方的插件例如时间插件,每次选择时间后都是更新DOM value
的值,这个时候DOM value
上的值事实上是你需要绑定到scope
属性上的值。那么这个时候就需要ngModelController
。
在第一个流程当中,例如我加载了一个时间插件:pikaday
html:
<div class="container" ng-controller="demoCtrl">
<p>选择的时间为: {{date.from}}</p>
<time-picker
ng-model="date.from"
></time-picker>
</div>
js:
angular.module('demoApp', []).controller('demoCtrl', function($scope) {
$scope.date = {
from: ''
};
$scope.$watch('date.from', function(val) {
console.log(val);
});
})
.directive('testDirective', ['$timeout', function($timeout) {
return {
restrict: 'EA',
require: '?ngModel',
link: function(scope, ele, attrs, ngModel) {
template: '<input type="text" id="pikadayTimerPicker">',
link: function (scope, ele, attr, ngModel) {
var picker = new Pikaday({
field: $('#pikadayTimerPicker')[0],
firstDay: 0,
yearRange: [2000, 2020],
format: 'YYYY-MM-DD',
hours24format : false,
showTime : true,
splitTimeView : true,
showSeconds : true,
minutesStep : 5,
i18n: {
previousMonth : '上月',
nextMonth : '下月',
months : ['一月','二月','三月','四月','五月','六月','七月','八月','九月','十月','十一月','十二月'],
weekdays : ['周日','周一','周二','周三','周四','周五','周六'],
weekdaysShort : ['日','一','二','三','四','五','六']
},
onSelect: function () {
if(!ngModel) return; //如果ngModel不存在
ngModel.$setViewValue(this._d);
}
});
}
}
}
})
在onSelect
回调事件里面,调用了ngModel.$setViewValue
方法,它的作用就是使DOM value
–>> $viewValue
–>> $modelValue
–>> scope
上绑定的属性.
第二个流程当中,比如你需要初始化一个时间,这个时间可能是从你后台调过来的,或者是获取的本地的时间.那么你首先要绑定scope
上的属性值,但是这个时候在时间插件上面显示的时间并不是scope
上绑定的属性值,这个时候就需要$render
方法了:
$scope.data.from = $filter(new Date().valueOf())('YYYY-MM-DD hh:mm:ss');
xxxxx
var picker = new Picker({ xxxx });
//这个地方调用
ngModel.$render = function() {
$('#pikadayTimerPicker').val(ngModel.$viewValue);
}
xxxxx
这里执行的流程就是:
scope上绑定的属性值发生变化 –>> $modelValue –>> $viewValue –>> 调用$(‘#pikadayTimerPicker’).val(ngModel.$viewValue)去更新DOM value.
在2个流程当中还应当注意一些地方:
第一个流程当中会经过$parsers, $validators2个管道。
第二个流程当中会经过$formatters,$render, $validators 3个管道。
关于$parses, $formatters , $validators 3个管道的用法会在下一篇里面讲。
这里着重讲一下源码里的$render方法(文档23189行),
ctrl.$render = function() {
// Workaround for Firefox validation #12102.
var value = ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue;
if (element.val() !== value) {
element.val(value);
}
};
这里就是首先判断$viewValue
是否为空,然后再判断当前元素的DOM Value
和$viewValue
是否一样,再选择是否更新视图。
另外就是需要注意的一个地方(文档26947行):
$scope.$watch(function ngModelWatch() {
var modelValue = ngModelGet($scope); //获取scope上绑定的ng-model的值
// if scope model value and ngModel value are out of sync
// TODO(perf): why not move this to the action fn?
if (modelValue !== ctrl.$modelValue &&
// checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator
(ctrl.$modelValue === ctrl.$modelValue || modelValue === modelValue)
) {
ctrl.$modelValue = ctrl.$$rawModelValue = modelValue;
parserValid = undefined;
var formatters = ctrl.$formatters,
idx = formatters.length;
var viewValue = modelValue;
while (idx--) {
viewValue = formatters[idx](viewValue);
}
if (ctrl.$viewValue !== viewValue) {
ctrl.$$updateEmptyClasses(viewValue);
ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue;
ctrl.$render();
ctrl.$$runValidators(modelValue, viewValue, noop);
}
}
return modelValue;
});
$render
方法一般是当ng-model
的值发生变化的时候就会触发:
例如
ng-model
直接放在input
,select
标签内,那么ng
会自动响应ng-model
变化后,便会触发这个方法,来使Dom
和$scope
上挂载的属性值保持相同.指令内部封装了
input
标签,但是ng-model
是在指令外部的外部标签下时。一般需要通过event handler
去调用ngModel.$setViewValue
方法去时DOM Value
和scope
上挂载的属性值保持一致。具体的demo就如上例。
ngModel.$render
方法是可以重新自定义的:
ngModel.$render = function() {
// scope上的属性值,$modelValue, $viewValue已经发生变化
// 利用这些值再次可以再次做相应的处理,然后更新DOM value
}
抽时间把关于ngModel再补上。睡觉。
参考资料: