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


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.