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