第一个点,要了解下:
DOM value
$viewValue
$modelValue
scope上挂载的属性的值
一般有2个流程:
$viewValue -->> $modelValue -->> scope
上挂载的属性的值scope上挂载的属性的值
-->> $modelValue -->> $viewValue
ngModel
常用的场景就是如果你使用第三方的插件例如时间插件,每次选择时间后都是更新DOM value
的值,这个时候DOM value
上的值事实上是你需要绑定到scope
属性上的值。那么这个时候就需要ngModelController
。
在第一个流程当中,例如我加载了一个时间插件:
html: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: '', 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); } }); } } } })选择的时间为: {
{date.from}}
在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再补上。睡觉。
参考资料: