$ смотреть объект


317

Я хочу следить за изменениями в словаре, но по какой-то причине смотреть обратный вызов не вызывается.

Вот контроллер, который я использую:

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

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

Вот скрипка .

Я ожидаю, что обратный вызов $ watch будет срабатывать при каждом изменении имени или фамилии, но этого не происходит.

Как правильно это сделать?


Ответы:


570

Вызов $watchс trueтретьим аргументом:

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

По умолчанию при сравнении двух сложных объектов в JavaScript они проверяются на «ссылочное» равенство, которое спрашивает, ссылаются ли два объекта на одно и то же, а не на «значение», которое проверяет, являются ли значения всех свойств этих объектов объекты равны.

Согласно документации Angular , третий параметр предназначен для objectEquality:

Когда objectEquality == true, неравенство watchExpression определяется в соответствии с angular.equalsфункцией. Для сохранения значения объекта для последующего сравнения используется angular.copyфункция. Следовательно, это означает, что просмотр сложных объектов будет иметь неблагоприятные последствия для памяти и производительности.


2
Это хороший ответ, но я думаю, что бывают случаи, когда вы не захотите рекурсивно наблюдать за объектом
Джейсон

16
Есть. По умолчанию, если вы не передадите true, то будет проверяться равенство ссылок, если отслеживаемое значение является объектом или массивом. В этом случае, вы должны указать свойства явно, как $scope.$watch('form.name')и$scope.$watch('form.surname')
rossipedia

3
Спасибо. Это именно то, что я пропустил. Джейсон, ты прав - ты не всегда хочешь наблюдать за рекурсивными объектами, но в моем случае это было именно то, что мне было нужно.
Владимир Сидоренко

1
ImmutableJS и эталонное сравнение могут помочь повысить производительность.
Драгос Русу

13

formОбъект не меняется, только nameсвойство

обновленная скрипка

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

$scope.changeCount = 0;
$scope.$watch('form.name', function(newVal, oldVal){
    console.log('changed');
    $scope.changeCount++;
});
}

12

Небольшой совет по производительности, если у кого-то есть сервис хранилища данных с парой ключ -> значение:

Если у вас есть служба с именем dataStore , вы можете обновлять временную метку всякий раз, когда изменяется ваш большой объект данных. Таким образом, вместо глубокого наблюдения за всем объектом, вы смотрите только временную метку для изменения.

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

});

И в директиве вы смотрите только метку времени, чтобы изменить

app.directive("myDir", function ($scope, dataStore) {
    $scope.dataStore = dataStore;
    $scope.$watch('dataStore.getChange("myKey")', function(newVal, oldVal){
        if(newVal !== oldVal && newVal){
            // Data changed
        }
    });
});

1
Я думаю, что стоит упомянуть, что в этом случае вы потеряете функциональность доступа к старому значению данных, которые вы храните. newVal, oldValВ этом примере , являются значения временных меток не наблюдаемые объекты значения. Это не делает этот ответ недействительным, я просто думаю, что это недостаток, который стоит упомянуть.
Михал Шильманн

Правда, этот метод я чаще всего использую, когда хочу загрузить сложную структуру данных в качестве источника чего-либо (например, новую версию некоторых данных). Если я забочусь о новых и старых значениях, я бы не хранил их в больших сложных объектах, скорее, у меня было бы небольшое текстовое представление того, что мне важно, и наблюдал бы это за изменениями или получал их при изменении метки времени.
Ференц Такач

5

Причина, по которой ваш код не работает, заключается в том, что $watchпо умолчанию выполняется проверка ссылок. Итак, в двух словах, убедитесь, что объект, который передается ему, является новым объектом. Но в вашем случае вы просто изменяете какое-то свойство объекта формы, а не создаете новое. Чтобы заставить его работать, вы можете передать trueв качестве третьего параметра.

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

Это будет работать, но вы можете использовать $ watchCollection, который будет более эффективным, чем $ watch, потому что $watchCollectionбудет следить за поверхностными свойствами объекта формы. Например

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

4

Поскольку вы ищете изменения объекта формы, лучший подход к просмотру - использовать
$watchCollection. Пожалуйста, ознакомьтесь с официальной документацией для различных характеристик производительности.


1

Попробуй это:

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

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

    $scope.$watch('form.name', track);
}

0

Для тех, кто хочет наблюдать за изменением объекта в массиве объектов, мне показалось, что это работает (как и другие решения на этой странице):

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

-3

вы должны изменить в $ 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>


8
Отвечая на двухлетний вопрос с ответом, который является почти точной копией существующего? Не круто.
Джаред Смит
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.