Angular forms berfungsi dengan sangat baik dengan HTML standard. input, textarea, dan select semuanya boleh dimasukkan ke dalam Reactive Forms tanpa usaha tambahan. Kerangka kerja ini memahami acara (events), nilai, dan keadaannya.

Namun, aplikasi moden jarang bergantung kepada elemen standard sahaja. Anda mungkin memerlukan widget penarafan bintang, pemilih tarikh komposit, atau pemilih warna tersuai. Masukkan salah satu daripadanya ke dalam kumpulan borang (form group), dan Angular akan menganggapnya sebagai HTML mati. patchValue tidak melakukan apa-apa. Validator mengabaikannya. Borang tidak tahu bila pengguna berinteraksi dengan kawalan tersebut, dan form.disable() membiarkan widget tersuai itu kekal interaktif sepenuhnya.

Inilah masalah yang wujud untuk diselesaikan oleh ControlValueAccessor.

Apa Sebenarnya yang Dilakukan oleh ControlValueAccessor

ControlValueAccessor adalah kontrak yang menukarkan komponen tersuai menjadi "warganegara borang" kelas pertama. Ia bertindak sebagai penterjemah antara Angular Forms API dan UI anda sendiri. Sebaik sahaja anda melaksanakannya dengan betul, komponen anda tidak dapat dibezakan daripada input asli dari sudut pandangan borang. Ia boleh menerima nilai, mengeluarkan perubahan (emit changes), melaporkan sentuhan (touches), dan menghormati keadaan dinyahaktifkan (disabled states) sama seperti elemen terbina dalam.

Antara muka (interface) ini memerlukan empat kaedah (methods) khusus. Setiap satunya mengendalikan arah komunikasi yang berbeza.

writeValue: Borang ke Komponen

writeValue(obj) adalah laluan masuk. Setiap kali model borang dikemas kini dan perlu menolak nilai baharu ke dalam UI anda, Angular akan memanggil kaedah ini. Jika anda memanggil patchValue({ rating: 4 }) pada kumpulan borang, nilai 4 tersebut akan sampai ke dalam komponen anda melalui writeValue. Jika anda menetapkan semula (reset) borang, writeValue akan menerima nilai permulaan baharu atau null. Tugas anda di dalam kaedah ini adalah untuk mengambil data yang masuk itu dan memetakkannya ke dalam keadaan dalaman komponen anda. Jika anda membina pemilih warna, writeValue menerima rentetan hex seperti #ff4400, dan anda mesti mengemas kini paparan anda untuk menunjukkan warna tersebut sebagai yang dipilih.

Terdapat sedikit kerumitan praktikal di sini. Angular boleh memanggil writeValue sebelum paparan anda diinisialisasi sepenuhnya, terutamanya di dalam komponen yang dirender secara dinamik, dialog, atau antara muka tab. Jika komponen anda cuba menyentuh DOM atau komponen anak terlalu awal, anda boleh menghadapi ralat masa larian (runtime errors). Corak yang mantap adalah dengan menyimpan nilai tersebut dalam harta (property) tempatan dan menerapkannya selepas paparan diinisialisasi, atau untuk melindungi daripada rujukan anak yang tidak ditakrifkan (undefined). Jangan sesekali menganggap writeValue hanya dicetuskan apabila templat anda stabil.

registerOnChange: Komponen ke Borang

registerOnChange(fn) menyediakan laluan keluar. Angular menyerahkan fungsi callback kepada anda, dan anda mesti menyimpan rujukan kepadanya. Setiap kali pengguna menukar nilai di dalam komponen anda, anda memanggil fungsi tersebut dengan nilai baharu. Dalam komponen penarafan bintang, apabila pengguna mengklik bintang ketiga, anda memanggil fungsi callback yang disimpan dengan nilai 3. Panggilan tersebut mengalir semula ke dalam FormControl, mengemas kini model, mencetuskan sebarang langganan valueChanges, dan menjalankan semula validator.

Melangkau langkah ini adalah cara paling biasa untuk merosakkan borang secara senyap. Widget tersebut mungkin kelihatan berfungsi. Pengguna melihat bintang menyala, warna berubah, atau tarikh diisi. Namun, model borang tidak pernah dikemas kini. Validator terus menilai data lama. Pengendali hantaran (submit handlers) menghantar nilai lama. Komponen kelihatan berfungsi, namun borang tersebut sebenarnya "buta". Jika kawalan tersuai anda menerima input pengguna tetapi borang di sekelilingnya tidak pernah menyedarinya, inilah puncanya.

registerOnTouched: Melaporkan Interaksi

Borang bukan sahaja menjejaki nilai. Ia juga menjejaki sama ada pengguna telah berinteraksi dengan sesuatu medan. Angular menggunakan keadaan touched untuk memutuskan bila masa yang sesuai untuk menunjukkan ralat pengesahan (validation errors). Input teks wajib tidak sepatutnya berkelip merah sebaik sahaja halaman dimuatkan. Ia sepatutnya menunggu sehingga pengguna beralih ke medan lain atau mengklik di tempat lain.

Input asli mengendalikan perkara ini secara automatik melalui acara blur. Komponen tersuai tidak. Anda mesti menggunakan registerOnTouched(fn) untuk melaporkan interaksi ini sendiri. Angular memberikan anda satu lagi fungsi callback; anda memanggilnya apabila anda memutuskan bahawa pengguna telah berinteraksi secara bermakna dengan kawalan tersebut.

Masa yang tepat bergantung pada komponen anda. Untuk input tersuai seperti teks, anda mungkin memanggilnya pada blur. Untuk penarafan bintang, klik pertama mungkin merupakan masa yang sesuai. Untuk pemilih warna yang membuka popover, anda mungkin menunggu sehingga palet ditutup. Kuncinya ialah konsistensi. Jika anda tidak pernah memanggil fungsi callback touched, Angular akan terus menandakan kawalan tersebut sebagai pristine. Ralat pengesahan akan kekal tersembunyi walaupun pengguna telah selesai menyunting dengan jelas. Ini akan membawa kepada kekeliruan dan pengalaman pengguna yang buruk.

setDisabledState: Mematuhi Arahan Borang

Borang dinamik sentiasa mengaktifkan dan menyahaktifkan medan berdasarkan logik perniagaan. Apabila anda memanggil .disable() pada FormControl, Angular memerlukan komponen tersuai anda untuk bertindak balas. setDisabledState(isDisabled) menerima boolean. Apabila ia bernilai true, anda harus mengunci UI anda.

Ini bermakna lebih daripada sekadar mengabaikan klik. Anda harus menyahaktifkan butang dalaman, membuang keadaan boleh fokus (focusable), dan menerapkan rawatan visual seperti pengurangan kelegapan (opacity) atau pointer-events: none. Jika anda mengabaikan kaedah ini, komponen anda akan kekal interaktif sepenuhnya sedangkan model borang menegaskan bahawa ia telah dinyahaktifkan. Ini mewujudkan pepijat yang sukar dikesan. Pengguna boleh mengubah nilai yang sepatutnya ditolak oleh borang. Butang simpan mungkin diaktifkan berdasarkan keadaan yang tidak sah. Kumpulan borang (form group) dan UI akan menjadi tidak selaras.

Kawalan tersuai yang dibina dengan baik menganggap setDisabledState sebagai keperluan utama, bukannya sekadar perkara sampingan.

Kesilapan Yang Akan Membazirkan Masa Debugging Anda

Beberapa kesilapan berulang sering memerangkap pembangun yang baru mengenali antara muka ini.

Lupa untuk memanggil callback perubahan. Komponen anda mengemas kini keadaan dalamannya, tetapi borang tidak pernah mengetahuinya. Validator terhenti, dan borang induk menghantar data lama (stale data). Sentiasa jalankan fungsi onChange yang disimpan itu sebaik sahaja pengguna menetapkan nilai baharu.

Melangkau callback touched. Tanpanya, Angular tidak akan menandakan kawalan tersebut sebagai 'touched'. Mesej ralat yang terikat dengan keadaan touched atau dirty tidak akan dipaparkan. Pengguna akan merenung borang yang kelihatan betul tetapi tidak boleh dihantar, tanpa sebarang petunjuk jelas tentang apa yang salah.

Mengabaikan keadaan dinyahaktifkan (disabled state). Kawalan yang kelihatan aktif secara visual tetapi dianggap dinyahaktifkan oleh borang akan mewujudkan sempadan kepercayaan yang rosak. Pengguna boleh terus menaip atau mengklik, tetapi model mengabaikan mereka. Atau lebih teruk lagi, model secara rawak menulis semula input mereka semasa kitaran penyinkronan.

Meninggalkan provider NG_VALUE_ACCESSOR. Ini adalah pembunuh senyap. Jika anda melaksanakan keempat-empat kaedah tersebut tetapi lupa untuk menambah NG_VALUE_ACCESSOR ke dalam tatasusunan providers komponen anda, Angular tidak akan mendaftarkan komponen anda sebagai 'value accessor'. Kod berjaya dikompil. Paparan (view) dipaparkan. Namun, tiada apa yang terikat (bind). Tiada mesej ralat, cuma komponen yang terapung di luar borang sepenuhnya. Sentiasa sertakannya dalam metadata dekorator.

Signals, Validators, dan Angular Moden

ControlValueAccessor bukanlah permukaan API lama (legacy). Ia sesuai dengan kemas dalam pembangunan Angular moden. Sama ada anda menguruskan keadaan dalaman dengan Signals, properti biasa, atau subjek RxJS, keempat-empat kaedah tersebut kekal sebagai kontrak awam anda dengan modul borang. Anda menggunakan nilai dalam writeValue, mengubah Signals atau keadaan anda, dan menghantarnya melalui callback yang disediakan oleh Angular.

Validator standard berfungsi tanpa pengubahsuaian. Validators.required, Validators.min, Validators.pattern, dan validator rentas-medan (cross-field) tersuai semuanya menilai komponen berasaskan CVA anda sama seperti input asli. Kawalan borang melihat nilai dan keadaan. Ia tidak peduli sama ada nilai itu datang daripada kotak teks atau pemilih bulan (month-picker) yang dibina sendiri.

Kebolehpindahan (portability) itulah sebabnya CVA penting untuk sistem reka bentuk dan perpustakaan UI kongsi. Satu pasukan membina input nombor telefon yang teguh atau widget muat naik fail. Mereka melaksanakan antara muka tersebut sekali sahaja. Setiap pasukan lain dalam organisasi boleh memasukkannya ke dalam Reactive Forms mereka tanpa sebarang pendawaian tambahan. Komponen tersebut berkelakuan secara boleh ramal, mengesahkan secara seragam, dan dinyahaktifkan secara konsisten merentasi setiap modul ciri.

Kesimpulan Sebenar

ControlValueAccessor bukan sekadar satu lagi antara muka untuk dihafal bagi soalan temu duga. Ia adalah jambatan yang membolehkan komponen tersuai anda menyertai ekosistem borang Angular setaraf dengan elemen HTML asli. Menguasainya bermakna memahami keseluruhan perbualan antara widget anda dan borang: menerima nilai, melaporkan perubahan, mengumumkan sentuhan (touches), dan menghormati keadaan dinyahaktifkan. Jika anda melakukan empat perkara ini dengan betul, anda boleh membina kawalan borang yang kompleks dan boleh guna semula yang terasa 'halimunan' kepada pembangun yang menggunakannya. Itulah tanda komponen Angular yang profesional.