Bagaimana saya mengabaikan beban awal saat menonton perubahan model di AngularJS?

184

Saya memiliki halaman web yang berfungsi sebagai editor untuk satu entitas, yang duduk sebagai grafik yang dalam di properti $ scope.fieldcontainer. Setelah saya mendapat tanggapan dari REST API saya (melalui $ resource), saya menambahkan arloji ke 'fieldcontainer'. Saya menggunakan arloji ini untuk mendeteksi apakah halaman / entitas "kotor". Saat ini saya sedang membuat tombol simpan memantul tetapi saya benar-benar ingin membuat tombol simpan tidak terlihat sampai pengguna memasukkan model.

Apa yang saya dapatkan adalah pemicu tunggal arloji, yang saya pikir sedang terjadi karena tugas .fieldcontainer = ... dilakukan segera setelah saya membuat arloji saya. Saya berpikir untuk hanya menggunakan properti "dirtyCount" untuk menyerap alarm palsu awal tapi itu terasa sangat berantakan ... dan saya pikir harus ada cara "Angular idiomatic" untuk menangani ini - saya bukan satu-satunya menggunakan arloji untuk mendeteksi model kotor.

Berikut kode tempat saya menyetel jam tangan:

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

Saya hanya terus berpikir harus ada cara yang lebih bersih untuk melakukan ini daripada menjaga kode "UI dirtying" saya dengan "if (dirtyCount> 0)" ...

Kevin Hoffman
sumber
Saya juga harus dapat memuat ulang $ scope.fieldcontainer berdasarkan tombol-tombol tertentu (misalnya memuat versi entitas sebelumnya). Untuk ini, saya perlu menangguhkan arloji, memuat ulang, lalu melanjutkan arloji.
Kevin Hoffman
Apakah Anda mempertimbangkan mengubah jawaban saya menjadi jawaban yang diterima? Mereka yang meluangkan waktu untuk membaca sampai tuntas cenderung setuju bahwa itu adalah solusi yang tepat.
trixtur
@trixtur Saya memiliki masalah OP yang sama, tetapi dalam kasus saya jawaban Anda tidak berfungsi, karena nilai lama saya tidak undefined. Ini memiliki nilai default yang diperlukan jika pembaruan model saya tidak muncul dengan semua informasi. Jadi beberapa nilai tidak dapat berubah tetapi harus dipicu.
oliholz
@oliholz ​​Jadi, alih-alih memeriksa terhadap yang tidak ditentukan, keluarkan "typeof" dan periksa old_fieldcontainer terhadap nilai default Anda.
trixtur
@KevinHoffman Anda harus menandai jawaban MW di jawaban yang benar untuk orang lain yang menemukan pertanyaan ini. Ini solusi yang jauh lebih baik. Terima kasih!
Dev01

Jawaban:

118

mengatur bendera sebelum memuat awal,

var initializing = true

dan kemudian ketika $ watch pertama menyala, lakukan

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

Bendera akan dihancurkan tepat di akhir siklus intisari saat ini, jadi perubahan selanjutnya tidak akan diblokir.

ditulis ulang
sumber
17
Ya, itu akan berhasil tetapi membandingkan pendekatan nilai lama dan baru yang disarankan oleh @MW. keduanya lebih sederhana dan lebih idiomatik. Bisakah Anda memperbarui jawaban yang diterima?
gerryster
2
Selalu mengatur oldValue agar sama dengan NewValue pada api pertama ditambahkan secara khusus untuk menghindari keharusan melakukan peretasan flag ini
Charlie Martin
2
Jawaban MW sebenarnya salah karena nilai baru ke nilai lama tidak akan menangani kasus di mana nilai lama tidak terdefinisi.
trixtur
1
Untuk komentator: ini bukan model dewa, tidak ada yang menghalangi Anda untuk meletakkan variabel 'diinisialisasi' di dalam lingkup dekorator, dan kemudian menghias jam tangan "normal" Anda dengannya. Dalam kasus apa pun yang sepenuhnya di luar ruang lingkup pertanyaan ini.
ditulis ulang
1
Lihat plnkr.co/edit/VrWrHHWwukqnxaEM3J5i untuk perbandingan metode yang disarankan, keduanya mengatur pengamat dalam panggilan balik .get (), dan inisialisasi lingkup.
ditulis ulang
483

Pertama kali pendengar dipanggil, nilai lama dan nilai baru akan identik. Jadi lakukan saja ini:

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

Ini sebenarnya cara yang direkomendasikan oleh dokumen Angular untuk menanganinya :

Setelah pengamat terdaftar dengan ruang lingkup, pendengar fn disebut asinkron (melalui $ evalAsync) untuk menginisialisasi pengamat. Dalam kasus yang jarang terjadi, ini tidak diinginkan karena pendengar dipanggil ketika hasil dari watchExpression tidak berubah. Untuk mendeteksi skenario ini di dalam listener fn, Anda dapat membandingkan newVal dan oldVal. Jika kedua nilai ini identik (===) maka pendengar dipanggil karena inisialisasi

MW.
sumber
3
cara ini lebih idiomatis daripada jawaban @ ditulis ulang
Jiří Vypědřík
25
Ini tidak benar. Intinya adalah abaikan perubahan dari nullke nilai awal yang dimuat, bukan untuk mengabaikan perubahan ketika tidak ada perubahan.
ditulis ulang
1
Nah, fungsi pendengar akan selalu terpicu saat arloji dibuat. Ini mengabaikan panggilan pertama itu, yang menurut saya diinginkan oleh si penanya. Jika dia secara khusus ingin mengabaikan perubahan ketika nilai lama adalah nol, tentu saja dia dapat membandingkan oldValue ke nol ... tapi saya tidak berpikir itu yang ingin dia lakukan.
MW.
OP secara khusus menanyakan tentang pengamat sumber daya (dari layanan REST). Sumber daya pertama kali diciptakan, kemudian pengamat diterapkan, dan kemudian atribut dari respons ditulis, dan hanya kemudian Angular membuat siklus. Melewati siklus pertama dengan bendera "inisialisasi" menyediakan cara untuk menerapkan pengamat hanya pada sumber daya yang diinisialisasi, tetapi masih melihat perubahan dari / ke null.
ditulis ulang
7
Ini salah, oldValueakan menjadi nol pada go-around pertama.
Kevin C.
42

Saya menyadari pertanyaan ini telah dijawab, namun saya punya saran:

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

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

Menggunakan flags berfungsi tetapi memiliki sedikit bau kode untuk itu bukan?

trixtur
sumber
1
Ini cara untuk pergi. Dalam Coffeescript, sesederhana return unless oldValue?(? Adalah operator eksistensial di CS).
Kevin C.
1
Alat peraga pada bau kode kata! lihat juga objek dewa lol. terlalu bagus.
Matthew Harwood
1
Meskipun ini bukan jawaban yang diterima, ini membuat kode saya berfungsi ketika mengisi suatu objek dari API dan saya ingin menonton berbagai status penyimpanan. Sangat bagus.
Lucas Reppe Welander
1
Terima kasih - ini membantu saya
Pembuat Tongkat
1
Ini bagus, namun dalam kasus saya, saya telah instantiated data sebagai array kosong sebelum melakukan panggilan AJAX ke API saya, jadi periksa panjangnya daripada jenis. Tetapi jawaban ini jelas menunjukkan metode yang paling efektif setidaknya.
Saborknight
4

Selama pemuatan awal dari nilai saat ini, bidang nilai lama tidak terdefinisi. Jadi contoh di bawah ini membantu Anda untuk mengecualikan pemuatan awal.

$scope.$watch('fieldcontainer', 
  function(newValue, oldValue) {
    if (newValue && oldValue && newValue != oldValue) {
      // here what to do
    }
  }), true;
Tahsin Turkoz
sumber
Anda mungkin ingin menggunakan! == sejak nulldan undefinedakan cocok dalam situasi ini, atau ( '1' == 1dll)
Jamie Pate
secara umum Anda benar. Tetapi dalam kasus itu, newValue dan oldValue tidak bisa menjadi nilai sudut seperti itu karena pemeriksaan sebelumnya. Kedua nilai memiliki tipe yang sama juga karena milik bidang yang sama. Terima kasih.
Tahsin Turkoz
3

Hanya validasikan keadaan val baru:

$scope.$watch('fieldcontainer',function(newVal) {
      if(angular.isDefined(newVal)){
          //Do something
      }
});
Luis Saraza
sumber