Implementing ControlValueAccessor with multiple FormControls in a child component allows you to create custom Angular form controls that can be used within Angular's reactive forms. This approach is useful when you need to encapsulate complex UI components or logic into reusable form controls.
To implement ControlValueAccessor with multiple FormControls in a child component, you need to:
ControlValueAccessor.Let's create an example where we have a custom component CustomFormComponent that contains two FormControls: one for a text input and one for a checkbox. This component will implement ControlValueAccessor to allow seamless integration with Angular forms.
Create custom-form.component.ts:
import { Component, forwardRef, OnInit } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormGroup, FormBuilder } from '@angular/forms'; @Component({ selector: 'app-custom-form', templateUrl: './custom-form.component.html', styleUrls: ['./custom-form.component.css'], providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CustomFormComponent), multi: true } ] }) export class CustomFormComponent implements ControlValueAccessor, OnInit { form: FormGroup; onChange: any = () => {}; onTouch: any = () => {}; constructor(private fb: FormBuilder) { } ngOnInit() { this.form = this.fb.group({ textInput: '', checkboxInput: false }); this.form.valueChanges.subscribe(() => { this.onChange(this.form.value); this.onTouch(); }); } writeValue(value: any) { if (value) { this.form.setValue(value, { emitEvent: false }); } } registerOnChange(fn: any) { this.onChange = fn; } registerOnTouched(fn: any) { this.onTouch = fn; } setDisabledState?(isDisabled: boolean): void { if (isDisabled) { this.form.disable(); } else { this.form.enable(); } } } Create custom-form.component.html:
<form [formGroup]="form"> <input type="text" formControlName="textInput" placeholder="Enter text"> <label> <input type="checkbox" formControlName="checkboxInput"> Checkbox </label> </form>
In the parent component's template (app.component.html), use CustomFormComponent within an Angular form:
<form [formGroup]="parentForm"> <app-custom-form formControlName="customControl"></app-custom-form> </form>
Make sure to include CustomFormComponent in your module (app.module.ts) declarations:
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { ReactiveFormsModule } from '@angular/forms'; import { AppComponent } from './app.component'; import { CustomFormComponent } from './custom-form/custom-form.component'; @NgModule({ imports: [BrowserModule, ReactiveFormsModule], declarations: [AppComponent, CustomFormComponent], bootstrap: [AppComponent] }) export class AppModule {} ControlValueAccessor Implementation: The CustomFormComponent implements ControlValueAccessor, providing methods (writeValue, registerOnChange, registerOnTouched, setDisabledState) required to integrate with Angular forms.CustomFormComponent, FormGroup manages textInput and checkboxInput.formControlName="customControl" in the parent template binds CustomFormComponent to parentForm's customControl.CustomFormComponent, NG_VALUE_ACCESSOR is provided to enable ControlValueAccessor functionality.This example demonstrates a basic implementation. Depending on your requirements, you may need to handle additional scenarios such as validation, handling custom events, or integrating with more complex form structures.
Implementing ControlValueAccessor for a custom form control
import { Component, forwardRef } from '@angular/core'; import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms'; @Component({ selector: 'app-custom-control', template: ` <input type="text" [(ngModel)]="value"> <input type="text" [(ngModel)]="value2"> `, providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CustomControlComponent), multi: true } ] }) export class CustomControlComponent implements ControlValueAccessor { value: string; value2: string; writeValue(value: any) { if (value) { this.value = value.value1; this.value2 = value.value2; } } registerOnChange(fn: any) { this.onChange = fn; } registerOnTouched(fn: any) { this.onTouched = fn; } onChange: any = () => {}; onTouched: any = () => {}; } CustomControlComponent implements ControlValueAccessor to handle two-way data binding (ngModel) for two input fields (value and value2).Passing formControl instances to ControlValueAccessor
import { Component, forwardRef, Input } from '@angular/core'; import { NG_VALUE_ACCESSOR, ControlValueAccessor, FormControl } from '@angular/forms'; @Component({ selector: 'app-multi-control', template: ` <input type="text" [formControl]="control1"> <input type="text" [formControl]="control2"> `, providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => MultiControlComponent), multi: true } ] }) export class MultiControlComponent implements ControlValueAccessor { @Input() control1: FormControl; @Input() control2: FormControl; writeValue(value: any) { // Implement if needed } registerOnChange(fn: any) { // Implement if needed } registerOnTouched(fn: any) { // Implement if needed } onChange: any = () => {}; onTouched: any = () => {}; } MultiControlComponent receives FormControl instances (control1 and control2) from its parent component and uses them directly in its template.Handling validation in ControlValueAccessor
import { Component, forwardRef, Input } from '@angular/core'; import { NG_VALUE_ACCESSOR, ControlValueAccessor, FormControl } from '@angular/forms'; @Component({ selector: 'app-multi-control', template: ` <input type="text" [formControl]="control1" [class.invalid]="control1.invalid && control1.touched"> <input type="text" [formControl]="control2" [class.invalid]="control2.invalid && control2.touched"> <div *ngIf="control1.invalid && control1.touched">Invalid input</div> <div *ngIf="control2.invalid && control2.touched">Invalid input</div> `, providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => MultiControlComponent), multi: true } ] }) export class MultiControlComponent implements ControlValueAccessor { @Input() control1: FormControl; @Input() control2: FormControl; writeValue(value: any) { // Implement if needed } registerOnChange(fn: any) { // Implement if needed } registerOnTouched(fn: any) { // Implement if needed } onChange: any = () => {}; onTouched: any = () => {}; } FormControl (control1 and control2) in MultiControlComponent.Creating a wrapper component with ControlValueAccessor
import { Component, forwardRef } from '@angular/core'; import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms'; @Component({ selector: 'app-custom-wrapper', template: ` <app-multi-control [formControl]="parentForm.get('control1')" [formControl2]="parentForm.get('control2')"></app-multi-control> `, providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CustomWrapperComponent), multi: true } ] }) export class CustomWrapperComponent implements ControlValueAccessor { parentForm: FormGroup; writeValue(value: any) { // Implement if needed } registerOnChange(fn: any) { // Implement if needed } registerOnTouched(fn: any) { // Implement if needed } onChange: any = () => {}; onTouched: any = () => {}; } CustomWrapperComponent integrates MultiControlComponent and manages its FormControl instances (control1 and control2) through a parent FormGroup.Binding initial values to ControlValueAccessor
import { Component, forwardRef, Input } from '@angular/core'; import { NG_VALUE_ACCESSOR, ControlValueAccessor, FormControl } from '@angular/forms'; @Component({ selector: 'app-multi-control', template: ` <input type="text" [formControl]="control1" [value]="control1.value"> <input type="text" [formControl]="control2" [value]="control2.value"> `, providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => MultiControlComponent), multi: true } ] }) export class MultiControlComponent implements ControlValueAccessor { @Input() control1: FormControl; @Input() control2: FormControl; writeValue(value: any) { if (value) { this.control1.setValue(value.control1); this.control2.setValue(value.control2); } } registerOnChange(fn: any) { // Implement if needed } registerOnTouched(fn: any) { // Implement if needed } onChange: any = () => {}; onTouched: any = () => {}; } MultiControlComponent sets initial values for control1 and control2 based on the provided value.Implementing a custom value accessor for multiple form controls
import { Component, forwardRef, Input } from '@angular/core'; import { NG_VALUE_ACCESSOR, ControlValueAccessor, FormControl } from '@angular/forms'; @Component({ selector: 'app-multi-control', template: ` <input type="text" [formControl]="control1"> <input type="text" [formControl]="control2"> `, providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => MultiControlComponent), multi: true } ] }) export class MultiControlComponent implements ControlValueAccessor { @Input() control1: FormControl; @Input() control2: FormControl; writeValue(value: any) { if (value) { this.control1.setValue(value.control1); this.control2.setValue(value.control2); } } registerOnChange(fn: any) { this.onChange = fn; } registerOnTouched(fn: any) { this.onTouched = fn; } onChange: any = () => {}; onTouched: any = () => {}; } MultiControlComponent implements ControlValueAccessor to synchronize values of control1 and control2 with the parent form.Handling changes in ControlValueAccessor
import { Component, forwardRef, Input } from '@angular/core'; import { NG_VALUE_ACCESSOR, ControlValueAccessor, FormControl } from '@angular/forms'; @Component({ selector: 'app-multi-control', template: ` <input type="text" [formControl]="control1" (input)="onInputChange()"> <input type="text" [formControl]="control2" (input)="onInputChange()"> `, providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => MultiControlComponent), multi: true } ] }) export class MultiControlComponent implements ControlValueAccessor { @Input() control1: FormControl; @Input() control2: FormControl; writeValue(value: any) { if (value) { this.control1.setValue(value.control1); this.control2.setValue(value.control2); } } registerOnChange(fn: any) { this.onChange = fn; } registerOnTouched(fn: any) { this.onTouched = fn; } onChange: any = () => {}; onTouched: any = () => {}; onInputChange() { this.onChange({ control1: this.control1.value, control2: this.control2.value }); } } MultiControlComponent triggers onChange when input values change, updating the parent form with new values of control1 and control2.Passing initial values to ControlValueAccessor
import { Component, forwardRef, Input } from '@angular/core'; import { NG_VALUE_ACCESSOR, ControlValueAccessor, FormControl } from '@angular/forms'; @Component({ selector: 'app-multi-control', template: ` <input type="text" [formControl]="control1" [value]="initialValues.control1"> <input type="text" [formControl]="control2" [value]="initialValues.control2"> `, providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => MultiControlComponent), multi: true } ] }) export class MultiControlComponent implements ControlValueAccessor { @Input() control1: FormControl; @Input() control2: FormControl; @Input() initialValues: { control1: any, control2: any }; writeValue(value: any) { if (value) { this.control1.setValue(value.control1); this.control2.setValue(value.control2); } } registerOnChange(fn: any) { this.onChange = fn; } registerOnTouched(fn: any) { this.onTouched = fn; } onChange: any = () => {}; onTouched: any = () => {}; } MultiControlComponent initializes control1 and control2 with initialValues provided by the parent component.Syncing values between ControlValueAccessor and parent form
import { Component, forwardRef, Input } from '@angular/core'; import { NG_VALUE_ACCESSOR, ControlValueAccessor, FormControl } from '@angular/forms'; @Component({ selector: 'app-multi-control', template: ` <input type="text" [formControl]="control1" (input)="onControl1Change($event.target.value)"> <input type="text" [formControl]="control2" (input)="onControl2Change($event.target.value)"> `, providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => MultiControlComponent), multi: true } ] }) export class MultiControlComponent implements ControlValueAccessor { @Input() control1: FormControl; @Input() control2: FormControl; writeValue(value: any) { if (value) { this.control1.setValue(value.control1); this.control2.setValue(value.control2); } } registerOnChange(fn: any) { this.onChange = fn; } registerOnTouched(fn: any) { this.onTouched = fn; } onChange: any = () => {}; onTouched: any = () => {}; onControl1Change(value: any) { this.onChange({ ...this.control1.value, control1: value }); } onControl2Change(value: any) { this.onChange({ ...this.control2.value, control2: value }); } } MultiControlComponent updates onChange with new values when control1 and control2 change, ensuring synchronization with the parent form.Handling disabled state in ControlValueAccessor
import { Component, forwardRef, Input } from '@angular/core'; import { NG_VALUE_ACCESSOR, ControlValueAccessor, FormControl } from '@angular/forms'; @Component({ selector: 'app-multi-control', template: ` <input type="text" [formControl]="control1" [attr.disabled]="control1.disabled ? true : null"> <input type="text" [formControl]="control2" [attr.disabled]="control2.disabled ? true : null"> `, providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => MultiControlComponent), multi: true } ] }) export class MultiControlComponent implements ControlValueAccessor { @Input() control1: FormControl; @Input() control2: FormControl; writeValue(value: any) { if (value) { this.control1.setValue(value.control1); this.control2.setValue(value.control2); } } registerOnChange(fn: any) { this.onChange = fn; } registerOnTouched(fn: any) { this.onTouched = fn; } onChange: any = () => {}; onTouched: any = () => {}; } MultiControlComponent applies the disabled attribute based on control1 and control2 states, reflecting the parent form's disabled state.rust jmeter-4.0 virtualhost systemd passwords android-input-method using ibatis graphql-spqr ruby-on-rails-4