Provided
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => TestingComponent),
multi: true
}
Injected NgControl
constructor(@Self() @Optional() public control: NgControl) {
this.control && (this.control.valueAccessor = this);
}
And yet something is missing here?
Although @Eliseo answer is very explanatory there is still one addition... If you want to use both external validators and internal ones then parent NgControl Validators must be set accordingly. Furthermore you need to use ngDoCheck lifecycle hook to handle NgControl touch status if you want to use validation as me below is a final working solution
@Component({
selector: 'app-testing',
templateUrl: 'testing.component.html'
})
export class TestingComponent implements ControlValueAccessor, DoCheck, AfterViewInit {
@Input()
public required: boolean;
@ViewChild('input', { read: NgControl })
private inputNgModel: NgModel;
public value: number;
public ngControlTouched: boolean;
constructor(@Optional() @Self() public ngControl: NgControl) {
if (this.ngControl != null) this.ngControl.valueAccessor = this;
}
ngDoCheck() {
if (this.ngControlTouched !== this.ngControl.touched) {
this.ngControlTouched = this.ngControl.touched;
}
}
ngAfterViewInit() {
// Setting ngModel validators to parent NgControl
this.ngControl.control.setValidators(this.inputNgModel.validator);
}
/**
* ControlValueAccessor Implementation
* Methods below
*/
writeValue(value: number): void {
this.value = value;
this.onChange(this.value);
}
onChange: (_: any) => void = (_: any) => {};
onTouched: () => void = () => {};
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
}
// Template
<input
#input="ngModel"
type="text"
class="form-control"
[class.is-invalid]="input.invalid && (input.dirty || input.touched || ngControlTouched)"
[(ngModel)]="value"
(ngModelChange)="onChange(value)"
(blur)="onTouched()"
[required]="required"
/>
// Usage
<app-testing [(ngModel)]="propertyDetails.whatThreeWords" name="testing" required></app-testing>