Angular forms मानक HTML सोबत उत्तम प्रकारे काम करतात. input, textarea, आणि select हे सर्व अतिरिक्त प्रयत्नांशिवाय Reactive Forms मध्ये सहजपणे बसतात. हे framework त्यांचे events, त्यांचे values आणि त्यांच्या states समजून घेते.

परंतु आधुनिक ॲप्लिकेशन्स केवळ मानक elements वर अवलंबून राहत नाहीत. तुम्हाला स्टार रेटिंग विजेट (star rating widget), कंपोझिट डेट सिलेक्टर (composite date selector), किंवा कस्टम कलर पिकर (custom color picker) ची आवश्यकता भासू शकते. यापैकी एक घटक form group मध्ये टाकल्यास, Angular कडे तो केवळ एक 'dead HTML' म्हणून गणला जातो. patchValue काहीच करत नाही. Validators कडे त्याकडे दुर्लक्ष करतात. युजरने कंट्रोलसोबत कधी संवाद साधला आहे, याची फॉर्मला कोणतीही कल्पना नसते आणि form.disable() मुळे कस्टम विजेट पूर्णपणे interactive राहते.

हीच ती समस्या आहे जी सोडवण्यासाठी ControlValueAccessor अस्तित्वात आहे.

ControlValueAccessor प्रत्यक्षात काय करते

ControlValueAccessor हा एक असा करार (contract) आहे जो कस्टम कंपोनंटला (custom component) फॉर्मचा एक महत्त्वाचा भाग (first-class form citizen) बनवतो. हे Angular Forms API आणि तुमच्या स्वतःच्या UI मध्ये एक ट्रान्सलेटर म्हणून काम करते. एकदा तुम्ही ते योग्यरित्या लागू केले की, फॉर्मच्या दृष्टिकोनातून तुमचा कंपोनंट एखाद्या नेटिव्ह इनपुटसारखाच वाटू लागतो. ते बिल्ट-इन एलिमेंटप्रमाणेच values प्राप्त करू शकते, changes emit करू शकते, touches रिपोर्ट करू शकते आणि disabled states चे पालन करू शकते.

या interface साठी चार विशिष्ट मेथड्सची आवश्यकता असते. प्रत्येक मेथड संवादाची एक वेगळी दिशा हाताळते.

writeValue: Form कडून Component कडे

writeValue(obj) ही इनबाउंड लेन (inbound lane) आहे. जेव्हा जेव्हा फॉर्म मॉडेल अपडेट होते आणि तुमच्या UI मध्ये नवीन value पाठवण्याची गरज असते, तेव्हा Angular ही मेथड कॉल करते. जर तुम्ही एखाद्या form group वर patchValue({ rating: 4 }) कॉल केला, तर 4 ही value writeValue द्वारे तुमच्या कंपोनंटमध्ये येते. जर तुम्ही फॉर्म रिसेट केला, तर writeValue ला नवीन initial value किंवा null प्राप्त होते. या मेथडमध्ये तुमचे काम म्हणजे ती येणारी डेटा घेणे आणि तुमच्या कंपोनंटच्या अंतर्गत स्टेटला (internal state) मॅप करणे हे आहे. जर तुम्ही कलर पिकर बनवत असाल, तर writeValue ला #ff4400 सारखी hex string मिळते आणि तुम्हाला तो रंग निवडलेला दाखवण्यासाठी तुमचे view अपडेट करावे लागते.

येथे एक व्यावहारिक अडचण येऊ शकते. Angular तुमची view पूर्णपणे इनिशियलाइज (initialize) होण्यापूर्वीच writeValue कॉल करू शकते, विशेषतः डायनॅमिकली रेंडर होणाऱ्या कंपोनंट्स, डायलॉग्स किंवा टॅब्ड इंटरफेसमध्ये. जर तुमच्या कंपोनंटने DOM किंवा चाइल्ड कंपोनंट्सना खूप लवकर स्पर्श करण्याचा प्रयत्न केला, तर तुम्हाला runtime errors येऊ शकतात. एक उत्तम पद्धत म्हणजे value एका लोकल प्रॉपर्टीमध्ये साठवून ठेवणे आणि view इनिशियलाइज झाल्यानंतर ती लागू करणे, किंवा undefined child references पासून सावध राहणे. writeValue फक्त तुमचा टेम्पलेट स्थिर (stable) झाल्यावरच फायर होईल, असे कधीही गृहीत धरू नका.

registerOnChange: Component कडून Form कडे

registerOnChange(fn) ही आउटबाउंड लेन (outbound lane) सेट करते. Angular तुम्हाला एक callback function देते आणि तुम्हाला त्याचा संदर्भ (reference) जपून ठेवावा लागतो. जेव्हा जेव्हा युजर तुमच्या कंपोनंटमधील value बदलतो, तेव्हा तुम्ही त्या फंक्शनला नवीन value सह कॉल करता. स्टार रेटिंग कंपोनंटमध्ये, जेव्हा युजर तिसऱ्या ताऱ्यावर क्लिक करतो, तेव्हा तुम्ही साठवलेले callback 3 सह कॉल करता. तो कॉल परत FormControl मध्ये जातो, मॉडेल अपडेट करतो, कोणत्याही valueChanges subscriptions ला ट्रिगर करतो आणि validators पुन्हा रन करतो.

ही पायरी वगळणे हा फॉर्म शांतपणे बिघडवण्याचा सर्वात सामान्य मार्ग आहे. विजेट जिवंत वाटू शकते. युजरला तारे चमकताना, रंग बदलताना किंवा तारखा भरताना दिसू शकतात. परंतु फॉर्म मॉडेल कधीच अपडेट होत नाही. Validators जुना (stale) डेटा तपासत राहतात. Submit handlers जुन्या values पाठवतात. कंपोनंट काम करत असल्यासारखा वाटतो, तरीही फॉर्म प्रत्यक्षात 'अंध' असतो. जर तुमचे कस्टम कंट्रोल युजर इनपुट स्वीकारत असेल पण आजूबाजूचा फॉर्म ते कधीच लक्षात घेत नसेल, तर याचे कारण बहुधा हेच असते.

registerOnTouched: Interaction रिपोर्ट करणे

फॉर्म केवळ values ट्रॅक करत नाहीत. युजरने एखाद्या फील्डसोबत संवाद साधला आहे की नाही, हे देखील ते ट्रॅक करतात. व्हॅलिडेशन एरर्स (validation errors) कधी दाखवणे योग्य आहे हे ठरवण्यासाठी Angular 'touched state' चा वापर करते. एखादे required टेक्स्ट इनपुट पेज लोड होताच लगेच लाल रंगात चमकू नये. युजरने टॅब (tab) दाबून पुढे जाईपर्यंत किंवा इतरत्र क्लिक करेपर्यंत त्याने थांबायला हवे.

नेटिव्ह इनपुट्स हे blur events द्वारे आपोआप हाताळतात. कस्टम कंपोनंट्स तसे करत नाहीत. हे interactions स्वतः रिपोर्ट करण्यासाठी तुम्हाला registerOnTouched(fn) वापरावे लागेल. Angular तुम्हाला आणखी एक callback देते; युजरने कंट्रोलसोबत अर्थपूर्ण संवाद साधला आहे असे तुम्हाला वाटल्यास तुम्ही ते कॉल करू शकता.

याचे नेमके टायमिंग तुमच्या कंपोनंटवर अवलंबून असते. टेक्स्टसारख्या कस्टम इनपुटसाठी, तुम्ही ते blur झाल्यावर कॉल करू शकता. स्टार रेटिंगसाठी, पहिला क्लिक हा कदाचित योग्य क्षण असेल. पॉपओव्हर (popover) उघडणाऱ्या कलर पिकरसाठी, तुम्ही पॅलेट (palette) बंद होईपर्यंत वाट पाहू शकता. महत्त्वाचे म्हणजे सातत्य (consistency). जर तुम्ही कधीही touched callback कॉल केला नाही, तर Angular त्या कंट्रोलला pristine म्हणून मार्क करत राहते. युजरने एडिटिंग पूर्ण केले असूनही व्हॅलिडेशन एरर्स लपलेले राहतात. यामुळे गोंधळ आणि खराब युजर एक्सपिरियन्स (user experience) निर्माण होतो.

setDisabledState: फॉर्म कमांड्सचे पालन करणे

डायनॅमिक फॉर्म्स बिझनेस लॉजिकनुसार सतत फील्ड्स इनेबल (enable) आणि डिसेबल (disable) करत असतात. जेव्हा तुम्ही FormControl वर .disable() कॉल करता, तेव्हा Angular ला तुमच्या कस्टम कंपोनंटकडून प्रतिसादाची अपेक्षा असते. setDisabledState(isDisabled) ला एक boolean व्हॅल्यू मिळते. जेव्हा ती true असते, तेव्हा तुम्ही तुमचे UI लॉक केले पाहिजे.

याचा अर्थ केवळ क्लिक्सकडे दुर्लक्ष करणे इतकाच नाही. तुम्ही अंतर्गत बटणे डिसेबल केली पाहिजेत, focusable states काढून टाकले पाहिजेत आणि reduced opacity किंवा pointer-events: none सारखे व्हिज्युअल बदल (visual treatments) लागू केले पाहिजेत. जर तुम्ही या मेथडकडे दुर्लक्ष केले, तर फॉर्म मॉडेलने ते डिसेबल असल्याचे सांगूनही तुमचा कंपोनंट पूर्णपणे इंटरअॅक्टिव्ह राहतो. यामुळे शोधण्यास कठीण असे बग्स (bugs) निर्माण होतात. युजर्स अशा व्हॅल्यूज बदलू शकतात ज्या फॉर्मने नाकारल्या पाहिजेत. चुकीच्या स्टेट्समुळे 'Save' बटणे इनेबल होऊ शकतात. यामुळे फॉर्म ग्रुप आणि UI यांच्यात तफावत निर्माण होते.

एक उत्तम प्रकारे तयार केलेला कस्टम कंट्रोल setDisabledState ला केवळ नंतरचा विचार न मानता, एक प्राथमिक आवश्यकता (first-class requirement) मानतो.

चुका ज्यामुळे तुमचा डीबगिंगचा (debugging) वेळ वाया जाईल

या इंटरफेसशी नवीन असलेल्या डेव्हलपर्सकडून अनेकदा काही ठराविक चुका होतात.

change callback कॉल करायला विसरणे. तुमचा कंपोनंट त्याची अंतर्गत स्टेट अपडेट करतो, परंतु फॉर्मला त्याबद्दल काहीच कळत नाही. Validators थांबतात आणि पेरेंट फॉर्म्स जुना (stale) डेटा सबमिट करतात. युजरने नवीन व्हॅल्यू सेट केल्याबरोबर नेहमी ती स्टोअर केलेली onChange फंक्शन कॉल करा.

touched callback वगळणे. याशिवाय, Angular त्या कंट्रोलला कधीही 'touched' म्हणून मार्क करत नाही. touched किंवा dirty स्टेट्सशी संबंधित एरर मेसेजेस दिसत नाहीत. युजर्सना असा फॉर्म दिसतो जो योग्य वाटतो पण सबमिट होत नाही, आणि काय चूक आहे याचे कोणतेही दृश्य संकेत मिळत नाहीत.

disabled state कडे दुर्लक्ष करणे. व्हिज्युअली इनेबल असलेला कंट्रोल जो फॉर्मला डिसेबल वाटतो, यामुळे विश्वासाची मर्यादा (trust boundary) मोडली जाते. युजर टाईप करणे किंवा क्लिक करणे सुरू ठेवू शकतो, परंतु मॉडेल त्यांच्याकडे दुर्लक्ष करते. किंवा त्याहून वाईट म्हणजे, सिंक सायकल दरम्यान मॉडेल त्यांच्या इनपुटला अधूनमधून ओव्हरराईट (overwrite) करू शकते.

NG_VALUE_ACCESSOR provider वगळणे. हा एक 'सायलेंट किलर' आहे. जर तुम्ही चारही मेथड्स इम्प्लिमेंट केल्या पण तुमच्या कंपोनंटच्या providers ॲरेमध्ये NG_VALUE_ACCESSOR जोडायला विसरलात, तर Angular तुमच्या कंपोनंटला व्हॅल्यू ॲक्सेसर (value accessor) म्हणून रजिस्टर करत नाही. कोड कंपाईल होतो, व्ह्यू रेंडर होतो, पण काहीही बाइंड (bind) होत नाही. कोणताही एरर मेसेज येत नाही, फक्त तुमचा कंपोनंट फॉर्मच्या पूर्णपणे बाहेर तरंगत राहतो. डेकोरेटर मेटाडेटा (decorator metadata) मध्ये ते नेहमी समाविष्ट करा.

Signals, Validators आणि मॉडर्न Angular

ControlValueAccessor ही जुनी (legacy) API सरफेस नाही. ती मॉडर्न Angular डेव्हलपमेंटमध्ये सहजपणे बसते. तुम्ही अंतर्गत स्टेट Signals, साध्या प्रॉपर्टीज किंवा RxJS subjects द्वारे मॅनेज केली तरी, या चार मेथड्स फॉर्म्स मॉड्यूलसोबतचा तुमचा सार्वजनिक करार (public contract) राहतात. तुम्ही writeValue मध्ये व्हॅल्यूज घेता, तुमच्या Signals किंवा स्टेटमध्ये बदल करता आणि Angular द्वारे दिलेल्या कॉलबॅकद्वारे त्या एमिट (emit) करता.

स्टँडर्ड व्हॅलिडेटर्स कोणत्याही बदलाशिवाय काम करतात. Validators.required, Validators.min, Validators.pattern आणि कस्टम क्रॉस-फील्ड व्हॅलिडेटर्स हे सर्व तुमच्या CVA-बॅक्ड कंपोनंटचे मूल्यमापन अगदी नेटिव्ह इनपुटप्रमाणेच करतात. फॉर्म कंट्रोलला फक्त व्हॅल्यू आणि स्टेट दिसते. ती व्हॅल्यू टेक्स्ट बॉक्समधून आली आहे की हाताने बनवलेल्या मंथ-पिकरमधून (month-picker), याने त्याला फरक पडत नाही.

त्या पोर्टेबिलिटीमुळेच (portability) डिझाइन सिस्टम्स आणि शेअर केलेल्या UI लायब्ररीजसाठी CVA महत्त्वाचे आहे. एक टीम एक मजबूत फोन-नंबर इनपुट किंवा फाईल अपलोड विजेट तयार करते. ते एकदा इंटरफेस इम्प्लिमेंट करतात. संस्थेतील इतर प्रत्येक टीम कोणत्याही अतिरिक्त वायरिंगशिवाय ते त्यांच्या Reactive Forms मध्ये वापरू शकते. कंपोनंटचा वर्तन अंदाज वर्तवता येईल असा (predictable) असतो, ते एकसमान व्हॅलिडेट होते आणि प्रत्येक फीचर मॉड्यूलमध्ये सातत्याने डिसेबल होते.

मुख्य निष्कर्ष (The Real Takeaway)

ControlValueAccessor हे केवळ मुलाखतीच्या प्रश्नांसाठी लक्षात ठेवण्यासारखे दुसरे इंटरफेस नाही. हे एक असे पूल (bridge) आहे जे तुमच्या कस्टम कंपोनंट्सना नेटिव्ह HTML एलिमेंट्सप्रमाणेच Angular च्या फॉर्म इकोसिस्टममध्ये सहभागी होण्यास मदत करते. यात मास्टर होणे म्हणजे तुमच्या विजेट आणि फॉर्ममधील संपूर्ण संवाद समजून घेणे: व्हॅल्यूज स्वीकारणे, बदल रिपोर्ट करणे, touches जाहीर करणे आणि disabled स्टेट्सचा आदर करणे. या चार गोष्टी योग्यरित्या केल्या, तर तुम्ही असे जटिल आणि पुन्हा वापरण्यायोग्य (reusable) फॉर्म कंट्रोल्स बनवू शकता जे वापरणाऱ्या डेव्हलपर्सना अगदी सहज (invisible) वाटतील. हेच एका प्रोफेशनल Angular कंपोनंटचे लक्षण आहे.