$ obejrzyj obiekt

317

Chcę obserwować zmiany w słowniku, ale z jakiegoś powodu funkcja oddzwaniania nie jest wywoływana.

Oto kontroler, którego używam:

function MyController($scope) {
    $scope.form = {
        name: 'my name',
        surname: 'surname'
    }

    $scope.$watch('form', function(newVal, oldVal){
        console.log('changed');
    });
}

Oto skrzypce .

Oczekuję, że oddzwanianie $ watch będzie uruchamiane przy każdej zmianie imienia lub nazwiska, ale tak się nie dzieje.

Jaki jest właściwy sposób to zrobić?

Vladimir Sidorenko
źródło

Odpowiedzi:

570

Zadzwoń $watchz truejako trzeci argument:

$scope.$watch('form', function(newVal, oldVal){
    console.log('changed');
}, true);

Domyślnie podczas porównywania dwóch złożonych obiektów w JavaScript, będą sprawdzane pod kątem równości „referencyjnej”, która pyta, czy dwa obiekty odnoszą się do tej samej rzeczy, a nie równości „wartościowej”, która sprawdza, czy wartości wszystkich właściwości tych właściwości obiekty są równe.

Zgodnie z dokumentacją Angular trzeci parametr dotyczy objectEquality:

Kiedy objectEquality == truenierówność wyrażenia watchExpression jest określana zgodnie z angular.equalsfunkcją. Aby zapisać wartość obiektu do późniejszego porównania, angular.copyużywana jest funkcja. Oznacza to zatem, że oglądanie złożonych obiektów będzie miało negatywny wpływ na pamięć i wydajność.

rossipedia
źródło
2
To dobra odpowiedź, ale wyobrażam sobie, że zdarzają się sytuacje, w których nie chciałbyś rekurencyjnie oglądać obiektu
Jason,
16
Tam są. Domyślnie, jeśli nie przejdziesz true, sprawdzi równość odniesienia, jeśli obserwowana wartość jest obiektem lub tablicą. W takim przypadku należy jawnie określić właściwości, takie jak $scope.$watch('form.name')i$scope.$watch('form.surname')
rossipedia 18.10.13
3
Dzięki. Właśnie tego mi brakowało. Jason, masz rację - nie zawsze chcesz oglądać obiekty rekurencyjne, ale w moim przypadku było to dokładnie to, czego potrzebowałem.
Vladimir Sidorenko
1
ImmutableJS i porównanie referencyjne może pomóc zwiększyć wydajność.
Dragos Rusu,
13

formObiekt nie zmienia, tylko namewłaściwość jest

zaktualizowane skrzypce

function MyController($scope) {
$scope.form = {
    name: 'my name',
}

$scope.changeCount = 0;
$scope.$watch('form.name', function(newVal, oldVal){
    console.log('changed');
    $scope.changeCount++;
});
}
Jason
źródło
12

Mała wskazówka dotycząca wydajności, jeśli ktoś ma usługę magazynu danych z kluczem -> parami wartości:

Jeśli masz usługę o nazwie dataStore , możesz aktualizować znacznik czasu za każdym razem, gdy zmienia się Twój obiekt big data. W ten sposób zamiast głębokiego oglądania całego obiektu, obserwujesz jedynie znacznik czasu dla zmiany.

app.factory('dataStore', function () {

    var store = { data: [], change: [] };

    // when storing the data, updating the timestamp
    store.setData = function(key, data){
        store.data[key] = data;
        store.setChange(key);
    }

    // get the change to watch
    store.getChange = function(key){
        return store.change[key];
    }

    // set the change
    store.setChange = function(key){
        store.change[key] = new Date().getTime();
    }

});

W dyrektywie obserwujesz tylko znacznik czasu, który chcesz zmienić

app.directive("myDir", function ($scope, dataStore) {
    $scope.dataStore = dataStore;
    $scope.$watch('dataStore.getChange("myKey")', function(newVal, oldVal){
        if(newVal !== oldVal && newVal){
            // Data changed
        }
    });
});
Ferenc Takacs
źródło
1
Myślę, że warto wspomnieć, że w tym przypadku straciłbyś funkcjonalność dostępu do starej wartości przechowywanych danych. newVal, oldValw tym przykładzie są wartościami datownika, a nie wartościami obserwowanych obiektów. To nie unieważnia tej odpowiedzi, myślę tylko, że jest to wada, o której warto wspomnieć.
Michał Schielmann
To prawda, że ​​tej metody używam głównie, gdy chcę załadować skomplikowaną strukturę danych jako źródło czegoś (na przykład nową wersję niektórych danych). Jeśli zależy mi na nowych i starych wartościach, nie zapisałbym ich w dużych, skomplikowanych obiektach, miałbym raczej małą reprezentację tekstową rzeczy, na której mi zależy, i obserwuję ją w celu zmiany lub otrzymuję, gdy zmienia się znacznik czasu.
Ferenc Takacs
5

Przyczyną tego, że Twój kod nie działa, jest $watchdomyślnie sprawdzanie referencji. Krótko mówiąc, upewnij się, że obiekt, który jest do niej przekazywany, jest nowym obiektem. Ale w twoim przypadku modyfikujesz tylko niektóre właściwości obiektu formularza, a nie tworzysz nową. Aby działało, możesz podać truejako trzeci parametr.

$scope.$watch('form', function(newVal, oldVal){
    console.log('invoked');
}, true);

Będzie działać, ale możesz użyć $ watchCollection, który będzie bardziej wydajny niż $ watch, ponieważ $watchCollectionbędzie szukał płytkich właściwości na obiekcie formularza. Na przykład

$scope.$watchCollection('form', function (newVal, oldVal) {
    console.log(newVal, oldVal);
});
sol4me
źródło
4

Gdy szukasz zmian obiektów formularza, najlepiej zastosować podejście obserwacyjne
$watchCollection. Zapoznaj się z oficjalną dokumentacją różnych cech wydajności.

Ramesh Papaganti
źródło
1

Spróbuj tego:

function MyController($scope) {
    $scope.form = {
        name: 'my name',
        surname: 'surname'
    }

    function track(newValue, oldValue, scope) {
        console.log('changed');
    };

    $scope.$watch('form.name', track);
}
Tarun
źródło
0

Dla każdego, kto chce obserwować zmianę obiektu w obrębie szeregu obiektów, wydaje mi się, że to działa dla mnie (podobnie jak inne rozwiązania na tej stronie):

function MyController($scope) {

    $scope.array = [
        data1: {
            name: 'name',
            surname: 'surname'
        },
        data2: {
            name: 'name',
            surname: 'surname'
        },
    ]


    $scope.$watch(function() {
        return $scope.data,
    function(newVal, oldVal){
        console.log(newVal, oldVal);
    }, true);
Maximillion Bartango
źródło
-3

musisz zmienić w $ watch ....

function MyController($scope) {
    $scope.form = {
        name: 'my name',
    }

    $scope.$watch('form.name', function(newVal, oldVal){
        console.log('changed');
     
    });
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.22/angular.min.js"></script>
<div ng-app>
    <div ng-controller="MyController">
        <label>Name:</label> <input type="text" ng-model="form.name"/>
            
        <pre>
            {{ form }}
        </pre>
    </div>
</div>

Vijay Patel
źródło
8
Odpowiadając na dwuletnie pytanie odpowiedzią, która jest niemal dokładnym duplikatem istniejącego? Nie fajnie.
Jared Smith