Saya mencari praktik terbaik tentang cara mengikat ke properti layanan di AngularJS.
Saya telah bekerja melalui beberapa contoh untuk memahami cara mengikat properti di layanan yang dibuat menggunakan AngularJS.
Di bawah ini saya memiliki dua contoh cara mengikat properti di layanan; keduanya bekerja. Contoh pertama menggunakan binding dasar dan contoh kedua menggunakan $ scope. $ Watch untuk mengikat ke properti layanan
Apakah salah satu dari contoh ini lebih disukai ketika mengikat ke properti dalam layanan atau apakah ada opsi lain yang saya tidak tahu yang akan direkomendasikan?
Premis dari contoh-contoh ini adalah bahwa layanan harus memperbarui propertinya "lastUpdated" dan "calls" setiap 5 detik. Setelah properti layanan diperbarui, tampilan harus mencerminkan perubahan ini. Kedua contoh ini berhasil; Saya ingin tahu apakah ada cara yang lebih baik untuk melakukannya.
Binding Dasar
Kode berikut dapat dilihat dan dijalankan di sini: http://plnkr.co/edit/d3c16z
<html>
<body ng-app="ServiceNotification" >
<div ng-controller="TimerCtrl1" style="border-style:dotted">
TimerCtrl1 <br/>
Last Updated: {{timerData.lastUpdated}}<br/>
Last Updated: {{timerData.calls}}<br/>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.js"></script>
<script type="text/javascript">
var app = angular.module("ServiceNotification", []);
function TimerCtrl1($scope, Timer) {
$scope.timerData = Timer.data;
};
app.factory("Timer", function ($timeout) {
var data = { lastUpdated: new Date(), calls: 0 };
var updateTimer = function () {
data.lastUpdated = new Date();
data.calls += 1;
console.log("updateTimer: " + data.lastUpdated);
$timeout(updateTimer, 5000);
};
updateTimer();
return {
data: data
};
});
</script>
</body>
</html>
Cara lain saya memecahkan ikatan ke properti layanan adalah dengan menggunakan $ scope. $ Watch di controller.
$ scope. $ watch
Kode berikut dapat dilihat dan dijalankan di sini: http://plnkr.co/edit/dSBlC9
<html>
<body ng-app="ServiceNotification">
<div style="border-style:dotted" ng-controller="TimerCtrl1">
TimerCtrl1<br/>
Last Updated: {{lastUpdated}}<br/>
Last Updated: {{calls}}<br/>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.js"></script>
<script type="text/javascript">
var app = angular.module("ServiceNotification", []);
function TimerCtrl1($scope, Timer) {
$scope.$watch(function () { return Timer.data.lastUpdated; },
function (value) {
console.log("In $watch - lastUpdated:" + value);
$scope.lastUpdated = value;
}
);
$scope.$watch(function () { return Timer.data.calls; },
function (value) {
console.log("In $watch - calls:" + value);
$scope.calls = value;
}
);
};
app.factory("Timer", function ($timeout) {
var data = { lastUpdated: new Date(), calls: 0 };
var updateTimer = function () {
data.lastUpdated = new Date();
data.calls += 1;
console.log("updateTimer: " + data.lastUpdated);
$timeout(updateTimer, 5000);
};
updateTimer();
return {
data: data
};
});
</script>
</body>
</html>
Saya sadar bahwa saya dapat menggunakan $ rootscope. $ Broadcast di layanan dan $ root. $ Di controller, tetapi dalam contoh lain yang saya buat yang menggunakan $ broadcast / $ pada siaran pertama tidak ditangkap oleh pengontrol, tetapi panggilan tambahan yang disiarkan dipicu di dalam pengontrol. Jika Anda mengetahui cara untuk menyelesaikan $ rootscope. $ Masalah siaran, berikan jawaban.
Tetapi untuk menyatakan kembali apa yang saya sebutkan sebelumnya, saya ingin mengetahui praktik terbaik tentang cara mengikat ke properti layanan.
Memperbarui
Pertanyaan ini awalnya diajukan dan dijawab pada April 2013. Pada Mei 2014, Gil Birman memberikan jawaban baru, yang saya ubah sebagai jawaban yang benar. Karena jawaban Gil Birman memiliki suara yang sangat sedikit, kekhawatiran saya adalah bahwa orang yang membaca pertanyaan ini akan mengabaikan jawaban yang mendukung jawaban lain dengan lebih banyak suara. Sebelum Anda membuat keputusan tentang apa jawaban terbaik, saya sangat merekomendasikan jawaban Gil Birman.
sumber
Jawaban:
Pertimbangkan beberapa pro dan kontra dari pendekatan kedua :
0
{{lastUpdated}}
bukannya{{timerData.lastUpdated}}
, yang bisa saja dengan mudah{{timer.lastUpdated}}
, yang menurut saya lebih mudah dibaca (tapi jangan berdebat ... Saya memberikan nilai netral pada poin ini sehingga Anda memutuskan sendiri)+1 Mungkin lebih nyaman bahwa controller bertindak sebagai semacam API untuk markup sedemikian rupa sehingga jika entah bagaimana struktur model data berubah, Anda dapat (secara teori) memperbarui pemetaan API pengontrol tanpa menyentuh sebagian html.
-1 Namun, teori tidak selalu berlatih dan saya biasanya menemukan diri saya harus memodifikasi markup dan logic controller ketika perubahan dipanggil untuk, pula . Jadi upaya ekstra untuk menulis API meniadakan kelebihannya.
-1 Selanjutnya, pendekatan ini tidak terlalu KERING.
-1 Jika Anda ingin mengikat data ke
ng-model
kode Anda menjadi lebih kering karena Anda harus mengemas$scope.scalar_values
ulang pengontrol untuk membuat panggilan REST baru.-0.1 Ada kinerja kecil yang membuat pengamat ekstra. Juga, jika properti data melekat pada model yang tidak perlu diawasi dalam pengontrol tertentu mereka akan membuat overhead tambahan untuk pengamat dalam.
-1 Bagaimana jika beberapa pengontrol memerlukan model data yang sama? Itu berarti Anda memiliki beberapa API untuk diperbarui dengan setiap perubahan model.
$scope.timerData = Timer.data;
mulai terdengar sangat menggoda saat ini ... Mari selami titik terakhir ... Perubahan model seperti apa yang sedang kita bicarakan? Model di back-end (server)? Atau model yang dibuat dan hanya hidup di front-end? Dalam kedua kasus tersebut, apa yang pada dasarnya API pemetaan data termasuk dalam lapisan layanan front-end , (pabrik atau layanan sudut). (Perhatikan bahwa contoh pertama Anda - preferensi saya - tidak memiliki API di lapisan layanan , yang baik-baik saja karena cukup sederhana tidak memerlukannya.)Kesimpulannya , semuanya tidak harus dipisahkan. Dan sejauh memisahkan markup sepenuhnya dari model data, kelemahannya lebih besar dari keuntungannya.
Pengendali, secara umum tidak boleh dikotori dengan
$scope = injectable.data.scalar
. Sebaliknya, mereka harus ditaburi dengan$scope = injectable.data
's,promise.then(..)
' s, dan$scope.complexClickAction = function() {..}
'sSebagai pendekatan alternatif untuk mencapai data-decoupling dan dengan demikian view-encapsulation, satu-satunya tempat yang benar-benar masuk akal untuk memisahkan pandangan dari model adalah dengan arahan . Tetapi bahkan di sana, jangan
$watch
nilai skalar dalamcontroller
ataulink
fungsi. Itu tidak akan menghemat waktu atau membuat kode lebih mudah dikelola atau dibaca. Bahkan tidak akan membuat pengujian lebih mudah karena tes yang kuat di sudut biasanya menguji DOM yang dihasilkan pula . Sebaliknya, dalam permintaan direktif API data Anda dalam bentuk objek, dan nikmatilah hanya dengan yang$watch
dibuat olehng-bind
.Contoh http://plnkr.co/edit/MVeU1GKRTN4bqA3h9Yio
MEMPERBARUI : Saya akhirnya kembali ke pertanyaan ini untuk menambahkan bahwa saya tidak berpikir bahwa kedua pendekatan itu "salah". Awalnya saya telah menulis bahwa jawaban Josh David Miller tidak benar, tetapi dalam retrospeksi poinnya sepenuhnya valid, terutama pendapatnya tentang pemisahan kekhawatiran.
Mengesampingkan kekhawatiran (tetapi terkait secara tangensial), ada alasan lain untuk menyalin defensif yang gagal saya pertimbangkan. Pertanyaan ini sebagian besar berkaitan dengan membaca data langsung dari suatu layanan. Tetapi bagaimana jika pengembang di tim Anda memutuskan bahwa pengontrol perlu mengubah data dengan cara yang sepele sebelum tampilan menampilkannya? (Apakah pengontrol harus mengubah data sama sekali adalah diskusi lain.) Jika dia tidak membuat salinan objek terlebih dahulu, dia mungkin tanpa disadari menyebabkan regresi dalam komponen tampilan lain yang menggunakan data yang sama.
Apa yang benar-benar disoroti oleh pertanyaan ini adalah kekurangan arsitektural dari aplikasi sudut tipikal (dan benar-benar aplikasi JavaScript apa pun): penggabungan perhatian yang ketat, dan mutabilitas objek. Saya baru-baru ini menjadi terpikat dengan aplikasi arsitek dengan Bereaksi dan struktur data yang tidak berubah. Melakukan hal itu akan menyelesaikan dua masalah berikut dengan luar biasa:
Pemisahan masalah : Komponen mengkonsumsi semua data itu melalui alat peraga dan memiliki sedikit ketergantungan pada singleton global (seperti layanan Angular), dan tidak tahu apa-apa tentang apa yang terjadi di atasnya dalam hierarki tampilan.
Mutabilitas : Semua properti tidak dapat diubah yang menghilangkan risiko mutasi data tanpa disadari.
Angular 2.0 sekarang di jalur untuk meminjam banyak dari Bereaksi untuk mencapai dua poin di atas.
sumber
Dari sudut pandang saya,
$watch
akan menjadi cara praktik terbaik.Anda sebenarnya dapat menyederhanakan contoh Anda sedikit:
Itu yang kamu butuhkan.
Karena properti diperbarui secara bersamaan, Anda hanya perlu satu arloji. Juga, karena mereka berasal dari objek tunggal, agak kecil, saya mengubahnya menjadi hanya menonton
Timer.data
properti. Parameter terakhir yang diteruskan untuk$watch
memberitahukannya adalah untuk memeriksa kesetaraan yang dalam, bukan hanya memastikan bahwa rujukannya sama.Untuk memberikan sedikit konteks, alasan saya lebih suka metode ini untuk menempatkan nilai layanan langsung pada ruang lingkup adalah untuk memastikan pemisahan masalah yang tepat. Pandangan Anda seharusnya tidak perlu tahu apa-apa tentang layanan Anda untuk beroperasi. Tugas pengendali adalah merekatkan semuanya menjadi satu; tugasnya adalah untuk mendapatkan data dari layanan Anda dan memprosesnya dengan cara apa pun yang diperlukan dan kemudian memberikan pandangan Anda dengan spesifik apa pun yang dibutuhkan. Tapi saya tidak berpikir tugasnya adalah hanya melewati layanan tepat ke tampilan. Kalau tidak, apa yang dilakukan bahkan oleh pengontrol di sana? Pengembang AngularJS mengikuti alasan yang sama ketika mereka memilih untuk tidak memasukkan "logika" dalam template (misalnya
if
pernyataan).Agar adil, mungkin ada beberapa perspektif di sini dan saya menantikan jawaban lain.
sumber
{{lastUpdated}}
vs.{{timerData.lastUpdated}}
Timer.data
dalam $ watch,Timer
harus didefinisikan pada $ scope, karena ekspresi string yang Anda berikan ke $ watch dievaluasi terhadap scope. Berikut adalah penyedot yang menunjukkan cara membuatnya. Parameter objectEquality didokumentasikan di sini - parameter ke-3 - tetapi tidak terlalu dijelaskan dengan baik.$watch
sangat tidak efisien. Lihat jawaban di stackoverflow.com/a/17558885/932632 dan stackoverflow.com/questions/12576798/…Terlambat ke pesta, tetapi untuk Googler masa depan - jangan gunakan jawaban yang disediakan.
JavaScript memiliki mekanisme melewati objek dengan referensi, sementara itu hanya melewati salinan dangkal untuk nilai "angka, string dll".
Dalam contoh di atas, alih-alih mengikat atribut layanan, mengapa kita tidak mengekspos layanan ke ruang lingkup?
Pendekatan sederhana ini akan membuat sudut mampu melakukan ikatan dua arah dan semua hal ajaib yang Anda butuhkan. Jangan meretas pengontrol Anda dengan pengamat atau markup yang tidak dibutuhkan.
Dan jika Anda khawatir tampilan Anda secara tidak sengaja menimpa atribut layanan Anda, gunakan
defineProperty
untuk membuatnya dapat dibaca, dihitung, dapat dikonfigurasi, atau mendefinisikan getter dan setter. Anda bisa mendapatkan banyak kendali dengan membuat layanan Anda lebih solid.Kiat terakhir: jika Anda menghabiskan waktu bekerja pada pengontrol Anda lebih dari layanan Anda maka Anda salah melakukannya :(.
Dalam kode demo tertentu yang Anda berikan, saya sarankan Anda lakukan:
Edit:
Seperti yang saya sebutkan di atas, Anda dapat mengontrol perilaku atribut layanan Anda menggunakan
defineProperty
Contoh:
Sekarang di pengontrol kami jika kami melakukannya
layanan kami akan mengubah nilai
propertyWithSetter
dan juga mengirim nilai baru ke basis data entah bagaimana!Atau kita dapat mengambil pendekatan apa pun yang kita inginkan.
Lihat dokumentasi MDN untuk
defineProperty
.sumber
$scope.model = {timerData: Timer.data};
, hanya melampirkannya pada model alih-alih langsung pada Lingkup.Saya pikir pertanyaan ini memiliki komponen kontekstual.
Jika Anda hanya menarik data dari layanan & memancarkan informasi itu ke tampilan itu, saya pikir mengikat langsung ke properti layanan baik-baik saja. Saya tidak ingin menulis banyak kode boilerplate untuk hanya memetakan properti layanan ke model properti untuk dikonsumsi dalam pandangan saya.
Selanjutnya, kinerja dalam sudut didasarkan pada dua hal. Yang pertama adalah berapa banyak binding yang ada di halaman. Yang kedua adalah seberapa mahal fungsi pengambil. Misko berbicara tentang ini di sini
Jika Anda perlu melakukan contoh logika spesifik pada data layanan (sebagai lawan dari pemijatan data yang diterapkan dalam layanan itu sendiri), dan hasil ini berdampak pada model data yang terpapar pada tampilan, maka saya akan mengatakan $ watcher sesuai, karena selama fungsinya tidak terlalu mahal. Dalam hal fungsi yang mahal, saya akan menyarankan caching hasil dalam variabel lokal (ke controller), melakukan operasi kompleks Anda di luar fungsi $ watcher, dan kemudian mengikat cakupan Anda ke hasil itu.
Sebagai peringatan, Anda tidak boleh menggantung properti apa pun langsung dari ruang lingkup $ Anda. The
$scope
variabel TIDAK Anda Model. Ini memiliki referensi ke model Anda.Dalam pikiran saya, "praktik terbaik" untuk sekadar memancarkan informasi dari layanan ke bawah untuk melihat:
Dan kemudian tampilan Anda akan berisi
{{model.timerData.lastupdated}}
.sumber
Membangun contoh-contoh di atas saya pikir saya akan melemparkan cara pengikatan variabel pengontrol secara transparan ke variabel layanan.
Dalam contoh di bawah ini, perubahan ke
$scope.count
variabel Kontroler akan secara otomatis tercermin dalamcount
variabel Layanan .Dalam produksi, kami benar-benar menggunakan pengikatan ini untuk memperbarui id pada layanan yang kemudian secara asinkron mengambil data dan memperbarui vars layanannya. Pengikatan lebih lanjut itu berarti bahwa pengontrol secara otomatis diperbarui ketika layanan memperbarui itu sendiri.
Kode di bawah ini dapat dilihat berfungsi di http://jsfiddle.net/xuUHS/163/
Melihat:
Layanan / Pengendali:
sumber
Saya pikir ini adalah cara yang lebih baik untuk mengikat layanan itu sendiri daripada atribut di dalamnya.
Inilah alasannya:
Anda dapat memainkannya di plunker ini .
sumber
Saya lebih suka menjaga pengamat saya sesedikit mungkin. Alasan saya didasarkan pada pengalaman saya dan orang mungkin membantahnya secara teoritis.
Masalah dengan menggunakan pengamat adalah Anda dapat menggunakan properti apa pun pada ruang lingkup untuk memanggil metode apa pun dalam komponen atau layanan apa pun yang Anda suka.
Dalam proyek dunia nyata, segera Anda akan berakhir dengan non-tracable (lebih baik mengatakan sulit untuk jejak) rantai metode yang disebut dan nilai-nilai yang berubah yang khusus membuat proses on-boarding tragis.
sumber
Untuk mengikat data apa pun, yang mengirimkan layanan bukan ide yang baik (arsitektur), tetapi jika Anda membutuhkannya lagi saya sarankan Anda 2 cara untuk melakukan itu
1) Anda bisa mendapatkan data tidak di dalam layanan Anda. Anda bisa mendapatkan data di dalam pengontrol / arahan Anda dan Anda tidak akan memiliki masalah untuk mengikatnya di mana saja
2) Anda dapat menggunakan peristiwa angular. Kapan pun Anda mau, Anda dapat mengirim sinyal (dari $ rootScope) dan menangkapnya di mana pun Anda inginkan. Anda bahkan dapat mengirim data pada eventName itu.
Mungkin ini bisa membantu Anda. Jika Anda membutuhkan lebih banyak dengan contoh, ini tautannya
http://www.w3docs.com/snippets/angularjs/bind-value-between-service-and-controller-directive.html
sumber
Bagaimana dengan
Di mana ParentScope adalah layanan yang disuntikkan?
sumber
Solusi Paling Elegan ...
Juga, saya menulis EDA (Event-Driven Architecture) jadi saya cenderung melakukan sesuatu seperti [versi yang disederhanakan] berikut:
Kemudian, saya menempatkan pendengar di controller saya pada saluran yang diinginkan dan tetap perbarui lingkup lokal saya dengan cara ini.
Kesimpulannya, tidak ada banyak "Praktik Terbaik" - melainkan, sebagian besar preferensi - selama Anda menjaga hal-hal SOLID dan menggunakan kopling yang lemah. Alasan saya mendukung kode yang terakhir adalah karena EDA memiliki kopling terendah yang layak secara alami. Dan jika Anda tidak terlalu khawatir tentang fakta ini, mari kita hindari bekerja sama dalam proyek yang sama.
Semoga ini membantu...
sumber