angularjs中ngModelController学习

我们首先看看ngModelController内部的签名是怎么样的?


我们首先看看下面的例子1:

[html]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <!doctype html>  
  2. <html ng-app="form-example2">  
  3.     <head>  
  4.         <link href="../bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen">  
  5.         <script src="framework/angular-1.3.0.14/angular.js"></script>  
  6.         <script src="FormCustom.js"></script>  
  7.         <style type="text/css">  
  8.             div[contentEditable] {  
  9.                 cursor: pointer;  
  10.                 background-color: #D0D0D0;  
  11.             }  
  12.         </style>  
  13.     </head>  
  14.     <body>  
  15.         <div>  
  16.           <!--这里的contentEditable就是我们自定义的指令-->  
  17.             <div contentEditable="true" ng-model="content" title="Click to edit">Some</div>  
  18.             <pre>model = {{content}}</pre>  
  19.         </div>  
  20.     </body>  
  21. </html>  
我们来看看

[javascript]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. angular.module('form-example2', []).directive('contenteditable'function() {  
  2.     //我们定义了一个指令contenteditable  
  3.     return {  
  4.         require : 'ngModel',  
  5.         //ng-model指令是一个特殊的指令,他提供更底层的API来处理控制器中的数据,当我们在指令中使用ng-model时候可以访问一个特殊的API,这个API用来处理数据的绑定,验证,CSS更新等不实际操作DOM的事情。ng-model  
  6.         //控制器会随着ngModel被一直注入到指令中,其中包含了一些方法,为了访问ngModelController必须使用require设置。注意:这个指令没有隔离作用域,如果给这个指令设置隔离作用域将导致内部ngModel无法更新外部ngModel  
  7.         //对应值:angularjs会在本地作用域以外查询值  
  8.         link : function(scope, elm, attrs, ctrl) {  
  9.             // view -> model更新Model  
  10.             elm.bind('keyup'function() {  
  11.                 scope.$apply(function() {  
  12.                     ctrl.$setViewValue(elm.text());  
  13.                     //为了设置作用域中的视图值,需要调用ngModel.$setViewValue函数  
  14.                 });  
  15.             });  
  16.             // model -> view更新view(控制器中定义这个方法可以定义视图具体的渲染方式)。这个方法会在$parser流水线完成后被调用。但是因为这个方法会破坏ng标准工作方式,因此要谨慎使用  
  17.             ctrl.$render = function() {  
  18.                 elm.html(ctrl.$viewValue);  
  19.                 //$viewValue属性保存着更新视图所需要的实际的字符串。$modelValue和$viewValue可能是相同的,取决于$parser流水线是否对其进行了操作  
  20.             };  
  21.             // load init value from DOM  
  22.             //从DOM中获取初始的值  
  23.             ctrl.$setViewValue(elm.html());  
  24.         }  
  25.     };  
  26. });  
其中link函数第四个参数就是我们的ngModelController,其中包含了$setViewValue,$viewValue,$render等方法。 其中$viewValue保存着更新视图所需要的实际的字符串;而$render方法可以定义视图具体渲染的方式;$setViewValue接受一个参数value,其代表着我们想要赋值给ngModel实例的实际值,这个方法会更新控制器上本地的$viewValue,然后把值传递给每一个$parser函数。当值被解析,且$parser流水线中所有的函数都被调用完成后就会被负值为$modelValue属性,并且传递给指令中ng-model属性提供的表达式。最后,所有的步骤完成后,$viewChangeListeners中所有的监听器就会被调用。 注意:单独调用$setViewValue不会唤起一个新的digest循环,因此如果想要更新指令,需要在设置$viewValue后手动调用digest!

注意:$setViewValue方法适合于在自定义指令中监听自定义事件,如具有回调函数的jQuery插件,我们会希望在回调时候设置$viewValue进行digest循环!
我们再来学习一下下面的例子2:

[html]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <!doctype html>  
  2. <html ng-app="form-example1">  
  3.     <head>  
  4.         <meta http-equiv="content-type" content="text/html; charset=utf-8" />  
  5.         <link rel="stylesheet" href="css/bootstrap-3.0.0/css/bootstrap.css" media="screen">  
  6.         <script src="framework/angular-1.3.0.14/angular.js"></script>  
  7.         <script src="FormValidation.js"></script>  
  8.     </head>  
  9.     <body>  
  10.         <div>  
  11.             <form name="myForm" class="css-form" novalidate>  
  12.             <!--下面是整数-->  
  13.                 <div>  
  14.                     整数(0-10):  
  15.                     <input type="number" ng-model="size" name="size" min="0" max="10" integer/>  
  16.                     {{size}}  
  17.                     <br/>  
  18.                     <span ng-show="myForm.size.$error.integer">不是合法的整数!</span>  
  19.                     <span ng-show="myForm.size.$error.min || myForm.size.$error.max">  
  20.                         数值必须位于0到10之间!  
  21.                     </span>  
  22.                 </div>  
  23.                 <!--下面必须是浮点数-->  
  24.                 <div>  
  25.                     浮点数:  
  26.                     <input type="text" ng-model="length" name="length" smart-float />  
  27.                         {{length}}  
  28.                     <br/>  
  29.                     <span ng-show="myForm.length.$error.float">不是合法的浮点数!</span>  
  30.                 </div>  
  31.                 <!--下面是远程校验-->  
  32.                 <div>  
  33.                     远程校验:  
  34.                     <input type="text" ng-model="remote" name="remote" remote-validation />  
  35.                         {{remote}}  
  36.                     <br/>  
  37.                     <span ng-show="myForm.remote.$error.remote">非法数据!</span>  
  38.                 </div>  
  39.             </form>  
  40.         </div>  
  41.     </body>  
  42. </html>  
这个例子我们可以学习在form中如何实现验证的,如上面例子的myForm.length.$error.float,myForm.remote.$error.remote等。我们再来看看下面的自定义指令部分:

[javascript]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. var app = angular.module('form-example1', []);  
  2. var INTEGER_REGEXP = /^\-?\d*$/;  
  3. //整数正则  
  4. app.directive('integer'function() {  
  5.     return {  
  6.         require : 'ngModel',  
  7.         link : function(scope, elm, attrs, ctrl) {  
  8.             //为parsers这个数组头部添加一个函数  
  9.             ctrl.$parsers.unshift(function(viewValue) {  
  10.                 if (INTEGER_REGEXP.test(viewValue)) {  
  11.                     //调用ngModelController的$setValidity方法  
  12.                     ctrl.$setValidity('integer'true);  
  13.                     return viewValue;  
  14.                     //如果正常就返回viewValue,否则就返回undefined表示不合法  
  15.                 } else {  
  16.                     ctrl.$setValidity('integer'false);  
  17.                     return undefined;  
  18.                 }  
  19.             });  
  20.         }  
  21.     };  
  22. });  
  23. var FLOAT_REGEXP = /^\-?\d+((\.|\,)\d+)?$/;  
  24. app.directive('smartFloat'function() {  
  25.     return {  
  26.         require : 'ngModel',  
  27.         link : function(scope, elm, attrs, ctrl) {  
  28.             ctrl.$parsers.unshift(function(viewValue) {  
  29.                 if (FLOAT_REGEXP.test(viewValue)) {  
  30.                     ctrl.$setValidity('float'true);  
  31.                     //如果满足替换掉逗号返回  
  32.                     return parseFloat(viewValue.replace(',','.'));  
  33.                 } else {  
  34.                     ctrl.$setValidity('float'false);  
  35.                     return undefined;  
  36.                 }  
  37.             });  
  38.         }  
  39.     };  
  40. });  
  41. app.directive('remoteValidation'function($http) {  
  42.     return {  
  43.         require : 'ngModel',  
  44.         link : function(scope, elm, attrs, ctrl) {  
  45.             elm.bind('keyup'function() {  
  46.                 $http({method: 'GET', url: 'FormValidation.jsp'}).  
  47.                 success(function(data, status, headers, config) {  
  48.                     if(parseInt(data)==0){  
  49.                         ctrl.$setValidity('remote',true);  
  50.                     }else{  
  51.                         ctrl.$setValidity('remote',false);  
  52.                     }  
  53.                 }).  
  54.                 error(function(data, status, headers, config) {  
  55.                     ctrl.$setValidity('remote'false);  
  56.                 });  
  57.             });  
  58.         }  
  59.     };  
  60. });  
我们可以看到这里使用了ngModelController的$parsers集合 (ngModel从DOM中读取的值会被传入到$parsers函数,并依次调用其中的解析器,这是为了对值进行处理和修饰) ,同时把验证函数放入到这个集合中, 这个验证函数唯一的参数就是指令所有的元素的输入内容 !同时我们可以学习到这里使用了$setValidity方法,如下面这种方式:

[javascript]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. ctrl.$setValidity('integer'true);  
我们看看在页面中是如何判断是否显示相应的提示信息的:

[html]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <span ng-show="myForm.size.$error.integer">不是合法的整数!</span>  

很显然每一个form中的元素都具有一个$error对象,保存着没有通过验证的验证器名称以及对应的错误信息。从顶部图片中,我们可以看到$viewChangeListeners保存的是由函数组成的数组,通过$viewChangeListeners可以在无需使用$watch的情况下实现类似的行为。

如果要深入理解ngModleController,建议阅读在Angular指令中使用NgModelController做数据绑定一文,其仔细论述了$modelValue,$viewValue,$parsers,$formatters等知识。详细内容见下图:


1.在外部控制器中(即这里的HelloApp的controller),我们通过ng-model="test"将test变量传入指令time-duration中,并建立绑定关系。

[html]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <time-duration ng-model="test"></time-duration>  
2.在指令内部,$modelValue其实就是test值的一份拷贝。

[javascript]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. angular.module('HelloApp').controller('HelloController'function($scope) {  
  2.     $scope.test = 1;  
  3. });  
3.我们通过$formatters()方法将$modelValue转变成$viewValue。

[javascript]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. ngModelCtrl.$formatters.push(function(modelValue) {  
  2.               var unit = 'minutes', num = 0, i, unitName;  
  3.               modelValue = parseInt(modelValue || 0);  
  4.               // Figure out the largest unit of time the model value  
  5.               // fits into. For example, 3600 is 1 hour, but 1800 is 30 minutes.  
  6.               //从天->小时->分钟等进行转换,获取modelview适合的最大的时间单位  
  7.               for (i = multiplierTypes.length-1; i >= 0; i--) {  
  8.                   unitName = multiplierTypes[i];  
  9.                   //获取单位名称['seconds', 'minutes', 'hours', 'days']  
  10.                   if (modelValue % multiplierMap[unitName] === 0) {  
  11.                       unit = unitName;  
  12.                       break;  
  13.                   }  
  14.               }  
  15.              //获取如多少天,多少小时,多少分钟等  
  16.               if (modelValue) {  
  17.                   num = modelValue / multiplierMap[unit]  
  18.               }  
  19.               //返回一个对象,有最大的时间单位名称+最大时间单位数量  
  20.               return {  
  21.                   unit: unit,  
  22.                   num:  num  
  23.               };  
  24.           });  
4.然后调用$render()方法将$viewValue渲染到directive template中。

[javascript]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. // $render用于将viewValue渲染到指令的模板中  
  2.          ngModelCtrl.$render = function() {  
  3.              scope.unit = ngModelCtrl.$viewValue.unit;  
  4.              scope.num  = ngModelCtrl.$viewValue.num;  
  5.          };  
  6.      }  
5.当我们通过某种途径监控到指令模板中的变量发生变化之后,我们调用$setViewValue()来更新$viewValue。(当我们修改了数字或者单位的时候就会调用$setViewValue)

[javascript]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. //$watch是一个scope函数,用于监听模型变化,当你的模型部分发生变化时它会通知你。  
  2.         //$watch(watchExpression, listener, objectEquality);  
  3.         //具体可以参考:http://yuankeqiang.lofter.com/post/8de51_1454f93  
  4.          scope.$watch('unit + num'function() {  
  5.                       // 如果当数据模型unit和num发生变化后,通过$setViewValue用于更新viewValue  
  6.              ngModelCtrl.$setViewValue({ unit: scope.unit, num: scope.num });  
  7.          });  
6.与(4)相对应,我们通过$parsers方法将$viewValue转化成$modelValue。

[javascript]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. // $parsers接受一个数组,数组是一系列方法,用于将viewValue转化成modelValue  
  2.         ngModelCtrl.$parsers.push(function(viewValue) {  
  3.             var unit = viewValue.unit, num = viewValue.num, multiplier;  
  4.             multiplier = multiplierMap[unit];  
  5.             //获取相乘后的结果数  
  6.             return num * multiplier;  
  7.         });  
7.当$modelValue发生变化后,则会去更新HelloApp的UI。所有整个内容如下:

[javascript]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. angular.module('HelloApp', []);  
  2. //这里是模块名称  
  3. function TimeDurationDirective() {  
  4.   var tpl = "\  
  5.          <div class='time-duration'> \  
  6.        <input type='text' ng-model='num' /> \  
  7.        <select ng-model='unit'> \  
  8.          <option value='seconds'>Seconds</option> \  
  9.          <option value='minutes'>Minutes</option> \  
  10.          <option value='hours'>Hours</option> \  
  11.          <option value='days'>Days</option> \  
  12.        </select> \  
  13.      </div>";  
  14.   return {  
  15.       restrict: 'E',  
  16.       template: tpl,  
  17.       require: 'ngModel',//timeDuration指令依赖于ng-model指令  
  18.       replace: true,//实现替换time-duration  
  19.       scope: {}, // 这个指令有一个独立的作用域对象,也就是有一个独立的scope对象  
  20.       link: function(scope, iElement, iAttrs, ngModelCtrl) {  
  21.           var multiplierMap = {seconds: 1, minutes: 60, hours: 3600, days: 86400};  
  22.           var multiplierTypes = ['seconds''minutes''hours''days']  
  23.                     // $formatters接受一个数组,数组是一系列方法,用于将modelValue转化成viewValue  
  24.           ngModelCtrl.$formatters.push(function(modelValue) {  
  25.               var unit = 'minutes', num = 0, i, unitName;  
  26.               modelValue = parseInt(modelValue || 0);  
  27.               // Figure out the largest unit of time the model value  
  28.               // fits into. For example, 3600 is 1 hour, but 1800 is 30 minutes.  
  29.               //从天->小时->分钟等进行转换,获取modelview适合的最大的时间单位  
  30.               for (i = multiplierTypes.length-1; i >= 0; i--) {  
  31.                   unitName = multiplierTypes[i];  
  32.                   //获取单位名称['seconds', 'minutes', 'hours', 'days']  
  33.                   if (modelValue % multiplierMap[unitName] === 0) {  
  34.                       unit = unitName;  
  35.                       break;  
  36.                   }  
  37.               }  
  38.              //获取如多少天,多少小时,多少分钟等  
  39.               if (modelValue) {  
  40.                   num = modelValue / multiplierMap[unit]  
  41.               }  
  42.               //返回一个对象,有最大的时间单位名称+最大时间单位数量  
  43.               return {  
  44.                   unit: unit,  
  45.                   num:  num  
  46.               };  
  47.           });  
  48.                   // $parsers接受一个数组,数组是一系列方法,用于将viewValue转化成modelValue  
  49.           ngModelCtrl.$parsers.push(function(viewValue) {  
  50.               var unit = viewValue.unit, num = viewValue.num, multiplier;  
  51.               multiplier = multiplierMap[unit];  
  52.               //获取相乘后的结果数  
  53.               return num * multiplier;  
  54.           });  
  55.          //$watch是一个scope函数,用于监听模型变化,当你的模型部分发生变化时它会通知你。  
  56.          //$watch(watchExpression, listener, objectEquality);  
  57.          //具体可以参考:http://yuankeqiang.lofter.com/post/8de51_1454f93  
  58.           scope.$watch('unit + num'function() {  
  59.                           // 如果当数据模型unit和num发生变化后,通过$setViewValue用于更新viewValue  
  60.               ngModelCtrl.$setViewValue({ unit: scope.unit, num: scope.num });  
  61.           });  
  62.   
  63.                   // $render用于将viewValue渲染到指令的模板中  
  64.           ngModelCtrl.$render = function() {  
  65.               scope.unit = ngModelCtrl.$viewValue.unit;  
  66.               scope.num  = ngModelCtrl.$viewValue.num;  
  67.           };  
  68.       }  
  69.   };  
  70. };  
  71. //这里定义了一个指令timeDuration  
  72. angular.module('HelloApp').directive('timeDuration', TimeDurationDirective);  
  73. //这里定义了一个控制器HelloController  
  74. angular.module('HelloApp').controller('HelloController'function($scope) {  
  75.     $scope.test = 1;  
  76. });  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值