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 tener ng-change llama cuando un elemento se hace clic en y el valor de foo ha cambiado ya.

Que es, si mi directiva se implementa como:

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

Yo esperaría a llamar bar cuando el valor de foo ha sido actualizado.

Con el código anterior, ngChange es llamado correctamente, pero se llama con el valor antiguo de foo en lugar de el nuevo valor actualizado.

Una forma de resolver el problema es llamar a ngChange dentro de un tiempo de espera para ejecutar en algún momento en el futuro, cuando el valor de foo ya ha sido cambiado. Pero esta solución me hacen perder el control sobre el orden en que las cosas deben ser ejecutadas y supongo que debe haber una solución más elegante.

Yo también podría utilizar un observador sobre foo en el ámbito primario, pero esta solución no da una ngChange método implmented y me han dicho que los observadores son grandes consumidores de memoria.

Hay una manera de hacer ngChange ser ejecutado de forma sincrónica sin un tiempo de espera o de un vigilante?

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

InformationsquelleAutor htellez | 2014-07-15

5 Comentarios

  1. 58

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

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

    Para que su solución para que funcione, por favor, elimine ngChange y ngModel de aislar ámbito de myDirective.

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

    • ¿Hay alguna forma de hacer esto sin la eliminación de la ngModel de la aislada alcance? Tratando de implementar de dos vías de enlace con ngChange y se ha convertido en enrevesado/ineficiente stackoverflow.com/questions/30575973/…
    • A pesar de resolver el problema, esta respuesta abarca el problema real. La costumbre de la directiva en la pregunta no va a trabajar con muchos de los comunes ng-* directivas porque no se registra que el punto de vista de valor ha sido actualizado. Ver a @lucienBertin la respuesta para más detalles.
    • Tienes razón Ed & @lucienBertin. De hecho, la viewChangListeners no son necesarios. Configuración de la vista de valor a través de la ngModelCtrl implícitamente evalúa el ng-cambió de expresión. Plunk limpiado un poco. Uno agradable.
    • Esto funciona, pero depende de tu punto de ser un tipo de valor. Si se utilizan objetos de que sólo funciona la primera vez que la referencia se establece. Usted necesitará para clonar un objeto ref antes de invocar setViewValue en orden para ngChange para desencadenar en estos casos (véase mi respuesta a continuación).
  2. 14

    tl;dr

    En mi experiencia, usted sólo tiene que heredar de la ngModelCtrl. el ng-change expresión serán evaluados automáticamente cuando se utiliza el método de 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 el ngModelCtrl.$commitViewValue() SI el objeto de referencia de su ngModel ha cambiado. el método $commitViewValue() es llamado automáticamente por $setViewValue(value, trigger) si no utiliza el gatillo argumento o no precisando ningún ngModelOptions.

    La que se especifica que el ng-chage sería activa automáticamente si la referencia de la $viewValue cambiado. Cuando su ngModel es un string o un int, usted no tiene que preocuparse acerca de esto. Si su ngModel es un objeto y que acaba de cambiar algunas de sus propiedades, a continuación, $setViewValue no eval ngChange.

    Si tomamos el ejemplo de código desde el inicio de la 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
    };
    • La gran penetración. La lectura de su descripción, me gustaría pensar que yo podría llamar manualmente $commitViewValue() para forzar ngChange (en el caso de la actualización de un objeto existente), pero no es así. AngularJS documentación sugiere «controles personalizados también pueden pasar objetos a este método. En este caso, debemos hacer una copia del objeto antes de pasar a $setViewValue.». Consulte docs.angularjs.org/api/ng/type/ngModel.NgModelController
  3. 9

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

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

    Así que, en mi caso, la solución fue utilizar

    $timeout(scope.ngChange, 0);

    De esta manera, no importa lo que es la firma de su devolución de llamada, se ejecuta tal como se define en el ámbito primario.

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

    • En realidad, esto funcionó muy bien para mí, ya que tenía un $tiempo de espera relacionados con el fn o la manipulación de los campos de texto.
    • Sólo como un FYI, la razón por la que esto funciona es porque $timeout ajusta automáticamente la función en un $scope.$apply, lo que provoca una $digerir ciclo de una patada. Si usted no quiere esto, se pasa a un tercer parámetro booleano a $timeout instruye para no hacerlo.
  4. 0

    Samuli Ulmanen y lucienBertin respuestas de las uñas, aunque con un poco de lectura adicional en el AngularJS documentación se proporciona más asesorar sobre cómo manejar esto (ver https://docs.angularjs.org/api/ng/type/ngModel.NgModelController).

    Específicamente en los casos donde se pasa objetos de $setViewValue(myObj). AngularJS Documentatation estados:

    Cuando se utiliza con el estándar de entradas, el punto de vista de valor siempre será una cadena (que en algunos casos, se analiza en otro tipo, como un objeto Date para la entrada[fecha].) Sin embargo, los controles personalizados también pueden pasar objetos a este método. En este caso, debemos hacer una copia del objeto antes de pasar a $setViewValue. Esto es debido a que ngModel no realizar una observación en profundidad de los objetos, que solo busca un cambio de identidad. Si sólo cambia la propiedad del objeto, a continuación, ngModel no se dan cuenta de que el objeto ha cambiado y no invocar la $analizadores y $validadores de las tuberías. Por esta razón, usted no debe cambiar las propiedades de la copia una vez que se ha pasado a $setViewValue. De lo contrario, puede provocar que el modelo de valor en el ámbito de cambiar de forma incorrecta.

    Para mi caso concreto, mi modelo es un momento de la fecha de objetos, así que tengo que clonar el objeto primero antes de llamar setViewValue. Tengo la suerte de aquí de momento ofrece un sencillo método clone: 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);
            }
        };
    }
  5. -2

    La cuestión fundamental aquí es que el modelo subyacente no se actualiza hasta que el ciclo de digerir que sucede después de scope.updateModel ha terminado de ejecutarse. Si el ngChange función requiere los detalles de la actualización que se realiza, a continuación, los detalles pueden estar disponibles de forma explícita a ngChange, en lugar de basarse en el modelo de actualización de haber sido aplicado anteriormente.

    Esto puede hacerse mediante la prestación de un mapa de nombres de variables locales a los valores cuando se llama a ngChange. En este escenario, usted puede asignar el nuevo valor de la modelo con un nombre que puede ser que se hace referencia en el ng-change expresión.

    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>

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

    • Un auténtico ng-cambio nunca debería saber lo que los argumentos son. Para este caso en particular lo que proponemos una solución, pero ¿qué pasa si, a continuación, intentar utilizar una devolución de llamada de la barra(a, b, c, …) con dos o más argumentos?
    • Los valores que la directiva quiere exponer a ser capaz de utilizar en la ng-change expresión debe ser especificado cuando se llama a scope.ngChange. Por ejemplo: scope.ngChange({newValue: item, a: 'something', b: 42}). La expresión puede, a continuación, utilizar la parte expuesta de los valores según sea necesario: ng-change="bar(newValue, a, b)"
    • Eso es exactamente lo que no quiero que suceda. Como se puede ver, updateModel es una función dentro de la directiva. Quiero la directiva para ser independiente de quién la está utilizando. Igual que el normal angular ng-change directiva obras. No es necesario redefinir el angular ng-directive a usarlo.
    • Lo siento Chris, pero se trata de enseñar a los malos hábitos para que cualquier usuario pueda leer, de manera que voy a votar. htellez que es correcto. Este es el acoplamiento estrecho y no es reutilizable.

Dejar respuesta

Please enter your comment!
Please enter your name here