¿Cómo ignoro la carga inicial cuando veo cambios de modelo en AngularJS?


184

Tengo una página web que sirve como editor para una sola entidad, que se ubica como un gráfico profundo en la propiedad $ scope.fieldcontainer. Después de recibir una respuesta de mi API REST (a través de $ resource), agrego un reloj a 'fieldcontainer'. Estoy usando este reloj para detectar si la página / entidad está "sucia". En este momento estoy haciendo que el botón Guardar rebote, pero realmente quiero hacer que el botón Guardar sea invisible hasta que el usuario ensucie el modelo.

Lo que estoy obteniendo es un solo disparador del reloj, lo que creo que está sucediendo porque la asignación .fieldcontainer = ... se lleva a cabo inmediatamente después de crear mi reloj. Estaba pensando en usar una propiedad "dirtyCount" para absorber la falsa alarma inicial, pero eso se siente muy hacky ... y pensé que tenía que haber una manera "idiomática angular" para lidiar con esto: no soy el único usando un reloj para detectar un modelo sucio.

Aquí está el código donde configuré mi reloj:

 $scope.fieldcontainer = Message.get({id: $scope.entityId },
            function(message,headers) {
                $scope.$watch('fieldcontainer',
                    function() {
                        console.log("model is dirty.");
                        if ($scope.visibility.saveButton) {
                            $('#saveMessageButtonRow').effect("bounce", { times:5, direction: 'right' }, 300);
                        }
                    }, true);
            });

Solo sigo pensando que debe haber una forma más limpia de hacerlo que proteger mi código de "ensuciamiento de la interfaz de usuario" con un "if (dirtyCount> 0)" ...


También necesito poder volver a cargar $ scope.fieldcontainer en función de ciertos botones (por ejemplo, cargar una versión anterior de la entidad). Para esto, tendría que suspender el reloj, volver a cargarlo y luego reanudarlo.
Kevin Hoffman

¿Consideraría cambiar mi respuesta como la respuesta aceptada? Aquellos que se toman el tiempo de leer hasta el final tienden a estar de acuerdo en que es la solución correcta.
trixtur

@trixtur Tengo el mismo problema de OP, pero en mi caso su respuesta no funciona, porque mi valor anterior no lo es undefined. Tiene un valor predeterminado que es necesario en el caso de que la actualización de mi modelo no proporcione toda la información. Por lo tanto, algunos valores no cambian pero tienen que activarse.
oliholz

@oliholz ​​Entonces, en lugar de verificar contra indefinido, saque el "typeof" y compruebe el contenedor old_fieldcon su valor predeterminado.
trixtur

@KevinHoffman, debe marcar la respuesta de MW en la respuesta correcta para otras personas que encuentren esta pregunta. Es una solución mucho mejor. ¡Gracias!
Dev01

Respuestas:


118

establecer una bandera justo antes de la carga inicial,

var initializing = true

y luego, cuando se dispara el primer $ watch,

$scope.$watch('fieldcontainer', function() {
  if (initializing) {
    $timeout(function() { initializing = false; });
  } else {
    // do whatever you were going to do
  }
});

La bandera se derribará justo al final del ciclo de resumen actual, por lo que el próximo cambio no se bloqueará.


17
Sí, eso funcionará, pero la comparación del enfoque de valor antiguo y nuevo sugerido por @MW. es a la vez más simple y más idiomático angular. ¿Puedes actualizar la respuesta aceptada?
gerryster

2
Siempre se agregó el valor antiguo para igualar el valor nuevo en el primer disparo específicamente para evitar tener que hacer esta piratería de bandera
Charlie Martin

2
La respuesta de MW es realmente incorrecta ya que el nuevo valor del valor anterior no manejará el caso en el que el valor anterior no está definido.
trixtur

1
Para los comentaristas: este no es un modelo divino, nada le impide poner la variable 'inicializada' dentro del alcance de un decorador, y luego decorar su reloj "normal" con ella. En cualquier caso, eso está completamente fuera del alcance de esta pregunta.
reescrito

1
Consulte plnkr.co/edit/VrWrHHWwukqnxaEM3J5i para comparar los métodos propuestos, tanto para configurar observadores en la devolución de llamada .get () como para la inicialización del alcance.
reescrito

483

La primera vez que se llama al oyente, el valor antiguo y el nuevo valor serán idénticos. Entonces solo haz esto:

$scope.$watch('fieldcontainer', function(newValue, oldValue) {
  if (newValue !== oldValue) {
    // do whatever you were going to do
  }
});

Esta es realmente la forma en que los documentos de Angular recomiendan manejarlo :

Después de registrar un observador con el ámbito, se llama al oyente fn de forma asíncrona (a través de $ evalAsync) para inicializar el observador. En casos raros, esto no es deseable porque se llama al oyente cuando el resultado de watchExpression no cambió. Para detectar este escenario dentro del oyente fn, puede comparar newVal y oldVal. Si estos dos valores son idénticos (===), se llamó al oyente debido a la inicialización


3
de esta manera es más idiomático que la respuesta de @ rewritten
Jiří Vypědřík

25
Esto no es correcto. El punto es ignorar el cambio desde nullel valor cargado inicial, no ignorar el cambio cuando no hay cambio.
reescrito el

1
Bueno, la función de escucha siempre se activará cuando se cree el reloj. Esto ignora esa primera llamada, que es lo que creo que el autor de la pregunta quería. Si quiere ignorar específicamente el cambio cuando el valor anterior era nulo, por supuesto, puede comparar oldValue con null ... pero no creo que eso sea lo que quería hacer.
MW.

El OP pregunta específicamente sobre los observadores de los recursos (de los servicios REST). Primero se crean los recursos, luego se aplican los observadores y luego se escriben los atributos de la respuesta, y solo entonces Angular hace un ciclo. Omitir el primer ciclo con un indicador de "inicialización" proporciona una manera de aplicar un observador solo en los recursos inicializados, pero aún está atento a los cambios de / a null.
reescrito

77
Esto está mal, oldValueserá nulo en la primera vuelta.
Kevin C.

42

Me doy cuenta de que esta pregunta ha sido respondida, sin embargo, tengo una sugerencia:

$scope.$watch('fieldcontainer', function (new_fieldcontainer, old_fieldcontainer) {
    if (typeof old_fieldcontainer === 'undefined') return;

    // Other code for handling changed object here.
});

El uso de banderas funciona pero tiene un poco de olor a código , ¿no le parece?


1
Este es el camino a seguir. En Coffeescript, es tan simple como return unless oldValue?(? Es el operador existencial en CS).
Kevin C.

1
Atrezzo en el código de la palabra olor! También echa un vistazo a dios objeto lol. demasiado bueno.
Matthew Harwood el

1
Aunque esta no es la respuesta aceptada, esto hizo que mi código funcionara al completar un objeto desde una API y quiero ver diferentes estados de guardado. Muy agradable.
Lucas Reppe Welander

1
Gracias, esto me ayudó
Wand Maker

1
Esto es genial, sin embargo, en mi caso, había instanciado los datos como una matriz vacía antes de hacer la llamada AJAX a mi API, así que verifique la longitud de eso en lugar de escribir. Pero esta respuesta definitivamente muestra el método más efectivo al menos.
Saborknight

4

Durante la carga inicial de los valores actuales, el campo de valor antiguo no está definido. Entonces, el siguiente ejemplo lo ayuda a excluir las cargas iniciales.

$scope.$watch('fieldcontainer', 
  function(newValue, oldValue) {
    if (newValue && oldValue && newValue != oldValue) {
      // here what to do
    }
  }), true;

¡Probablemente quiera usar! == desde entonces nully undefinedcoincidirá en esta situación, o ( '1' == 1etc.)
Jamie Pate

En general tienes razón. Pero en ese caso, newValue y oldValue no pueden ser valores de esquina debido a las comprobaciones anteriores. Ambos valores tienen el mismo tipo también porque pertenecen al mismo campo. Gracias.
Tahsin Turkoz

3

Solo valido el estado del nuevo val:

$scope.$watch('fieldcontainer',function(newVal) {
      if(angular.isDefined(newVal)){
          //Do something
      }
});
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.