Come posso ignorare il carico iniziale quando osservo i cambiamenti del modello in AngularJS?


184

Ho una pagina web che funge da editor per una singola entità, che funge da grafico profondo nella proprietà $ scope.fieldcontainer. Dopo aver ricevuto una risposta dalla mia API REST (tramite $ risorse), aggiungo un orologio a "fieldcontainer". Sto usando questo orologio per rilevare se la pagina / entità è "sporca". In questo momento sto facendo rimbalzare il pulsante di salvataggio, ma in realtà voglio rendere il pulsante di salvataggio invisibile fino a quando l'utente non sporca il modello.

Quello che sto ricevendo è un singolo trigger dell'orologio, che penso stia accadendo perché l'assegnazione .fieldcontainer = ... ha luogo immediatamente dopo la creazione dell'orologio. Stavo pensando di usare solo una proprietà "dirtyCount" per assorbire il falso allarme iniziale, ma mi sembra molto confuso ... e ho pensato che ci doveva essere un modo "angolare idiomatico" per affrontare questo - non sono l'unico utilizzando un orologio per rilevare un modello sporco.

Ecco il codice in cui ho impostato l'orologio:

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

Continuo a pensare che ci debba essere un modo più pulito per farlo che proteggere il mio codice "UI dirtying" con un "if (dirtyCount> 0)" ...


Devo anche essere in grado di ricaricare $ scope.fieldcontainer in base a determinati pulsanti (ad es. Caricamento di una versione precedente dell'entità). Per questo, avrei bisogno di sospendere l'orologio, ricaricare, quindi riprendere l'orologio.
Kevin Hoffman,

Considereresti di cambiare la mia risposta come la risposta accettata? Coloro che impiegano il tempo necessario per leggere fino in fondo tendono ad ammettere che è la soluzione corretta.
trixtur,

@trixtur Ho lo stesso problema OP, ma nel mio caso la tua risposta non funziona, perché il mio vecchio valore non lo è undefined. Ha un valore predefinito che è necessario nel caso in cui l'aggiornamento del mio modello non fornisca tutte le informazioni. Quindi alcuni valori non cambiano ma devono innescarsi.
oliholz,

@oliholz ​​Quindi, invece di verificare undefined, elimina "typeof" e controlla old_fieldcontainer rispetto al valore predefinito.
trixtur,

@KevinHoffman dovresti contrassegnare la risposta di MW come risposta corretta per le altre persone che trovano questa domanda. È una soluzione molto migliore. Grazie!
Dev01

Risposte:


118

imposta un flag appena prima del caricamento iniziale,

var initializing = true

e poi quando il primo $ watch spara, fallo

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

La bandiera verrà abbattuta proprio alla fine dell'attuale ciclo di digest, quindi la prossima modifica non verrà bloccata.


17
Sì, funzionerà, ma confrontando il vecchio e il nuovo approccio al valore suggerito da @MW. è sia più semplice che più idiomatico angolare. Puoi aggiornare la risposta accettata?
Gerryster,

2
Impostando sempre oldValue in modo che corrisponda a newValue al primo fuoco è stato aggiunto specificamente per evitare di dover fare questo hack bandiera
Charlie Martin

2
La risposta di MW è in realtà errata poiché il nuovo valore al vecchio valore non gestirà il caso in cui il vecchio valore non è definito.
trixtur,

1
Per i commentatori: questo non è un modello divino, nulla ti impedisce di inserire la variabile 'inizializzata' all'interno dell'ambito di un decoratore, e quindi decorare il tuo orologio "normale" con esso. In ogni caso, questo è completamente al di fuori dell'ambito di questa domanda.
riscritto il

1
Vedere plnkr.co/edit/VrWrHHWwukqnxaEM3J5i per il confronto dei metodi proposti, sia per l'impostazione degli watcher nel callback .get (), sia per l'inizializzazione dell'ambito.
riscritto il

483

La prima volta che viene chiamato l'ascoltatore, il vecchio valore e il nuovo valore saranno identici. Quindi fai solo questo:

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

Questo è in realtà il modo in cui i documenti angolari consigliano di gestirlo :

Dopo che un watcher è stato registrato con l'ambito, il listener fn viene chiamato in modo asincrono (tramite $ evalAsync) per inizializzare il watcher. In rari casi, questo è indesiderabile perché l'ascoltatore viene chiamato quando il risultato di watchExpression non è cambiato. Per rilevare questo scenario nel listener fn, puoi confrontare newVal e oldVal. Se questi due valori sono identici (===), il listener è stato chiamato a causa dell'inizializzazione


3
in questo modo è più idiomatico della risposta di @
rewritten

25
Questo non è corretto Il punto è ignorare la modifica da nullal valore caricato iniziale, non ignorare la modifica quando non vi è alcuna modifica.
riscritto il

1
Bene, la funzione di ascolto si attiverà sempre quando viene creato l'orologio. Questo ignora quella prima chiamata, che è ciò che credo volesse chi l'ha chiesto. Se vuole specificamente ignorare la modifica quando il vecchio valore era null, ovviamente può confrontare oldValue con null ... ma non penso che sia quello che voleva fare.
MW.

L'OP chiede in particolare informazioni sugli osservatori sulle risorse (dai servizi REST). Le risorse vengono prima create, quindi applicate dagli osservatori, quindi vengono scritti gli attributi della risposta e solo allora Angular compie un ciclo. Saltare il primo ciclo con un flag di "inizializzazione" fornisce un modo per applicare un watcher solo alle risorse inizializzate, ma ancora osservando le modifiche da / a null.
riscritto il

7
Questo è sbagliato, oldValuesarà nullo al primo giro.
Kevin C.

42

Mi rendo conto che a questa domanda è stata data una risposta, tuttavia ho un suggerimento:

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

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

L'uso delle bandiere funziona ma ha un po 'di odore di codice , non credi?


1
Questa è la strada da percorrere. In Coffeescript, è semplice come return unless oldValue?(? È l'operatore esistenziale in CS).
Kevin C.

1
Puntelli sull'odore del codice parola! controlla anche dio oggetto lol. troppo buono.
Matthew Harwood,

1
Sebbene questa non sia la risposta accettata, questo ha fatto funzionare il mio codice quando ho popolato un oggetto da un'API e voglio guardare diversi stati di salvataggio. Molto bella.
Lucas Reppe Welander,

1
Grazie - mi ha aiutato
Wand Maker l'

1
Questo è fantastico, tuttavia nel mio caso avevo istanziato i dati come un array vuoto prima di fare la chiamata AJAX alla mia API, quindi controlla la lunghezza invece di digitare. Ma questa risposta mostra sicuramente almeno il metodo più efficace.
Saborknight

4

Durante il caricamento iniziale dei valori correnti, il vecchio campo valore non è definito. Quindi l'esempio seguente ti aiuta ad escludere i caricamenti iniziali.

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

Probabilmente vuoi usare! == poiché nulle undefinedcorrisponderà in questa situazione, oppure ( '1' == 1ecc.)
Jamie Pate,

in generale hai ragione. Ma in tal caso newValue e oldValue non possono essere tali valori d'angolo a causa dei controlli precedenti. Entrambi i valori hanno lo stesso tipo anche perché appartengono allo stesso campo. Grazie.
Tahsin Turkoz,

3

Valido solo lo stato del nuovo val:

$scope.$watch('fieldcontainer',function(newVal) {
      if(angular.isDefined(newVal)){
          //Do something
      }
});
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.