personalizadas directivas compile angularjs angularjs-directive angularjs-ng-change

angularjs - directivas - Cómo implementar un cambio de ng para una directiva personalizada



directivas personalizadas angularjs (5)

Después de algunas investigaciones, parece que el mejor enfoque es usar $timeout(callback, 0) .

Inicia automáticamente un ciclo de $digest justo después de que se ejecuta la devolución de llamada.

Entonces, en mi caso, la solución fue usar

$timeout(scope.ngChange, 0);

De esta manera, no importa cuál sea la firma de su devolución de llamada, se ejecutará tal como lo definió en el ámbito principal.

Aquí está el plunkr con tales cambios: http://plnkr.co/edit/9MGptJpSQslk8g8tD2bZ?p=preview

Tengo una directiva con una plantilla como

<div> <div ng-repeat="item in items" ng-click="updateModel(item)"> <div>

Mi directiva se declara como:

return { templateUrl: ''...'', restrict: ''E'', require: ''^ngModel'', scope: { items: ''='', ngModel: ''='', ngChange: ''&'' }, link: function postLink(scope, element, attrs) { scope.updateModel = function(item) { scope.ngModel = item; scope.ngChange(); } } }

Me gustaría que se llame a ng-change cuando se hace clic en un elemento y el valor de foo se ha cambiado.

Es decir, si mi directiva se implementa como:

<my-directive items=items ng-model="foo" ng-change="bar(foo)"></my-directive>

Espero llamar a bar cuando el valor de foo se haya actualizado.

Con el código dado anteriormente, ngChange se llama con éxito, pero se llama con el valor antiguo de foo lugar del nuevo valor actualizado.

Una forma de resolver el problema es llamar a ngChange dentro de un tiempo de espera para ejecutarlo en algún momento en el futuro, cuando el valor de foo ya haya cambiado. Pero esta solución me permite perder el control sobre el orden en que se supone que se deben ejecutar las cosas y asumo que debería haber una solución más elegante.

También podría usar un vigilante sobre foo en el ámbito principal, pero esta solución realmente no ofrece un método de cambio de ngChange para ser implementado y me han dicho que los observadores son grandes consumidores de memoria.

¿Hay alguna manera de hacer que ngChange se ejecute de forma sincrónica sin un tiempo de espera o un observador?

Ejemplo: http://plnkr.co/edit/8H6QDO8OYiOyOx8efhyJ?p=preview


El problema fundamental aquí es que el modelo subyacente no se actualiza hasta que el ciclo de resumen que ocurre después de que scope.updateModel haya terminado de ejecutarse. Si la función ngChange requiere detalles de la actualización que se está realizando, esos detalles pueden estar disponibles explícitamente para ngChange , en lugar de confiar en la actualización del modelo que se aplicó anteriormente.

Esto se puede hacer proporcionando un mapa de nombres de variables locales a valores al llamar a ngChange . En este escenario, puede asignar el nuevo valor del modelo a un nombre al que se puede hacer referencia en la expresión ng-change .

Por ejemplo:

scope.updateModel = function(item) { scope.ngModel = item; scope.ngChange({newValue: item}); }

En el HTML:

<my-directive ng-model="foo" items=items ng-change="bar(newValue)"></my-directive>

Consulte: http://plnkr.co/edit/4CQBEV1S2wFFwKWbWec3?p=preview


Las respuestas de Samuli Ulmanen y lucienBertin lo aclaran, aunque un poco de lectura adicional en la documentación de AngularJS proporciona más consejos sobre cómo manejar esto (consulte ngModelCtrl ).

Específicamente en los casos en los que está pasando objetos a $ setViewValue (myObj). AngularJS Documentatation estados:

Cuando se usa con entradas estándar, el valor de vista siempre será una cadena (que en algunos casos se analiza en otro tipo, como un objeto Fecha para entrada [fecha]). Sin embargo, los controles personalizados también pueden pasar objetos a este método. En este caso, deberíamos hacer una copia del objeto antes de pasarlo a $ setViewValue. Esto se debe a que ngModel no realiza una observación profunda de los objetos, solo busca un cambio de identidad. Si solo cambia la propiedad del objeto, ngModel no se dará cuenta de que el objeto ha cambiado y no invocará las líneas de $ analizadores y $ validadores. Por este motivo, no debe cambiar las propiedades de la copia una vez que se haya pasado a $ setViewValue. De lo contrario, puede hacer que el valor del modelo en el alcance cambie incorrectamente.

Para mi caso específico, mi modelo es un objeto de fecha de momento, por lo que debo clonar el objeto antes de llamar a setViewValue. Tengo suerte aquí porque el momento proporciona un método de clonación simple: var b = moment(a);

link : function(scope, elements, attrs, ctrl) { scope.updateModel = function (value) { if (ctrl.$viewValue == value) { var copyOfObject = moment(value); ctrl.$setViewValue(copyOfObject); } else { ctrl.$setViewValue(value); } }; }


Si necesita ngModel , simplemente puede llamar a $setViewValue en ngModelController , que evalúa implícitamente ng-change . El cuarto parámetro para la función de enlace debe ser el ngModelCtrl. El siguiente código hará que ng-change funcione para su directiva.

link : function(scope, element, attrs, ngModelCtrl){ scope.updateModel = function(item) { ngModelCtrl.$setViewValue(item); } }

Para que su solución funcione, elimine ngChange y ngModel del ámbito de aislamiento de myDirective.

Aquí hay un plunk: http://plnkr.co/edit/UefUzOo88MwOMkpgeX07?p=preview


tl; dr

En mi experiencia, solo necesita heredar de ngModelCtrl . la expresión ng-change se evaluará automáticamente cuando utilice el método ngModelCtrl.$setViewValue

angular.module("myApp").directive("myDirective", function(){ return { require:"^ngModel", // this is important, scope:{ ... // put the variables you need here but DO NOT have a variable named ngModel or ngChange }, link: function(scope, elt, attrs, ctrl){ // ctrl here is the ngModelCtrl scope.setValue = function(value){ ctrl.$setViewValue(value); // this line will automatically eval your ng-change }; } }; });

Más precisamente

ng-change se evalúa durante ngModelCtrl.$commitViewValue() SI la referencia de objeto de su ngModel ha cambiado. El método $commitViewValue() es llamado automáticamente por $setViewValue(value, trigger) si no usa el argumento de activación o si no ha seleccionado ninguna ngModelOptions .

Especifiqué que el ng-chage se ng-chage automáticamente si la referencia de $viewValue cambiaba. Cuando su ngModel es una string o un int , no tiene que preocuparse por eso. Si su ngModel es un objeto y solo está cambiando algunas de sus propiedades, $setViewValue no ngChange .

Si tomamos el ejemplo de código desde el inicio del post.

scope.setValue = function(value){ ctrl.$setViewValue(value); // this line will automatically evalyour ng-change }; scope.updateValue = function(prop1Value){ var vv = ctrl.$viewValue; vv.prop1 = prop1Value; ctrl.$setViewValue(vv); // this line won''t eval the ng-change expression };