Angularjs自定义指令中引入NgModel

原文:https://www.nadeau.tv/using-ngmodelcontroller-with-custom-directives/

指令例子:

自定义一个指令,用户可以用输入时间,可以选择时间单位。

My Super App


    How often should we emal you? {{email_notify_pref}}

这个指令有两个输入框:

1、一个文本输入框,可以输入数字

2、一个下拉框,可以选择时间单位

模型 model

        我们将会把用户输入的数值以秒的形式存储于后端模型(model),当渲染模型(model)时,我们会根据时间单位转化一下显示在输入框中。例如:当我们模型(model)为3600秒时,指令将显示1 hour。

        这个指令将会展示Angular存储模型(model)数据时,如何解析($parsers)与格式化($formatters)数据。模型(model)一直以秒的形式存储,当展示到页面上时会被解析成一个数字与一个时间单位。这意味着我们必须处理从“秒”到 “时间单位/数值”,再到“秒”的转换。

        一旦明白了解析(parsers)与格式化(formatters)的进本工作流程,我们将会自定义各种复杂的指令。

步骤

开始定义指令

function TimeDurationDirective(){
        let tpl=`


            
            
        
`
         return {
                 restrict:'E',
                 template: tpl,
                 require:'ngModel', 
                 replace:true,
                 link: function(scope, iElement, iAttrs, ngModelCtrl){
                     //TODO 
                  }
             }
};
angualr.module('myModule').directive('timeDuration',TimeDurationDirective);

现在为止,只是创建了一个显示输入框(输入数字)和下拉菜单(选择时间单位)的指令。

使用NgModelController

进一步讨论之前,先来看看link函数的参数

link:function(scope,iElement,iAttrs,ngModelCtrl){
        //TODO
}

    scope:指令绑定在模板上的scope,

    iElement:实际的HTML DOM元素,

    iAttrs:原始指令HTML的属性集合,

    ngModelCtrl:引入的NgModelController的实例。

现在让看一下指令一共处理几种数据。这里一共有4种不同的值

    1、存储在scope上的数据模型(model),例如我们可能在代码中设置这样的值:$scope.email_notify_pref=3600;

    2、ngModelCtrl.$modelValue: 数据模型(model)的拷贝;

    3、ngModelCtrl.$viewValue: html上的值的拷贝;

    4、html上的值。

            NgModelController的任务就是反复的处理这四个数值。例如,如果更改表单(#4)里的数值,我们用NgModelController 确保数据模型(#1)更新;相反,当更改数据模型(#1)的值,我们用NgModelController确保UI上的数值也更新。

$formatters管道

        第一个问题就是如何将模型(model)上的值转换成页面上的值。在本示例中就意味这将3600s转换成1hour

        第一步决定我们页面上将要用的数据(ngModelCtrl.$viewValue)的数据结构。对于我们来说,它是由HTML模板中的表单决定的,一个数字的输入框和一个时间单位的选择框。存储这个的最简单方法是具有两个属性的对象{num:1,unit:'hours'}。ngModelController通过$formatters里的函数对ngModelCtrl.$modelValue进行处理,将最终的返回值赋给ngModelCtrl.$viewValue。

        这样的话link函数将会变成下面这样:

link: function(scope, iElement, iAttrs, ngModelCtrl) {
    // 时间单位
     let multiplierMap = {seconds: 1, minutes: 60, hours: 3600, days: 86400};
     let multiplierTypes = ['seconds', 'minutes', 'hours', 'days']
     ngModelCtrl.$formatters.push(function(modelValue) {
         var unit = 'minutes', num = 0, i, unitName;
         modelValue = parseInt(modelValue || 0);    
        // 计算出model的最大单位时间
        //例如,3600是1小时,但1800是30分钟
         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 };
     });
}

现在,管道看起来是这样的:

$scope.email_notify_pref = 3600

 ngModelCtrl.$formatters(3600)
 ↓
 $viewValue = { unit: 'hours', num: 1}

根据$viewValue更新UI

        将$viewValue的数值渲染到页面上是通过ngModelCtrl.$render函数实现的。

        在我们的例子中,我们使用指令scope将值绑定到表单,意味着我们只需更新scope上的值就可以了。

        下面是我们的$render方法,将视ngModelCtrl.$viewValue分配给我们在HTML模板中使用的scope。

ngModelCtrl.$render = function() {
     scope.unit = ngModelCtrl.$viewValue.unit;
     scope.num = ngModelCtrl.$viewValue.num;
};

$parsers管道

        类似于$formatters管道将模型(model)的值转换为$viewValue,我们通过$parsers管道将$viewValue转换为$modelValue(最终被分配到模型(model)中)

ngModelCtrl.$parsers.push(function(viewValue) {
     var unit = viewValue.unit, num = viewValue.num, multiplier;
     // 在上面已经定义了 multiplierMap 
     multiplier = multiplierMap[unit];
     return num * multiplier;
 });

        让我们来看一下这条管道

$viewValue = { unit: 'hours', num: 1 };

 ngModelCtrl.$parsers({unit: 'hours', num: 1})
 ↓
 $modelValue = 3600;

当UI变化时更新$viewValue

        最后一个问题是,当UI中的值发生变化时,确保更新$viewValue。当值发生变化时,我们通过执行ngModelCtrl.$setViewValue()来执行此操作。

        我们如何知道值什么时候发生变化?这完全取决于我们的指令。这在我们的例子中很简单,因为我们将值绑定到了指令的scope上了,所以我们设个watch就可以了。

scope.$watch('unit + num', function() {
     ngModelCtrl.$setViewValue({ unit: scope.unit, num: scope.num });
});

完整的过程

realModel → ngModelCtrl.$formatters(realModel) → $viewModel
 ↑                                                                                     ↓ 
                                                                                   $render()
                                                                                         ↓
 ↑                                                                                 UI changed
                                                                                         ↓
 ngModelCtrl.$parsers(newViewModel) ← $setViewModel(newViewModel)

完整指令代码

function TimeDurationDirective() {
    let tpl=`


        
        
    
`;
 return {
     restrict: 'E',
     template: tpl,
     require: 'ngModel',
     replace: true,
     link: function(scope, iElement, iAttrs, ngModelCtrl) {
        let multiplierMap = {seconds: 1, minutes: 60, hours: 3600, days: 86400};
        let multiplierTypes = ['seconds', 'minutes', 'hours', 'days']         ngModelCtrl.$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 };
        });
        ngModelCtrl.$parsers.push(function(viewValue) {
            var unit = viewValue.unit, num = viewValue.num, multiplier;
             multiplier = multiplierMap[unit];
            return num * multiplier;
         });
        scope.$watch('unit + num', function() {
            ngModelCtrl.$setViewValue({ unit: scope.unit, num: scope.num });
        });
        ngModelCtrl.$render = function() {
            if (!$viewValue) $viewValue = { unit: 'hours', num: 1 };
            scope.unit = ngModelCtrl.$viewValue.unit;
            scope.num = ngModelCtrl.$viewValue.num;
        };
    } };
};
angular.module('myModule').directive('timeDuration', TimeDurationDirective);

你可能感兴趣的:(Angularjs自定义指令中引入NgModel)