2012-08-10 10 views
21

Estoy creando directivas simples de 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.¿Puedo usar ng-model con alcance aislado?

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

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

Por lo que yo entiendo - las mejores prácticas cuando se crea un nuevo componente es el uso de alcance aislado.

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

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

Por supuesto que puedo crear directivas con alcance aislado y trabajar con el valor de ngModel a través de "= expresión" (example). Pero creo que trabajar con ngModelController es una mejor práctica.

Mis preguntas:

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

Respuesta

2

Haga que su directiva funcione con una prioridad mayor que ngModel y corrija el enlace del modelo para su alcance 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í es 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 compilación, los cheques directiva si el atributo ngModel está presente. Este control funciona en el valor normalizado usando Angular's Attributes. 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.

+2

Parece como solución. Pero para la solución prefiero 'scope: true' y' ng-model = "someObj.someProp" ' –

19

Reemplazar scope: true con scope: { datetime1: '=ngModel'} en su primer violín parece funcionar bien - fiddle. 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 alcance aislado.

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

actualización: acabo de descubrir algo bastante interesante: si la propiedad alcance aislado se le da un nombre diferente - por ejemplo, en lugar de decir DT1 datetime1 - scope: { dt1: '=ngModel'} - ya no funciona! Supongo que cuando hacemos require: 'ngModel', ngModelController usa el nombre en el HTML/vista (es decir, el valor del atributo ng-model) para crear una propiedad en el ámbito 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 (por ejemplo, dt1) no está asociada con el ngModelController que necesitamos.

Aquí hay un updated fiddle.

+1

Parece que ngModelController [usa] (https://github.com/angular/angular.js/blob/v1.0.1 /src/ng/directive/input.js#L873) y [watches] (https://github.com/angular/angular.js/blob/v1.0.1/src/ng/directive/input.js#L998) basado en ngModel. Entonces también debemos usar diferentes soluciones. –

+0

¿Eso es un error @MarkRajcok? – finishingmove

+0

@finishingmove, no sé. Adivinaré que tenemos "suerte" si usamos el mismo nombre (pero tampoco sé si esa suerte podría romperse en alguna parte dentro de la directiva). Ninguno de los ejemplos angulares utiliza un alcance aislado cuando 'requieren: 'ngModel'', por lo que me mantendría alejado de él. –

1

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

Por lo tanto, el problema tiene varias partes:

  1. su directiva personalizada quiere algunas propiedades privadas, es decir, el alcance aislado
  2. DOM nodo sólo puede tener un alcance, todas las directivas compartirlo
  3. ngModel =" algo" se une a 'algo' en ese ámbito compartido (aislado), y este es el problema real

lo tanto, mi primer paso fue volver a escribir mi directiva utilizar scope:true en lugar de scope:{...} (de hecho, eso era un requisito, porque quería usar algunas propiedades de ámbito global dentro del contenido transcluido de mi directiva): cosas como attrs.$observe(), $scope.$parent.$watch(), etc. me ayudaron.

Luego en compile() vuelvo a encuadernar ngModel a la propiedad principal del osciloscopio: attrs.$set('ngModel', '$parent.' + attrs.ngModel, false). Y eso es todo.

Aquí es mi directiva, con el código no esenciales 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 
      }; 
     } 
    }; 
}); 
+2

Simplemente puede usar 'scope: true' y' ng- model = "someObj.someProp" 'en lugar de tu truco. Se recomienda usar '" someObj.someProp "' para 'ng-model'. –

+1

Sí, su solución también funciona, gracias por señalar. Sin embargo, mi código soluciona el problema de todas las instancias de una directiva y requiere un ajuste de código por separado para cada instancia. He compilado una demostración en la que puede ver mi código en acción sin algún Obj (es decir, $ scope.someProp): http://jsbin.com/ejozow/1/edit. BTW, ¿puedes publicar un enlace para leer sobre recomendaciones de uso de re ng-model? Los documentos oficiales parecen ser bastante escasos en general. – alx

+0

https://plus.google.com/118090665492423851447/posts/KKiLKLCF4Xa - vea el comentario de Miško Hevery. No es obligatorio sino recomendado. –

0

probar 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 "<input ng-model='YYY' type="' + type + '" + required + ' />'; 
        } 
    }; 
}); 
Cuestiones relacionadas