如何在AngularJS指令中创建controller与ngModel进行交互
推荐于2016-10-25 · 知道合伙人数码行家
知道合伙人数码行家
向TA提问 私信TA
正如很多文章所说的一样,指令是AngularJS的灵魂,只有真正熟练掌握了Angular 指令,才有希望成为AngularJS高手。在编写Angular指令时,我们最常用到的就是其中的link函数,它用来与scope中的数据进行交互并绑定一些事件,例如click等等。但是如果我们想要编写一些高级的指令,例如要在指令之间进行相互交流的指令,我们就必须用到Angular指令中的controller。
指令中的controller是一个难点,我们也很少会用到它。一般来说,我们最常用到需要进行交互的指令就是ngModel。如果你想要创建一个自定义input,或者甚至是想绑定一个需要制定格式的input插件,ngModel都会为你提供你所需要的方法,它会帮助你在你的数据模型和插件之间进行交互。
在本文中,我们将一步一步学着创建一个用来输入时间值的指令。我们的目标是能够将一个普通的文本输入字段变成一个能够以HH:mm格式显示时间的时间选择器,但是将它以毫秒为单位存储在我们的数据属性中。
对于这个例子,我们的HTML非常简单,如下所示:
<input type='text' ng-model='timeOfDay' time-picker/>
显然,我们需要在主页中包含一个timePicker插件以便于它能够被绑定在我们的input上。对于这个指令,我们首先来进行一个最基本的定义,由于我们需要数据绑定功能,我们将从一开始就require ngModel:
.directive('timePicker',function(){
var today = new Date(new Date().toString());
return {
require: '?ngModel',
link: function($scope,$element,$attrs,ngModel){
}
}
});
注意到我们需要涉及元素的实际实例,因此我们所有的代码基本上都位于linking函数中,我们在此完全不需要担心编译过程。我们同事也实例化了一个today变量来保存一个Date对象,以此在午夜十分更新。创建这个变量作为factory函数的一部分允许我们在多个实例之间分享一个today变量。注意,尽管这允许我们将内存的使用最小化,它也意味着如果我们的应用在半夜还依然再运行,我们的指令会开始提供不准确的结果。如果你打算将这个指令用于一个实时应用中,你最好创建另一个函数在第二天来临时更新这个值。现在,我们急需要看我们的controller函数:
link: function($scope.$element,$attrs,ngModel){
ngModel = ngModel || {
"$setViewValue" : angular.noop
}
}
你以前可能也注意过这种模式。一定要记住我们让我们的controller有进行选择性的需求,因此如果有人想要使用我们的指令来绑定一个timepicker,而不需要由ngModel提供一个数据绑定,当指令没找到一个请求的controller时它也不会抛出一个错误。在这里,如果ngModel被定义了,我们就使用它,否则,我们就使用自定义的那个对象,其中有一个noop函数。于是当我们在指令中调用ngModel.$setViewValue时,如果没有ng-Model绑定到我们的节点上,我们的代码会继续执行。
说到ngModel.$setViewValue,我们来看看我们如何绑定我们的time picker,以及我们在什么地方会需要这个函数:
link: function($scope,$element,$attrs,ngModel){
..
var initialized = false;
setTimeout(function(){
initialized = $element.timepicker()
.on('changeTime',function(ev,ui){
var sec = $element.timepicker('getSecondsFrimMidnight');
ngModel.$setViewValue(sec * 1000);
});
});
}
毫无疑问的,你的第一个问题肯定是关于setTimeout的,因为它其中居然没有实际的延时数字。因为我们处于linking函数中,我们的$element是完全被实例化的,因此这种小技巧是没有必要的。但是我们还是推荐使用这种办法。
现在我们来看看我们如何使用$setViewValue。因为我们之前已经获得了一个分享的ngModel的实例,我们现在可以在我们的指令中调用$setViewValue函数,它将帮助我们链接我们的插件和数据模型。记住这是用来接受显示值的,它可以进行任何必要的解析,然后将它储存在数据属性中。timepicker插件将会发送一个changeTime时间,只要用户更新了在我们的input中显示的时间,因此我们使用它来了解应该何时改变内部值。在我们的时间处理函数内部,我们只需要获得自午夜以来的秒数,对此插件提供了一个方便的方法,然后将它乘以1000并传递给$setViewValue。当我们完成了以上过程,我们的数据将会经历以下的步骤:
一旦我们将我们的timepicker实例化完成并监听视图上的变化,我们的下一步就是要定义$render方法,它将负责将数据值转换为合适的显示或者视图值。只要我们指令内部的数值发生变化,它就会发生,包括第一次实例化时:
link : function ($scope, $element, $attrs, ngModel) {
...
ngModel.$render = function (val) {
if (!initialized) {
//如果$render在我们的timepicker插件准备好之前就被调用,返回
return; };
$element.timepicker('setTime', new Date(today.getTime() + val));
}
}
再次,注意到我们实际上重定义了$render方法,因此当ngModel观察到一个数据变化时,它告诉控制器来执行$render,这时我们定义的$render就被调用了。我们所需要做的仅仅是将数据模型的值转换成为我们的插件期待的值。在这个例子中,timepicker插件提供了一种方法来设置时间显示。因为我们的时间以毫秒存储,当我们需要渲染一个值时,我们只需要简单地从today获取时间,加上我们的新值,然后用这个值创建一个Date对象即可。当我们完成时,我们的数据将会通过以下步骤返回到视图中: