22

我正在创建简单的 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 === 未定义。

当我尝试使用新范围时(我的示例,不是很好的变体恕我直言) - ngModel 在新创建的范围上使用值。

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

我的问题:

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

4 回答 4

18

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

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

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

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

这是一个更新的小提琴

于 2013-02-09T23:00:53.623 回答
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对标准化值起作用。如果该属性存在,则将其替换为“模型”(而不是“ngModel”),这是数据绑定到我们隔离中的名称。但是,我们还必须创建一个属性,以便 Angular 可以为我们执行数据绑定。这两个属性都可以(由您选择)使用false保持 DOM 不变的参数进行修改。

于 2012-12-12T09:12:01.133 回答
1

我想我有同样的问题,我找到了部分但可用的解决方案。

所以,这个问题有几个部分:

  1. 您的自定义指令需要一些私有属性,即隔离范围
  2. DOM 节点只能有一个范围,所有指令共享它
  3. ngModel="something" 绑定到共享(隔离)范围内的“something”,这是实际问题

所以,我的第一步是重写我的指令来使用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
            };
        }
    };
});
于 2013-04-10T19:37:25.810 回答
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 + ' />';
                  }
    };
});
于 2013-07-01T21:39:49.823 回答