Angular‘da Custom Form Elemanları oluşturmak: ControlValueAccessor
Selamlar, sizlere bugün Angular ile geliştirme yapan çoğu kişinin hazır kütüphaneler ile dolaylı olarak kullandığı fakat form elemanı görevi gören custom bir component yazmadıysanız detayından haberinizin olmayabileceği bir özellikten bahsedeceğim.

ControlValueAccessor nedir ve ne işe yarar?
ControlValueAccessor @angular/forms modülü altında Angular’ın bize sunmuş olduğu bir interfacedir. Bu interface sayesinde DOM üzerinde bulunan elementlerimiz ile Angular formları arasında iletişim kurabiliyoruz. Reactive veya Template-Driven formların her ikisinde kullanabilirsiniz. Bu sayede uygulamanızın her yerinde tekrar kullanabileceğiniz form elemanlarını component olarak oluşturabilirsiniz.
ControlValueAccessor’a neden ihtiyacım var?
ControlValueAccessor kullanılmadığı senaryoda @Output kullanılarak bu işlem yapılabilir fakat bu durumda her bir form elemanı için value değeri değiştiğinde parent component’de ayrı birer method oluşturup form’a yeni değeri set etmeniz gerekir, bu da gerçekten bakımı, okunabilirliği ve yeniden kullanılabilirliği kötü olan bir kod yazmanıza sebeb olacaktır.
ControlValueAccessor kullanarak “Like Input” componenti oluşturmak
ControlValueAccessor‘ın teorik kısımlarını anlatmaya devam ederken, bunun en kolay şekilde anlaşılacağını düşündüğüm bir örnek ile sizlere aktarmak istiyorum.
Örneğimizde kullanıcının belirtilen müzik türlerini beğenip beğenmediğini seçeceği bir form olacak. Burada aslında kullanıcıdan true/false boolean değerler alacağımız bir input türü kullanmamız gerekiyor. Bunun için HTML içerisinde bulunan inputlardan checkbox kullanılabilir fakat bizim senaryoda like/unlike icon’larının bulunacağı custom bir input oluşturacağız.
Yapacağım örnekte anlatım tamamen reactive form üzerinden olacaktır fakat yazı sonunda paylaşacağım link’de template-driven form örneğini de inceleyebilirsiniz.
Adım 1/5: InputLike Component’in oluşturulması
InputLike isimli bir component oluşturup ilgili HTML ve CSS kodlarını yazdık. value değeri sayesinde component’de true durumunda Like, false durumunda Unlike iconlarını göstereceğiz. isDisabled özelliği ile de ilgili form elemanını tıklanamaz hale getireceğiz.



Adım 2/5: ControlValueAccessor interface’nin implemente edilmesi ve methodların uygulanması
ControlValueAccessor interface ile beraber gelen 3 zorunlu, 1 isteğe bağlı methodu uygulamanız gerekiyor. Bu methodları sırasıyla inceleyelim.
writeValue: Form elemanına ilk değer atandığında veya FormGroup class’ının methodları(patchValue,setValue) kullanılarak değer set edildiği durumda writeValue methodu tetiklenir. Bu method sayesinde InputLike componenti üzerindeki value değerine form’dan gelen değerler aktarılır. Method’un aldığı parametre form elemanına set ettiğiniz değere bağlıdır bizim senaryomuzda true/false değerleri set edileceği için boolean tipinde bir parametre alacak.
value: boolean;
writeValue(obj: boolean): void {
this.value = obj;
}
registerOnChange: Component ilk defa oluşturulurken bu method tetiklenir. _onChange değişkeninde form elemanında değişiklik olduğunda form’a bunu bir method aracılığıyla bildirmek için bu method’un referansını tutmamız gerekir.
private _onChange: (obj: boolean) => void;
registerOnChange(fn: (obj: boolean) => void): void {
this._onChange = fn;
}
registerOnTouched: Component ilk defa oluşturulurken bu method tetiklenir. _onTouched değişkeninde form elemanına dokunulduğunda form’a bunu bir method aracılığıyla bildirmek için bu method’un referansını tutmamız gerekir.
private _onTouched: () => void;
registerOnTouched(fn: () => void): void {
this._onTouched = fn;
}
setDisabledState: Bu method opsiyonel olduğundan uygulanması zorunlu değildir. Form elemanına ilk değer atandığında veya FormGroup class’ının disable methodu ile form elemanı disable edilmek isteniyorsa bu methodu’da oluşturmak gereklidir. Bu method sayesinde InputLike componenti üzerindeki value değerine form’dan gelen disabled değeri aktarılır.
isDisabled: boolean;
setDisabledState?(isDisabled: boolean): void {
this.isDisabled = isDisabled;
}
Adım 3/5: Like-Unlike iconlarının click event’inin yazılması
Kullanıcı iconlara tıklandığında form elemanları disabled durumunda değilse çalışacak olan bir fonksiyon yazmamız gerekiyor.
Like durumunda bu elemana tıklandığında Unlike, Unlike durumunda tıklandığında ise Like durumuna set edilecek kısaca mevcut value değerinin tersini tekrardan value değerine set etmemiz gerekiyor.
Bu işlem sonrasında Angular form’larına form elamanının change ve touch değişikliğini bildiriyoruz. Bunu da Adım 2'de bahsettiğim registerOnTouched, registerOnChange methodlarında method referanslarını atadığım değişkenler sayesinde gerçekleştiriyoruz.
setValue(): void {
if (this.isDisabled) return;
this.value = !this.value;
this._onChange(this.value);
this._onTouched();
}
Adım 4/5: NG_VALUE_ACCESSOR sağlayıcısının eklenmesi
Oluşturduğumuz bir component’de formControlName veya [(ngModel)] kullanmak için NG_VALUE_ACCESSOR sağlayıcısını component’in providers özelliğine eklememiz gerekiyor, aksi takdirde console üzerinde hata ile karşılaşacaksınız.
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => InputLikeComponent),
multi: true,
},

Adım 5/5: Parent Component’de form oluşturulması
Parent componenent’de müzik türleri ile ilgili bilgileri almak için bir form oluşturuyoruz. Burada atama yaptığımız ilk değerler InputLike component’inde writeValue methodu üzerinde tetiklenecektir.
isLikeRap elemanının disabled özelliğide yine InputLike component’inde setDisabledState methodunda tetiklenecek.
musicForm: FormGroup;
constructor(private fb: FormBuilder) {
this.musicForm = this.fb.group({
isLikePop: true,
isLikeRock: true,
isLikeRap: { value: false, disabled: true },
});
}<form [formGroup]=”musicForm”>
<label class=”label-control”>Pop Müzik</label>
<input-like formControlName=”isLikePop”> </input-like><label class=”label-control”>Rock Müzik</label>
<input-like formControlName=”isLikeRock”> </input-like>
<label class=”label-control”>Rap Müzik</label>
<input-like formControlName=”isLikeRap”> </input-like>
<button [disabled]=”!musicForm.touched” type=”submit”>Gönder</button>
</form>

Artık sizlerde kendi Text, Select, Radio vb. input’larınızı component yapıp, uygulamalarınızda kullanabilirsiniz.
Bu sayede Select input’una yeni bir özellik eklediğinizde tüm uygulamanızdaki select inputları’da bu özelliğe sahip olacak. Çalışmayı detaylı olarak incelemek isteyenler için Stackblitz üzerinde hazırladığım uygulamayı aşağıya bırakıyorum.