我可以使用具有隔离范围的ng模型吗?

时间:2012-08-10 06:46:40

标签: angularjs

我正在创建简单的ui-datetime指令。它将javascript Date对象拆分为_date,_hours和_minutes部分。 _date使用jquery ui datepicker,_hours和_minutes - 数字输入。

angular.module("ExperimentsModule", [])
    .directive("uiDatetime", function () {
    return {
        restrict: 'EA',
        replace: true,
        template: '<div class="ui-datetime">' +
            '<input type="text" ng-model="_date" class="date">' +
            '<input type="number" ng-model="_hours" min="0" max="23" class="hours">' +
            '<input type="number" ng-model="_minutes" min="0" max="59" class="minutes">' +
            '<br />Child datetime1: {{datetime1}}' +
            '</div>',
        require: 'ngModel',
        scope: true,
        link: function (scope, element, attrs, ngModelCtrl) {
            var elDate = element.find('input.date');

            ngModelCtrl.$render = function () {
                var date = new Date(ngModelCtrl.$viewValue);
                var fillNull = function (num) {
                    if (num < 10) return '0' + num;
                    return num;
                };
                scope._date = fillNull(date.getDate()) + '.' + fillNull(date.getMonth() + 1) + '.' + date.getFullYear();
                scope._hours = date.getHours();
                scope._minutes = date.getMinutes();
            };

            elDate.datepicker({
                dateFormat: 'dd.mm.yy',
                onSelect: function (value, picker) {
                    scope._date = value;
                    scope.$apply();
                }
            });

            var watchExpr = function () {
                var res = scope.$eval('_date').split('.');
                if (res.length == 3) return new Date(res[2], res[1] - 1, res[0], scope.$eval('_hours'), scope.$eval('_minutes'));
                return 0;
            };
            scope.$watch(watchExpr, function (newValue) {
                ngModelCtrl.$setViewValue(newValue);
            }, true);
        }
    };
});

function TestController($scope) {
    $scope.datetime1 = new Date();
}

jsfiddle

在github上:https://github.com/andreev-artem/angular_experiments/tree/master/ui-datetime

据我了解 - 创建新组件时的最佳做法是使用隔离范围。

当我尝试使用隔离范围时 - 没有任何作用。 ngModel。$ viewValue === undefined。

当我尝试使用新范围时(我的例子,不是那么好的变体imho) - ngModel在新创建的范围上使用值。

当然,我可以使用隔离范围创建指令,并通过“= expression”(example)使用ngModel值。但我认为使用ngModelController是一种更好的做法。

我的问题:

  1. 我可以使用具有隔离范围的ngModelController吗?
  2. 如果不可能哪种解决方案更适合创建此类组件?

4 个答案:

答案 0 :(得分:18)

在您的第一个小提琴中用scope: true替换scope: { datetime1: '=ngModel'}似乎工作正常 - fiddle。不幸的是,你的“示例”小提琴的链接被打破了,所以我不确定你在那里尝试了什么。

因此,似乎ngModelController可以与隔离范围一起使用。

这是一个较小的小提琴,它在HTML /视图中使用ng-model,一个隔离范围,以及链接函数中的$ setViewValue:fiddle

更新:我刚刚发现了一些相当有趣的东西:如果隔离范围属性被赋予了不同的名称 - 例如,说dt1而不是datetime1 - scope: { dt1: '=ngModel'} - 它不再作品!我猜测当我们require: 'ngModel'时,ngModelController使用HTML /视图中的名称(即ng-model属性值)在隔离范围上创建属性。因此,如果我们在对象哈希中指定相同的名称,那么一切都很好。但是如果我们指定一个不同的名称,那么新的范围属性(例如,dt1)就不会与我们需要的ngModelController相关联。

这是一个updated fiddle

答案 1 :(得分:2)

使指令以比ngModel更高的优先级运行,并更正隔离范围的模型绑定。我选择优先级为'100'的级别与输入指令相同,经过高优先级模板操作(如ngRepeat)之后但默认值为0之前,这是ngModel使用的。

以下是示例代码:

myDirective = function() {
  return {
    compile: function(tElement, tAttrs, transclude) {
      // Correct ngModel for isolate scope
      if (tAttrs.ngModel) {
        tAttrs.$set('model', tAttrs.ngModel, false);
        tAttrs.$set('ngModel', 'model', false);
      }

      return {
        post: function(scope, iElement, iAttrs, controller) {
          // Optionally hook up formatters and parsers
          controller.$formatters.push(function(value) {
             // ...
          })

          // Render
          return controller.$render = function() {
            if (!controller.$viewValue) {
              return;
            }
            angular.extend(scope, controller.$viewValue);
          };
        }
      };
    },
    priority: 100,
    require: '^ngModel',
    scope: {
      model: '='
    },
  };
}

在编译期间,该指令检查是否存在ngModel属性。此检查使用Angular的Attributes对标准化值起作用。如果该属性存在,则将其替换为'model'(不是'ngModel'),这是绑定到隔离数据中的名称。但是,我们还必须创建一个属性,以便Angular可以为我们执行数据绑定。这两个属性都可以(根据您的选择)使用false参数进行修改,使DOM保持不变。

答案 2 :(得分:1)

我认为我遇到了同样的问题,我找到了部分可用的解决方案。

所以,问题有几个部分:

  1. 您的自定义指令需要一些私有属性,即隔离范围
  2. DOM节点只能有一个范围,所有指令都共享它
  3. ngModel =“something”绑定到该共享(隔离)范围内的“某事”,这是实际问题
  4. 所以,我的第一步是重写我的指令以使用scope:true而不是scope:{...}(实际上,这是一个要求,因为我想在我的指令的被转换内容中使用一些全局范围属性) :attrs.$observe()$scope.$parent.$watch()等内容有帮助。

    然后在compile()中,我将ngModel重新绑定到父作用域的属性:attrs.$set('ngModel', '$parent.' + attrs.ngModel, false)。这就是全部。

    这是我的指令,剥离了非基本代码:

    angular.module('App', []).directive('dir', function () {
        return {
            /* This one is important: */
            scope:true,
            compile:function (element, attrs, transclude) {
                /* The trick is here: */
                if (attrs.ngModel) {
                    attrs.$set('ngModel', '$parent.' + attrs.ngModel, false);
                }
    
                return function ($scope, element, attrs, ngModel) {
                    // link function body
                };
            }
        };
    });
    

答案 3 :(得分:0)

试试这个版本:

.directive('myDir', function() {
    return {
        restrict: 'EA',
        scope:    {
                    YYY: '=ngModel'
                  },
        require:  'ngModel',
        replace:  true,
        template: function render(element, attrs) {
            var type = attrs.type || 'text';
            var required = attrs.hasOwnProperty('required') ? " required='required'" : "";
            return "<input ng-model='YYY' type="' + type + '" + required + ' />';
                  }
    };
});