我们首先看看ngModelController内部的签名是怎么样的?
我们首先看看下面的例子1:
- <!doctype html>
- <html ng-app="form-example2">
- <head>
- <link href="../bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen">
- <script src="framework/angular-1.3.0.14/angular.js"></script>
- <script src="FormCustom.js"></script>
- <style type="text/css">
- div[contentEditable] {
- cursor: pointer;
- background-color: #D0D0D0;
- }
- </style>
- </head>
- <body>
- <div>
- <!--这里的contentEditable就是我们自定义的指令-->
- <div contentEditable="true" ng-model="content" title="Click to edit">Some</div>
- <pre>model = {{content}}</pre>
- </div>
- </body>
- </html>
- angular.module('form-example2', []).directive('contenteditable', function() {
- //我们定义了一个指令contenteditable
- return {
- require : 'ngModel',
- //ng-model指令是一个特殊的指令,他提供更底层的API来处理控制器中的数据,当我们在指令中使用ng-model时候可以访问一个特殊的API,这个API用来处理数据的绑定,验证,CSS更新等不实际操作DOM的事情。ng-model
- //控制器会随着ngModel被一直注入到指令中,其中包含了一些方法,为了访问ngModelController必须使用require设置。注意:这个指令没有隔离作用域,如果给这个指令设置隔离作用域将导致内部ngModel无法更新外部ngModel
- //对应值:angularjs会在本地作用域以外查询值
- link : function(scope, elm, attrs, ctrl) {
- // view -> model更新Model
- elm.bind('keyup', function() {
- scope.$apply(function() {
- ctrl.$setViewValue(elm.text());
- //为了设置作用域中的视图值,需要调用ngModel.$setViewValue函数
- });
- });
- // model -> view更新view(控制器中定义这个方法可以定义视图具体的渲染方式)。这个方法会在$parser流水线完成后被调用。但是因为这个方法会破坏ng标准工作方式,因此要谨慎使用
- ctrl.$render = function() {
- elm.html(ctrl.$viewValue);
- //$viewValue属性保存着更新视图所需要的实际的字符串。$modelValue和$viewValue可能是相同的,取决于$parser流水线是否对其进行了操作
- };
- // load init value from DOM
- //从DOM中获取初始的值
- ctrl.$setViewValue(elm.html());
- }
- };
- });
注意:$setViewValue方法适合于在自定义指令中监听自定义事件,如具有回调函数的jQuery插件,我们会希望在回调时候设置$viewValue进行digest循环!
我们再来学习一下下面的例子2:
- <!doctype html>
- <html ng-app="form-example1">
- <head>
- <meta http-equiv="content-type" content="text/html; charset=utf-8" />
- <link rel="stylesheet" href="css/bootstrap-3.0.0/css/bootstrap.css" media="screen">
- <script src="framework/angular-1.3.0.14/angular.js"></script>
- <script src="FormValidation.js"></script>
- </head>
- <body>
- <div>
- <form name="myForm" class="css-form" novalidate>
- <!--下面是整数-->
- <div>
- 整数(0-10):
- <input type="number" ng-model="size" name="size" min="0" max="10" integer/>
- {{size}}
- <br/>
- <span ng-show="myForm.size.$error.integer">不是合法的整数!</span>
- <span ng-show="myForm.size.$error.min || myForm.size.$error.max">
- 数值必须位于0到10之间!
- </span>
- </div>
- <!--下面必须是浮点数-->
- <div>
- 浮点数:
- <input type="text" ng-model="length" name="length" smart-float />
- {{length}}
- <br/>
- <span ng-show="myForm.length.$error.float">不是合法的浮点数!</span>
- </div>
- <!--下面是远程校验-->
- <div>
- 远程校验:
- <input type="text" ng-model="remote" name="remote" remote-validation />
- {{remote}}
- <br/>
- <span ng-show="myForm.remote.$error.remote">非法数据!</span>
- </div>
- </form>
- </div>
- </body>
- </html>
- var app = angular.module('form-example1', []);
- var INTEGER_REGEXP = /^\-?\d*$/;
- //整数正则
- app.directive('integer', function() {
- return {
- require : 'ngModel',
- link : function(scope, elm, attrs, ctrl) {
- //为parsers这个数组头部添加一个函数
- ctrl.$parsers.unshift(function(viewValue) {
- if (INTEGER_REGEXP.test(viewValue)) {
- //调用ngModelController的$setValidity方法
- ctrl.$setValidity('integer', true);
- return viewValue;
- //如果正常就返回viewValue,否则就返回undefined表示不合法
- } else {
- ctrl.$setValidity('integer', false);
- return undefined;
- }
- });
- }
- };
- });
- var FLOAT_REGEXP = /^\-?\d+((\.|\,)\d+)?$/;
- app.directive('smartFloat', function() {
- return {
- require : 'ngModel',
- link : function(scope, elm, attrs, ctrl) {
- ctrl.$parsers.unshift(function(viewValue) {
- if (FLOAT_REGEXP.test(viewValue)) {
- ctrl.$setValidity('float', true);
- //如果满足替换掉逗号返回
- return parseFloat(viewValue.replace(',','.'));
- } else {
- ctrl.$setValidity('float', false);
- return undefined;
- }
- });
- }
- };
- });
- app.directive('remoteValidation', function($http) {
- return {
- require : 'ngModel',
- link : function(scope, elm, attrs, ctrl) {
- elm.bind('keyup', function() {
- $http({method: 'GET', url: 'FormValidation.jsp'}).
- success(function(data, status, headers, config) {
- if(parseInt(data)==0){
- ctrl.$setValidity('remote',true);
- }else{
- ctrl.$setValidity('remote',false);
- }
- }).
- error(function(data, status, headers, config) {
- ctrl.$setValidity('remote', false);
- });
- });
- }
- };
- });
- ctrl.$setValidity('integer', true);
- <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中,并建立绑定关系。
- <time-duration ng-model="test"></time-duration>
- angular.module('HelloApp').controller('HelloController', function($scope) {
- $scope.test = 1;
- });
- ngModelCtrl.$formatters.push(function(modelValue) {
- var unit = 'minutes', num = 0, i, unitName;
- modelValue = parseInt(modelValue || 0);
- // Figure out the largest unit of time the model value
- // fits into. For example, 3600 is 1 hour, but 1800 is 30 minutes.
- //从天->小时->分钟等进行转换,获取modelview适合的最大的时间单位
- for (i = multiplierTypes.length-1; i >= 0; i--) {
- unitName = multiplierTypes[i];
- //获取单位名称['seconds', 'minutes', 'hours', 'days']
- if (modelValue % multiplierMap[unitName] === 0) {
- unit = unitName;
- break;
- }
- }
- //获取如多少天,多少小时,多少分钟等
- if (modelValue) {
- num = modelValue / multiplierMap[unit]
- }
- //返回一个对象,有最大的时间单位名称+最大时间单位数量
- return {
- unit: unit,
- num: num
- };
- });
- // $render用于将viewValue渲染到指令的模板中
- ngModelCtrl.$render = function() {
- scope.unit = ngModelCtrl.$viewValue.unit;
- scope.num = ngModelCtrl.$viewValue.num;
- };
- }
- //$watch是一个scope函数,用于监听模型变化,当你的模型部分发生变化时它会通知你。
- //$watch(watchExpression, listener, objectEquality);
- //具体可以参考:http://yuankeqiang.lofter.com/post/8de51_1454f93
- scope.$watch('unit + num', function() {
- // 如果当数据模型unit和num发生变化后,通过$setViewValue用于更新viewValue
- ngModelCtrl.$setViewValue({ unit: scope.unit, num: scope.num });
- });
- // $parsers接受一个数组,数组是一系列方法,用于将viewValue转化成modelValue
- ngModelCtrl.$parsers.push(function(viewValue) {
- var unit = viewValue.unit, num = viewValue.num, multiplier;
- multiplier = multiplierMap[unit];
- //获取相乘后的结果数
- return num * multiplier;
- });
- angular.module('HelloApp', []);
- //这里是模块名称
- function TimeDurationDirective() {
- var tpl = "\
- <div class='time-duration'> \
- <input type='text' ng-model='num' /> \
- <select ng-model='unit'> \
- <option value='seconds'>Seconds</option> \
- <option value='minutes'>Minutes</option> \
- <option value='hours'>Hours</option> \
- <option value='days'>Days</option> \
- </select> \
- </div>";
- return {
- restrict: 'E',
- template: tpl,
- require: 'ngModel',//timeDuration指令依赖于ng-model指令
- replace: true,//实现替换time-duration
- scope: {}, // 这个指令有一个独立的作用域对象,也就是有一个独立的scope对象
- link: function(scope, iElement, iAttrs, ngModelCtrl) {
- var multiplierMap = {seconds: 1, minutes: 60, hours: 3600, days: 86400};
- var multiplierTypes = ['seconds', 'minutes', 'hours', 'days']
- // $formatters接受一个数组,数组是一系列方法,用于将modelValue转化成viewValue
- ngModelCtrl.$formatters.push(function(modelValue) {
- var unit = 'minutes', num = 0, i, unitName;
- modelValue = parseInt(modelValue || 0);
- // Figure out the largest unit of time the model value
- // fits into. For example, 3600 is 1 hour, but 1800 is 30 minutes.
- //从天->小时->分钟等进行转换,获取modelview适合的最大的时间单位
- for (i = multiplierTypes.length-1; i >= 0; i--) {
- unitName = multiplierTypes[i];
- //获取单位名称['seconds', 'minutes', 'hours', 'days']
- if (modelValue % multiplierMap[unitName] === 0) {
- unit = unitName;
- break;
- }
- }
- //获取如多少天,多少小时,多少分钟等
- if (modelValue) {
- num = modelValue / multiplierMap[unit]
- }
- //返回一个对象,有最大的时间单位名称+最大时间单位数量
- return {
- unit: unit,
- num: num
- };
- });
- // $parsers接受一个数组,数组是一系列方法,用于将viewValue转化成modelValue
- ngModelCtrl.$parsers.push(function(viewValue) {
- var unit = viewValue.unit, num = viewValue.num, multiplier;
- multiplier = multiplierMap[unit];
- //获取相乘后的结果数
- return num * multiplier;
- });
- //$watch是一个scope函数,用于监听模型变化,当你的模型部分发生变化时它会通知你。
- //$watch(watchExpression, listener, objectEquality);
- //具体可以参考:http://yuankeqiang.lofter.com/post/8de51_1454f93
- scope.$watch('unit + num', function() {
- // 如果当数据模型unit和num发生变化后,通过$setViewValue用于更新viewValue
- ngModelCtrl.$setViewValue({ unit: scope.unit, num: scope.num });
- });
- // $render用于将viewValue渲染到指令的模板中
- ngModelCtrl.$render = function() {
- scope.unit = ngModelCtrl.$viewValue.unit;
- scope.num = ngModelCtrl.$viewValue.num;
- };
- }
- };
- };
- //这里定义了一个指令timeDuration
- angular.module('HelloApp').directive('timeDuration', TimeDurationDirective);
- //这里定义了一个控制器HelloController
- angular.module('HelloApp').controller('HelloController', function($scope) {
- $scope.test = 1;
- });