AngularJS实践之使用NgModelController进行数据绑定


Posted in Javascript onOctober 08, 2016

前言

在Angular应用中,ng-model指令时不可缺少的一个部分,它用来将视图绑定到数据,是双向绑定魔法中重要的一环。ngModelController则是ng-model指令中所定义的controller。这个controller包含了一些用于数据绑定,验证,CSS更新,以及数值格式化和解析的服务。它不用来进行DOM渲染或者监听DOM事件。与DOM相关的逻辑都应该包含在其他的指令中,然后让这些指令来试用ngModelController中的数据绑定功能。

注意:本篇文章不是对NgModelController文档的说明,而是更偏向实践。下面我将全程带领大家去实现一个自定义指令,并且利用ng-model属性来做双方的数据绑定。

示例

我们的app中使用了一个自定义的指令,名字叫做timeDruation

如下

<div ng-app="HelloApp" ng-controller="HelloController">
 <h1>自定义指令</h1>
 <time-duration ng-model="test"></time-duration>
 <h1>默认指令</h1>
 <input ng-model="test">second
</div>

JS代码如下,

angular.module('HelloApp', [])
 .directive('timeDuration', TimeDurationDirective);
 .controller('HelloController', function($scope) {
 $scope.test = 1;
 });

我们的示例指令可以做这样一件事,可以指定几个常见的时间单位,并且能够输入数据。最终我们将得到对应的秒数。其功能的截图如下,

AngularJS实践之使用NgModelController进行数据绑定

这里我们特意将test变量分别绑定到我们的自定义指令和默认指令中,以观察其效果。

自定义指令

闲话少叙,下面来看代码

先上指令的模板。从上图中可以看出,指令包含一个输入框一个下拉选择框。

<div class="time-duration">
 <input 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>

模板其实很简单,这里就不多说了。下面我们来看看这个指令的逻辑部分。

function TimeDurationDirective() {
 var tpl = '....'; // 指令模板代码就是上面的内容,这里就不复制了。
 
 return {
 restrict: 'E',
 replace: true,
 template: tpl,
 require: 'ngModel',
 scope: {},
 link: function(scope, element, attrs, ngModelController) {
  var multiplierMap = {
  seconds: 1,
  minutes: 60,
  hours: 3600,
  days: 86400
  };
  var multiplierTypes = ['seconds', 'minutes', 'hours', 'days'];

  // TODO
 }
 };
}

指令的link方法我们暂时TODO了它。后面会逐步完善。

我先来看看这个指令的定义,其中用到了require声明。简单来说,require的作用就是为这个directive声明一个依赖关系,表明此directive依赖另一个指令的controller属性。

这里稍微说明一下require的衍生用法。

我们可以在require前加上修辞量词,比如,

return {
 require: '^ngModel'
}

return {
 require: '?ngModel'
}

return {
 require: '?^ngModel'
}

     1、^前缀修饰表示允许查找当前指令的父级指令,如果找不到对应指令的controller则抛出一个错误。

     2、?则表示将这个require动作变成一个可选项,意思就是找不到对应指令的controller就算了,不会抛出错误。

     3、当然,我们也可以联合使用这两个前缀修饰。

相对?ngModel,^ngModel我们使用的频率要更加高一点。

比如

<my-directive ng-model="my-model">
 <other-directive></other-directive>
</my-directive>

这时,我们在other-directive中使用require: ^ngModel,它将会自动查找my-directive指令声明中的controller属性。

使用NgModelController

当我们声明了require: 'ngModel'之后,在link方法中会注入第四个参数,这个参数就是我们require的那个指令对应的controller。这里就是内置指令ngModel的指控器ngModeController了。

link: function (scope, element, attrs, ngModelCtrl) {
 // TODO
}

$viewValue和$modelValue

在ngModelController中有两个很重要的属性,一个叫做$viewValue,一个叫做$modeValue。

这两者的含义官方的解释如下

     $viewValue: Actual string value in the view.

     $modelValue: The value in the model, that the control is bound to.

如果你对上面的官方解释有疑惑的话,我这里给出一种我个人的解释。

$viewView就是指令渲染模板所用的值,而$modelView是在控制器中流通的值。很多时候,这两个值可能是不一样的。

比如你在页面上展示了一个日期,它显示的可能是“Oct. 20 2015”这样的字符串,但是呢,这个字符串在控制器中对应的值可能是一个Javascript的Date对象的实例。

再比如,我们的这个time-duration示例中,$viewValue其实指的是指令模板中num和unit组合出来的值,而$modelValue是HelloAppController中test变量对应的值。

$formatters和$parses

除了$viewValue和$modelValue这两个属性之外,还有两个用来处理他们的方法。分别是$parses和$formatters。

前者的是作用是将$viewValue->$modelValue,后者的作用恰好相反,是将$modelValue->$viewValue

time-duration指令与外部控制器以及其内部的运作如下图,

AngularJS实践之使用NgModelController进行数据绑定

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

     2、在指令内部,$modelValue其实就是test值的一份拷贝。

     3、我们通过$formatters()方法将$modelValue转变成$viewValue。

     4、然后调用$render()方法将$viewValue渲染到directive template中。

     5、当我们通过某种途径监控到指令模板中的变量发生变化之后,我们调用$setViewValue()来更新$viewValue。

     6、与(4)相对应,我们通过$parsers方法将$viewValue转化成$modelValue。

     7、当$modelValue发生变化后,则会去更新HelloApp的UI。

完善指令逻辑

按照上面的流程,我们先来将$modelValue转化成$viewValue,然后在指令模板中进行渲染。

// $formatters接受一个数组
// 数组是一系列方法,用于将modelValue转化成viewValue
ngModelController.$formatters.push(function(modelValue) {
 var unit = 'minutes', num = 0, i, unitName;
 modelValue = parseInt(modelValue || 0);
 
 for (i = multiplierTypes.length-1; i >= 0; i--) {
 unitName = multiplierTypes[i];

 if (modelValue % multiplierMap[unitName] === 0) {
  unit = unitName;
  break;
 }
 }
 
 if (modelValue) {
 num = modelValue / multiplierMap[unit];
 }

 return {
 unit: unit,
 num: num
 };
});

最后返回的对象就是$viewValue的value。(当然$viewValue还会有其他的一些属性。)

第二步,我们调用$render方法将$viewValue渲染到指令模板中去。

// $render用于将viewValue渲染到指令的模板中
ngModelController.$render = function() {
 scope.unit = ngModelCtrl.$viewValue.unit;
 scope.num = ngModelCtrl.$viewValue.num;
};

第三步,我们通过$watch来监控指令模板中num和unit变量。当其发生变化时,我们需要更新$viewValue。

scope.$watch('unit + num', function() {
// $setViewValue用于更新viewValue
 ngModelController.$setViewValue({
 unit: scope.unit,
 num: scope.num
 });
});

第四步,我们通过$parsers将$viewValue->$modelValue。

// $parsers接受一个数组
// 数组是一系列方法,用于将viewValue转化成modelValue
ngModelController.$parsers.push(function(viewValue) {
 var unit = viewValue.unit;
 var num = viewValue.num;
 var multiplier;

 multiplier = multiplierMap[unit];

 return num * multiplier;
});

总结

好了,到这一个双方的数据绑定逻辑就建立了。不知道大家都学会了吗?希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流。

Javascript 相关文章推荐
使用Javascript和DOM Interfaces来处理HTML
Oct 09 Javascript
jqPlot 图表中文API使用文档及源码和在线示例
Feb 07 Javascript
JSON.parse()和JSON.stringify()使用介绍
Jun 20 Javascript
JavaScript中document对象使用详解
Jan 06 Javascript
javascript实现博客园页面右下角返回顶部按钮
Feb 22 Javascript
jQuery form插件之formDdata参数校验表单及验证后提交
Jan 23 Javascript
JS实现漂亮的时间选择框效果
Aug 20 Javascript
angular-ui-sortable实现可拖拽排序列表
Dec 28 Javascript
基于Angular中ng-controller父子级嵌套的相关属性详解
Oct 08 Javascript
详解vue服务端渲染浏览器端缓存(keep-alive)
Oct 12 Javascript
element-ui组件中input等的change事件中传递自定义参数
May 22 Javascript
JavaScript实时更新当前的时间的示例代码
Jul 15 Javascript
Bootstrap Navbar Component实现响应式导航
Oct 08 #Javascript
微信小程序 WXML、WXSS 和JS介绍及详解
Oct 08 #Javascript
JS中使用mailto实现将用户在网页中输入的内容传递到本地邮件客户端
Oct 08 #Javascript
Javascript单例模式的介绍和实例
Oct 08 #Javascript
jquery把int类型转换成字符串类型的方法
Oct 07 #Javascript
jquery判断类型是不是number类型的实例代码
Oct 07 #Javascript
js判断是否为空和typeof的用法(详解)
Oct 07 #Javascript
You might like
《DOTA3》开发工作已经开始 《DOTA3》将代替《DOTA2》
2021/03/06 DOTA
学习使用PHP数组
2006/10/09 PHP
PHP实现UTF-8文件BOM自动检测与移除实例
2014/11/05 PHP
WordPress中用于更新伪静态规则的PHP代码实例讲解
2015/12/18 PHP
ThinkPHP框架获取最后一次执行SQL语句及变量调试简单操作示例
2018/06/13 PHP
基于JQuery的cookie插件
2010/04/07 Javascript
Jquery.TreeView结合ASP.Net和数据库生成菜单导航条
2010/08/27 Javascript
Javascript级联下拉菜单以及AJAX数据验证核心代码
2013/05/10 Javascript
nodejs教程 安装express及配置app.js文件的详细步骤
2013/05/11 NodeJs
js之事件冒泡和事件捕获详细介绍
2013/10/28 Javascript
jQuery EasyUI实现右键菜单变灰不可用效果
2015/09/24 Javascript
jQuery可见性过滤选择器用法示例
2016/09/09 Javascript
提高Web性能的前端优化技巧总结
2017/02/27 Javascript
JavaScript Uploadify文件上传实例
2017/02/28 Javascript
基于vue实现分页/翻页组件paginator示例
2017/03/09 Javascript
nodeJS(express4.x)+vue(vue-cli)构建前后端分离实例(带跨域)
2017/07/05 NodeJs
使用AngularJS编写多选按钮选中时触发指定方法的指令代码详解
2017/07/24 Javascript
关于redux-saga中take使用方法详解
2018/02/27 Javascript
jQuery 导航自动跟随滚动的实现代码
2018/05/30 jQuery
weui上传多图片,压缩,base64编码的示例代码
2020/06/22 Javascript
Python运算符重载用法实例分析
2015/06/01 Python
python 统计代码行数简单实例
2017/05/04 Python
在python win系统下 打开TXT文件的实例
2018/04/29 Python
Series和DataFrame使用简单入门
2019/11/13 Python
Python数组并集交集补集代码实例
2020/02/18 Python
Python基于requests库爬取网站信息
2020/03/02 Python
解决IDEA 的 plugins 搜不到任何的插件问题
2020/05/04 Python
Book Depository美国:全球领先的专业网上书店之一
2019/08/14 全球购物
主要的Ajax框架都有什么
2013/11/14 面试题
机械制造专业个人的自我评价
2013/12/28 职场文书
超市促销活动总结
2014/07/01 职场文书
关于学习的决心书
2015/02/05 职场文书
交通事故调解协议书
2015/05/20 职场文书
工作简报怎么写
2015/07/21 职场文书
学生病假条怎么写
2015/08/17 职场文书
2016年大学生社区服务活动总结
2016/04/06 职场文书