Angular forms work beautifully with standard HTML. input, textarea, and select all slot into Reactive Forms without extra effort. The framework understands their events, their values, and their states.

But modern applications rarely get by on standard elements alone. You might need a star rating widget, a composite date selector, or a custom color picker. Drop one of these into a form group, and Angular treats it as dead HTML. patchValue does nothing. Validators ignore it. The form has no idea when a user interacts with the control, and form.disable() leaves the custom widget fully interactive.

This is the problem ControlValueAccessor exists to solve.

What ControlValueAccessor Actually Does

ControlValueAccessor is the contract that turns a custom component into a first-class form citizen. It acts as a translator between the Angular Forms API and your own UI. Once you implement it correctly, your component becomes indistinguishable from a native input from the form’s point of view. It can receive values, emit changes, report touches, and respect disabled states just like a built-in element.

The interface requires four specific methods. Each one handles a distinct direction of communication.

writeValue: Form to Component

writeValue(obj) is the inbound lane. Whenever the form model updates and needs to push a new value into your UI, Angular calls this method. If you invoke patchValue({ rating: 4 }) on a form group, that value of 4 arrives inside your component through writeValue. If you reset the form, writeValue receives the new initial value or null. Your job inside this method is to take that incoming data and map it onto your component’s internal state. If you are building a color picker, writeValue receives a hex string like #ff4400, and you must update your view to show that color as selected.

There is a practical wrinkle here. Angular can call writeValue before your view is fully initialized, especially inside dynamically rendered components, dialogs, or tabbed interfaces. If your component tries to touch the DOM or child components too early, you can hit runtime errors. A solid pattern is to store the value in a local property and apply it after the view initializes, or to guard against undefined child references. Never assume writeValue only fires when your template is stable.

registerOnChange: Component to Form

registerOnChange(fn) sets up the outbound lane. Angular hands you a callback function, and you must keep a reference to it. Every time the user changes the value inside your component, you call that function with the new value. In a star rating component, when the user clicks the third star, you invoke the stored callback with 3. That call flows back into the FormControl, updates the model, triggers any valueChanges subscriptions, and re-runs validators.

Skipping this step is the most common way to silently break a form. The widget might look alive. The user sees stars light up, colors shift, or dates populate. But the form model never updates. Validators continue to evaluate stale data. Submit handlers send old values. The component appears to work, yet the form is effectively blind. If your custom control accepts user input but the surrounding form never notices, this is almost always the culprit.

registerOnTouched: Reporting Interaction

Forms do not just track values. They track whether a user has interacted with a field. Angular uses the touched state to decide when it is appropriate to show validation errors. A required text input should not flash red the instant the page loads. It should wait until the user tabs away or clicks elsewhere.

Native inputs handle this automatically through blur events. Custom components do not. You must use registerOnTouched(fn) to report these interactions yourself. Angular gives you another callback; you call it when you decide the user has meaningfully engaged with the control.

The exact timing depends on your component. For a text-like custom input, you might call it on blur. For a star rating, the first click is probably the right moment. For a color picker that opens a popover, you might wait until the palette closes. The key is consistency. If you never call the touched callback, Angular keeps marking the control as pristine. Validation errors stay hidden even after the user has clearly finished editing. That leads to confusion and poor user experience.

setDisabledState: فارم کمانڈز کا احترام کرنا

ڈائنامک فارمز بزنس لاجک کی بنیاد پر مسلسل فیلڈز کو enable اور disable کرتے رہتے ہیں۔ جب آپ FormControl پر .disable() کال کرتے ہیں، تو Angular کو ضرورت ہوتی ہے کہ آپ کا کسٹم کمپوننٹ اس کا جواب دے۔ setDisabledState(isDisabled) ایک boolean وصول کرتا ہے۔ جب یہ true ہو، تو آپ کو اپنے UI کو لاک (lock down) کر دینا چاہیے۔

اس کا مطلب صرف کلکس کو نظر انداز کرنا نہیں ہے۔ آپ کو اندرونی بٹنز کو disable کرنا چاہیے، focusable states کو ختم کرنا چاہیے، اور visual treatments جیسے کہ reduced opacity یا pointer-events: none کا استعمال کرنا چاہیے۔ اگر آپ اس میتھڈ کو نظر انداز کرتے ہیں، تو آپ کا کمپوننٹ مکمل طور پر interactive رہتا ہے جبکہ فارم ماڈل یہ دعویٰ کرتا ہے کہ وہ disabled ہے۔ اس سے ایسے بگ (bugs) پیدا ہوتے ہیں جن کا سراغ لگانا مشکل ہوتا ہے۔ صارفین ان ویلیوز کو تبدیل کر سکتے ہیں جنہیں فارم کو مسترد کرنا چاہیے تھا۔ سیو (Save) بٹنز غلط اسٹیٹس کی بنیاد پر enable ہو سکتے ہیں۔ فارم گروپ اور UI ایک دوسرے سے الگ ہو جاتے ہیں۔

ایک بہتر طریقے سے بنایا گیا کسٹم کنٹرول setDisabledState کو ایک بنیادی ضرورت کے طور پر دیکھتا ہے، نہ کہ کسی بعد کے خیال کے طور پر۔

وہ غلطیاں جو آپ کا ڈیبگنگ (debugging) کا وقت ضائع کریں گی

کئی بار ہونے والی غلطیاں ان ڈویلپرز کو الجھا دیتی ہیں جو اس انٹرفیس سے نئے ہیں۔

change callback کو کال کرنا بھول جانا۔ آپ کا کمپوننٹ اپنی اندرونی اسٹیٹ کو اپ ڈیٹ کرتا ہے، لیکن فارم کو اس کا علم نہیں ہوتا۔ Validators رک جاتے ہیں، اور پیرنٹ فارمز پرانی (stale) ڈیٹا جمع کر دیتے ہیں۔ جیسے ہی صارف نئی ویلیو فراہم کرے، ہمیشہ اس محفوظ شدہ onChange فنکشن کو کال کریں۔

touched callback کو چھوڑ دینا۔ اس کے بغیر، Angular کنٹرول کو کبھی بھی touched کے طور پر مارک نہیں کرتا۔ touched یا dirty اسٹیٹس سے منسلک ایرر میسجز نظر نہیں آتے۔ صارفین ایک ایسے فارم کو دیکھتے رہتے ہیں جو درست لگتا ہے لیکن سبمٹ نہیں ہوتا، اور یہ بھی معلوم نہیں ہوتا کہ غلطی کہاں ہے۔

disabled اسٹیٹ کو نظر انداز کرنا۔ ایک ایسا کنٹرول جو دیکھنے میں enabled لگے لیکن فارم اسے disabled سمجھے، وہ اعتماد کے رشتے کو توڑ دیتا ہے۔ صارف ٹائپ کرنا یا کلک کرنا جاری رکھ سکتا ہے، لیکن ماڈل انہیں نظر انداز کر دیتا ہے۔ یا اس سے بھی بدتر، ماڈل سنک سائیکلز (sync cycles) کے دوران ان کی ان پٹ کو وقفے وقفے سے اوور رائٹ (overwrite) کر دیتا ہے۔

NG_VALUE_ACCESSOR provider کو شامل نہ کرنا۔ یہ ایک خاموش قاتل ہے۔ اگر آپ چاروں میتھڈز کو نافذ (implement) کرتے ہیں لیکن اپنے کمپوننٹ کے 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 فراہم کرتا ہے۔

اسٹینڈرڈ Validators بغیر کسی تبدیلی کے کام کرتے ہیں۔ Validators.required، Validators.min، Validators.pattern اور کسٹم کراس-فیلڈ Validators آپ کے CVA-بیکڈ کمپوننٹ کا بالکل اسی طرح جائزہ لیتے ہیں جیسے وہ کسی نیٹیو (native) ان پٹ کا لیتے ہیں۔ فارم کنٹرول ایک ویلیو اور ایک اسٹیٹ دیکھتا ہے۔ اسے اس سے کوئی فرق نہیں پڑتا کہ وہ ویلیو ٹیکسٹ باکس سے آئی ہے یا کسی خاص طور پر بنائے گئے month-picker سے۔

یہی پورٹیبلٹی (portability) وجہ ہے کہ ڈیزائن سسٹمز اور شیئرڈ UI لائبریریز کے لیے CVA اہم ہے۔ ایک ٹیم ایک مضبوط فون نمبر ان پٹ یا فائل اپ لوڈ ویجیٹ بناتی ہے۔ وہ صرف ایک بار انٹرفیس کو نافذ کرتے ہیں۔ تنظیم کی ہر دوسری ٹیم اسے بغیر کسی اضافی وائرنگ کے اپنے Reactive Forms میں استعمال کر لیتی ہے۔ کمپوننٹ قابلِ پیش گوئی (predictably) طریقے سے کام کرتا ہے، یکساں طور پر ویلیڈیٹ کرتا ہے، اور ہر فیچر ماڈیول میں مستقل مزاجی سے ڈس ایبل ہوتا ہے۔

اصل خلاصہ

ControlValueAccessor صرف انٹرویو کے سوالات کے لیے یاد کرنے والا ایک اور انٹرفیس نہیں ہے۔ یہ وہ پل ہے جو آپ کے کسٹم کمپوننٹس کو Angular کے فارم ایکو سسٹم میں نیٹیو HTML عناصر کے برابر حصہ لینے کی اجازت دیتا ہے۔ اس میں مہارت حاصل کرنے کا مطلب ہے اپنے ویجیٹ اور فارم کے درمیان مکمل گفتگو کو سمجھنا: ویلیوز وصول کرنا، تبدیلیاں رپورٹ کرنا، ٹچز (touches) کا اعلان کرنا، اور disabled اسٹیٹس کا احترام کرنا۔ ان چار چیزوں کو درست کر لیں، اور آپ پیچیدہ، دوبارہ استعمال ہونے والے فارم کنٹرولز بنا سکتے ہیں جو استعمال کرنے والے ڈویلپرز کے لیے بالکل فطری (invisible) محسوس ہوتے ہیں۔ یہی ایک پروفیشنل Angular کمپوننٹ کی نشانی ہے۔