I have been trying to create a custom radio component using ControlValueAccessor in Angular.
In the main component I have a form and it has two form controls which is tied to the custom radio component I created. In the initial load, both the custom radio components are selected, but as soon as I change value, only one of the custom radio maintains the selected state.
There seems to be an issue which is causing this unusual behavior and I am unable to figure it out. I tried with both ngModel (with standalone option) & FormControl within the custom component implementing the ControlValueAccessor, but to no avail.
radio.html
<div>
<ng-container *ngFor="let o of options; index as i; trackBy: trackByCode">
<input
type="radio"
id="{{ inputName + '_' + o.code }}"
[value]="o.code"
[attr.name]="inputName"
[formControl]="control"
(blur)="onBlur($event)"
(change)="onChange($event)"
/>
<label for="{{ inputName + '_' + o.code }}"> {{ o.name }} </label>
</ng-container>
</div>
radio.component.ts
import { Component, forwardRef, Input } from '@angular/core';
import {
ControlValueAccessor,
FormControl,
NG_VALUE_ACCESSOR,
} from '@angular/forms';
const MY_RADIO_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => RadioComponent),
multi: true,
};
@Component({
selector: 'radio',
templateUrl: './radio.html',
providers: [MY_RADIO_VALUE_ACCESSOR],
})
export class RadioComponent implements ControlValueAccessor {
@Input() inputName;
@Input() options = [];
control = new FormControl();
private _onChange!: (_: any) => void;
private _onTouched!: (_: any) => void;
trackByCode(index: number, option: any) {
return option.code;
}
writeValue(value: string): void {
this.control.setValue(value);
}
registerOnChange(fn: (_: any) => void): void {
this._onChange = fn;
}
registerOnTouched(fn: (_: any) => void): void {
this._onTouched = fn;
}
setDisabledState(isDisabled: boolean): void {
isDisabled ? this.control.disable() : this.control.enable();
}
onChange($event: any) {
this._onChange(this.control.value);
}
onBlur($event: any) {
this._onTouched($event);
}
}
app.component.ts
fruitOptions = [
{ name: 'Tomato', code: 'tomato' },
{ name: 'Apple', code: 'apple' },
{ name: 'Banana', code: 'banana' },
];
vegOptions = [
{ name: 'Cabbage', code: 'cabbage' },
{ name: 'Potato', code: 'potato' },
{ name: 'Beans', code: 'beans' },
];
form = this.fb.group({
fruit: ['tomato', Validators.required],
veg: ['cabbage', Validators.required],
});
app.component.html
<div [formGroup]="form">
<radio inputName="fruit" [options]="fruitOptions" formControlName="fruit"></radio>
<radio inputName="veg" [options]="vegOptions" formControlName="veg"></radio>
</div>
<br />
<div>{{ form.value | json }}</div>

