Jak zignorować początkowe obciążenie podczas oglądania zmian modelu w AngularJS?

184

Mam stronę internetową, która służy jako edytor dla pojedynczego elementu, który znajduje się jako głęboki wykres we właściwości $ scope.fieldcontainer. Po otrzymaniu odpowiedzi z mojego interfejsu API REST (za pośrednictwem zasobu $) dodaję zegarek do 'fieldcontainer'. Używam tego zegarka do wykrywania, czy strona / jednostka jest „brudna”. W tej chwili odbijam przycisk Zapisz, ale naprawdę chcę, aby przycisk Zapisz był niewidoczny, dopóki użytkownik nie zabrudzi modelu.

To, co otrzymuję, to pojedynczy spust zegarka, który, jak sądzę, dzieje się, ponieważ przypisanie .fieldcontainer = ... ma miejsce natychmiast po utworzeniu mojego zegarka. Myślałem o użyciu po prostu właściwości „dirtyCount” do zaabsorbowania początkowego fałszywego alarmu, ale wydaje mi się to bardzo hackerskie ... i pomyślałem, że musi istnieć sposób „Angular idiomatic”, aby sobie z tym poradzić - nie jestem jedyny za pomocą zegarka, aby wykryć brudny model.

Oto kod, w którym ustawiam zegarek:

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

Po prostu myślę, że musi istnieć czystszy sposób, aby to zrobić, niż pilnowanie mojego kodu „UI dirtying” za pomocą „if (dirtyCount> 0)” ...

Kevin Hoffman
źródło
Potrzebuję też móc ponownie załadować $ scope.fieldcontainer na podstawie niektórych przycisków (np. Ładowanie poprzedniej wersji bytu). W tym celu musiałbym zawiesić zegarek, ponownie załadować, a następnie wznowić zegarek.
Kevin Hoffman
Czy rozważyłbyś zmianę mojej odpowiedzi na odpowiedź zaakceptowaną? Ci, którzy poświęcają czas na czytanie do samego końca, zwykle zgadzają się, że jest to właściwe rozwiązanie.
trixtur
@trixtur Mam ten sam problem OP, ale w moim przypadku twoja odpowiedź nie działa, ponieważ moja stara wartość nie undefined. Ma wartość domyślną, która jest niezbędna, w przypadku mojej aktualizacji modelu nie pojawiły się wszystkie informacje. Dlatego niektóre wartości nie zmieniają się, ale muszą zostać wyzwolone.
oliholz
@oliholz ​​Zamiast sprawdzać przed niezdefiniowanym, wyjmij „typeof” i sprawdź old_fieldcontainer pod kątem wartości domyślnej.
trixtur
@KevinHoffman powinieneś zaznaczyć odpowiedź MW przy poprawnej odpowiedzi dla innych osób, które znajdą to pytanie. To znacznie lepsze rozwiązanie. Dzięki!
Dev01

Odpowiedzi:

118

ustaw flagę tuż przed początkowym ładowaniem,

var initializing = true

a potem, gdy wystrzeli pierwszy zegarek, zrób

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

Flaga zostanie zburzona tuż po zakończeniu bieżącego cyklu podsumowania, więc następna zmiana nie zostanie zablokowana.

przepisane
źródło
17
Tak, to zadziała, ale porównanie starego i nowego podejścia do wartości sugerowanego przez @MW. jest zarówno prostszy, jak i bardziej kątowy idiomatyczny. Czy możesz zaktualizować zaakceptowaną odpowiedź?
gerryster
2
Zawsze ustawiam oldValue na równi newValue przy pierwszym pożarze, aby uniknąć konieczności włamania się do flagi
Charlie Martin
2
Odpowiedź MW jest w rzeczywistości niepoprawna, ponieważ nowa wartość do starej wartości nie obsługuje przypadku, w którym stara wartość jest niezdefiniowana.
trixtur
1
Dla komentatorów: to nie jest boski model, nic nie stoi na przeszkodzie, aby umieścić zmienną „inicjalizowaną” w zakresie dekoratora, a następnie ozdobić nią „normalny” zegarek. W każdym razie jest to całkowicie poza zakresem tego pytania.
przepisany
1
Zobacz plnkr.co/edit/VrWrHHWwukqnxaEM3J5i w celu porównania proponowanych metod, zarówno konfigurowania obserwatorów w wywołaniu zwrotnym .get (), jak i inicjalizacji zakresu.
przepisany
483

Przy pierwszym wywołaniu detektora stara wartość i nowa wartość będą identyczne. Po prostu zrób to:

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

W ten sposób doktorzy Angular zalecają obchodzenie się z tym :

Po zarejestrowaniu obserwatora w zakresie detektor fn jest wywoływany asynchronicznie (przez $ evalAsync) w celu zainicjowania obserwatora. W rzadkich przypadkach jest to niepożądane, ponieważ słuchacz jest wywoływany, gdy wynik watchExpression nie zmienił się. Aby wykryć ten scenariusz w odbiorniku fn, możesz porównać newVal i oldVal. Jeśli te dwie wartości są identyczne (===), wówczas detektor został wywołany z powodu inicjalizacji

MW.
źródło
3
ten sposób jest bardziej idiomatyczny niż odpowiedź @ przepisanego
Jiří Vypědřík
25
To nie jest poprawne. Chodzi o ignorowanie zmiany z nullpoczątkowej wartości załadowanej, aby nie ignorować zmiany, gdy nie ma zmiany.
przepisany
1
Cóż, funkcja nasłuchiwania zawsze będzie uruchamiana podczas tworzenia zegarka. Ignoruje to pierwsze połączenie, które moim zdaniem chciał pytający. Jeśli szczególnie chce zignorować zmianę, gdy stara wartość była zerowa, może oczywiście porównać oldValue do null ... ale nie sądzę, że to właśnie chciał zrobić.
MW.
OP dokładnie pyta o obserwatorów zasobów (z usług REST). Najpierw tworzone są zasoby, następnie stosowane obserwatory, a następnie zapisywane są atrybuty odpowiedzi, i dopiero wtedy Angular wykonuje cykl. Pominięcie pierwszego cyklu z flagą „inicjalizującą” umożliwia zastosowanie obserwatora tylko do zainicjowanych zasobów, ale nadal obserwuje zmiany od / do null.
przepisany
7
To źle, oldValueprzy pierwszym odejściu będzie zerowane.
Kevin C.
42

Zdaję sobie sprawę, że na to pytanie udzielono odpowiedzi, jednak mam sugestię:

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

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

Używanie flag działa, ale wyczuwa zapach kodu , nie sądzisz?

trixtur
źródło
1
To jest odpowiednie rozwiązanie. W Coffeescript jest to tak proste, jak return unless oldValue?(? Operator egzystencjalny w CS).
Kevin C.
1
Rekwizyty na zapach kodu słowo! sprawdź także boski obiekt lol. za dobrze.
Matthew Harwood
1
Chociaż nie jest to akceptowana odpowiedź, spowodowało to, że mój kod działał podczas zapełniania obiektu z interfejsu API i chcę oglądać różne stany zapisu. Bardzo dobrze.
Lucas Reppe Welander
1
Dzięki - pomogło mi to
Wand Maker
1
To świetnie, jednak w moim przypadku utworzyłem instancję danych jako pustą tablicę przed wykonaniem wywołania AJAX do mojego interfejsu API, więc sprawdź długość zamiast tego. Ale ta odpowiedź zdecydowanie pokazuje najskuteczniejszą metodę.
Saborknight
4

Podczas początkowego ładowania bieżących wartości stare pole wartości jest niezdefiniowane. Poniższy przykład pomaga w wykluczeniu początkowych obciążeń.

$scope.$watch('fieldcontainer', 
  function(newValue, oldValue) {
    if (newValue && oldValue && newValue != oldValue) {
      // here what to do
    }
  }), true;
Tahsin Turkoz
źródło
Prawdopodobnie chcesz użyć! == od tego czasu nulli undefinedbędzie pasować w tej sytuacji, lub ( '1' == 1itp.)
Jamie Pate
ogólnie masz rację. Ale w takim przypadku newValue i oldValue nie mogą być takimi wartościami narożnymi z powodu poprzednich kontroli. Obie wartości mają ten sam typ, ponieważ należą do tego samego pola. Dziękuję Ci.
Tahsin Turkoz
3

Po prostu popraw stan nowej wartości:

$scope.$watch('fieldcontainer',function(newVal) {
      if(angular.isDefined(newVal)){
          //Do something
      }
});
Luis Saraza
źródło