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中的prototype与继承
Apr 14 Javascript
Javascript核心读书有感之语句
Feb 11 Javascript
js实现三张图(文)片一起切换的banner焦点图
Aug 25 Javascript
JQuery+EasyUI轻松实现步骤条效果
Feb 22 Javascript
一句jQuery代码实现返回顶部效果(简单实用)
Dec 28 Javascript
webpack进阶——缓存与独立打包的用法
Aug 02 Javascript
React-Native使用Mobx实现购物车功能
Sep 14 Javascript
AngularJS中的路由使用及实现代码
Oct 09 Javascript
react中使用swiper的具体方法
May 15 Javascript
vue 使用axios 数据请求第三方插件的使用教程详解
Jul 05 Javascript
JavaScript高级程序设计之变量与作用域
Nov 17 Javascript
VUE使用draggable实现组件拖拽
Apr 06 Vue.js
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
PHP中__get()和__set()的用法实例详解
2013/06/04 PHP
PHP+iFrame实现页面无需刷新的异步文件上传
2014/09/16 PHP
PHP二维关联数组的遍历方式(实例讲解)
2017/10/18 PHP
Laravel程序架构设计思路之使用动作类
2018/06/07 PHP
PHP实现的日历功能示例
2018/09/01 PHP
PHP使用PDO操作sqlite数据库应用案例
2019/03/07 PHP
JS 页面内容搜索,类似于 Ctrl+F功能的实现代码
2007/08/13 Javascript
Jquery 学习笔记(一)
2009/10/13 Javascript
AJAX的跨域与JSONP(为文章自动添加短址的功能)
2010/01/17 Javascript
原创javascript小游戏实现代码
2010/08/19 Javascript
基于jquery的bankInput银行卡账号格式化
2012/08/22 Javascript
JQuery获取表格数据示例代码
2014/05/26 Javascript
JavaScript lastIndexOf方法入门实例(计算指定字符在字符串中最后一次出现的位置)
2014/10/17 Javascript
angularJS 入门基础
2015/02/09 Javascript
jquery点击改变class并toggle的实现代码
2016/05/15 Javascript
JQuery实现动态操作表格
2017/01/11 Javascript
Vue表单验证插件的制作过程
2017/04/01 Javascript
微信小程序 下拉菜单简单实例
2017/04/13 Javascript
JQuery实现定时刷新功能代码
2017/05/09 jQuery
学习使用Bootstrap输入框、导航、分页等常用组件
2017/05/11 Javascript
vue实现自定义H5视频播放器的方法步骤
2019/07/01 Javascript
在vue中使用echarts(折线图的demo,markline用法)
2020/07/20 Javascript
Vue 中使用lodash对事件进行防抖和节流操作
2020/07/26 Javascript
vue 项目@change多个参数传值多个事件的操作
2021/01/29 Vue.js
[01:12:08]LGD vs OG 2019国际邀请赛淘汰赛 胜者组 BO3 第一场 8.24
2019/09/10 DOTA
python连接MySQL、MongoDB、Redis、memcache等数据库的方法
2013/11/15 Python
Python3中常用的处理时间和实现定时任务的方法的介绍
2015/04/07 Python
Python实现随机取一个矩阵数组的某几行
2019/11/26 Python
智能电子秤、手表和健康监测仪:Withings(之前为诺基亚健康)
2018/10/30 全球购物
德国最大的服装、鞋子和配件在线商店之一:Outfits24
2019/07/23 全球购物
小学教育毕业生自荐信
2013/11/18 职场文书
历史教育专业个人求职信
2013/12/13 职场文书
2015年预备党员自我评价
2015/03/04 职场文书
最新农村养殖致富:资金投入较低的创业项目有哪些?
2019/09/26 职场文书
POST提交数据常见的四种方式
2022/01/18 HTML / CSS
Nginx静态压缩和代码压缩提高访问速度详解
2022/05/30 Servers