¿Puedo usar ng-model con un scope aislado?

Estoy creando una directiva simple ui-datetime. Divide el objeto Date de JavaScript en las partes _date, _hours y _minutes. _date usa jquery ui datepicker, _hours y _minutes – entradas numéricas.

angular.module("ExperimentsModule", []) .directive("uiDatetime", function () { return { restrict: 'EA', replace: true, template: '
' + '' + '' + '' + '
Child datetime1: {{datetime1}}' + '
', 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

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

Por lo que yo entiendo, la mejor práctica al crear un nuevo componente es usar un scope aislado.

Cuando traté de usar el scope aislado, nada funciona. ngModel. $ viewValue === undefined.

Cuando traté de usar el nuevo ámbito (mi ejemplo, no tan bueno variante imho) – ngModel utiliza el valor en el ámbito recién creado.

Por supuesto, puedo crear directivas con scope aislado y trabajar con ngModel value a través de “= expression” ( ejemplo ). Pero creo que trabajar con ngModelController es una mejor práctica.

Mis preguntas:

  1. ¿Puedo usar ngModelController con scope aislado?
  2. Si no es posible, ¿qué solución es mejor para crear dicho componente?

Reemplazar el scope: true con el scope: { datetime1: '=ngModel'} en su primer violín parece funcionar bien – violín . Lamentablemente, el enlace a su violín de “ejemplo” está roto, por lo que no estoy seguro de lo que ha intentado allí.

Por lo tanto, parece que ngModelController se puede usar con un scope aislado.

Aquí hay un violín más pequeño que usa ng-model en el HTML / vista, un scope aislado y $ setViewValue en la función de enlace: fiddle .

Actualización : Acabo de descubrir algo bastante interesante: si a la propiedad del scope del aislamiento se le asigna un nombre diferente, por ejemplo, dt1 en vez de datetime1- scope: { dt1: '=ngModel'} , ¡ya no funciona! Supongo que cuando require: 'ngModel' , ngModelController usa el nombre en el HTML / vista (es decir, el valor del atributo ng-model) para crear una propiedad en el scope aislado. Entonces, si especificamos el mismo nombre en el hash del objeto, todo está bien. Pero si especificamos un nombre diferente, esa nueva propiedad de ámbito (p. Ej., Dt1) no está asociada con el ngModelController que necesitamos.

Aquí hay un violín actualizado .

Haga que su directiva se ejecute con una prioridad mayor que ngModel y corrija el enlace del modelo para su scope aislado. Elegí una prioridad de ‘100’ que es el mismo nivel que la directiva de entrada, después de manipulaciones de plantilla de alta prioridad como ngRepeat pero antes del valor predeterminado de 0, que es lo que usa ngModel.

Aquí está el código de ejemplo:

 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: '=' }, }; } 

Durante la comstackción, la directiva verifica si el atributo ngModel está presente. Esta comprobación funciona en el valor normalizado utilizando los atributos de Angular. Si el atributo está presente, se reemplaza por ‘modelo’ (no ‘ngModel’), que es el nombre vinculado a datos en nuestro aislado. Sin embargo, también debemos crear un atributo para que Angular pueda realizar el enlace de datos para nosotros. Ambos atributos pueden modificarse (a su elección) con un parámetro false que no modifica el DOM.

Creo que tuve el mismo problema y encontré una solución parcial pero utilizable.

Entonces, el problema tiene varias partes:

  1. su directiva personalizada quiere algunas propiedades privadas, es decir, scope aislado
  2. El nodo DOM solo puede tener un scope, todas las directivas lo comparten
  3. ngModel = “algo” se une a “algo” en ese ámbito compartido (aislado), y este es el problema real

Entonces, mi primer paso fue reescribir mi directiva para usar scope:true lugar de scope:{...} (en realidad, eso era un requisito, porque quería usar algunas propiedades de scope global dentro del contenido transcluido de mi directiva): cosas como attrs.$observe() , $scope.$parent.$watch() , etc. ayudado.

Luego, en compile() reinvierto ngModel para la propiedad del scope principal: attrs.$set('ngModel', '$parent.' + attrs.ngModel, false) . Y eso es todo.

Aquí está mi directiva, con código no esencial despojado:

 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 }; } }; }); 

Prueba una versión de esto:

 .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 "'; } }; });