$ спостерігати об’єкт


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 вони перевіряться на "посилання" рівності, яка запитує, чи два об'єкти посилаються на одне і те ж, а не на "значення" рівності, яка перевіряє, чи значущі всі властивості цих об’єкти рівні.

Згідно з кутовою документацією , третій параметр призначений для objectEquality:

Коли objectEquality == true, нерівність годинного виразу визначається відповідно до 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у цьому прикладі є значення часових позначок, а не значення спостережуваних об'єктів. Це не робить цю відповідь недійсною, я просто думаю, що це недолік, який варто згадати.
Michał Schielmann

Правда, цей метод я в основному використовую, коли я хочу завантажити складну структуру даних як джерело для чогось (наприклад, нову версію деяких даних). Якщо мені байдуже нові та старі значення, я б не зберігав їх у великих складних об'єктах, скоріше я мав би невелике подання тексту про те, що мені цікаво, і слідкувати за зміною або отримувати його, коли часова мітка змінюється.
Ференц Такач

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.