I have a custom input component that is updating validation and states with the exception of touched/untouched. Everything else state-wise (pristine/dirty) works as expected.
Here's a plunker: https://plnkr.co/edit/O9KWzwhjvySnXd7vyo71
import { Component, OnInit, Input, ElementRef, forwardRef, Renderer } from '@angular/core'; import { REACTIVE_FORM_DIRECTIVES, Validator, Validators, NG_VALUE_ACCESSOR, ControlValueAccessor} from '@angular/forms'; export const CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR: any = /*@ts2dart_const*/ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CustomInputComponent), multi: true }; const noop = () => {}; @Component({ selector: 'my-custom-input', providers: [CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR], template: ` <div class="form-group"> <label>CUSTOM INPUT</label> <input type="text" class="form-control" [(ngModel)]="value" required> <p *ngIf="control.errors.required && control.touched">Field is required</p> <strong>Has input been touched: {{control.touched ? 'Yes' : 'No'}}</strong><br> <strong>Is input untouched: {{control.untouched ? 'Yes' : 'No'}}</strong><br> <strong>Is input dirty: {{control.dirty ? 'Yes' : 'No'}}</strong> <br> <strong>Is input pristine: {{control.pristine ? 'Yes' : 'No'}}</strong> </div> <div> In Custom Component: {{value}} </div> ` }) export class CustomInputComponent implements ControlValueAccessor { @Input() control; // The internal data model private _value: any = ''; //Placeholders for the callbacks private _onTouchedCallback: (_:any) => void = noop; private _onChangeCallback: (_:any) => void = noop; //get accessor get value(): any { return this._value; }; //set accessor including call the onchange callback set value(v: any) { if (v !== this._value) { this._value = v; this._onChangeCallback(v); } } //Set touched on blur onTouched(){ this._onTouchedCallback(null); } //From ControlValueAccessor interface writeValue(value: any) { this._value = value; } //From ControlValueAccessor interface registerOnChange(fn: any) { this._onChangeCallback = fn; } //From ControlValueAccessor interface registerOnTouched(fn: any) { this._onTouchedCallback = fn; } }
Thanks for any help!
Just stepped on @sharpmachine answer and it helped to solve my problem. I just would like to improve it:
Instead of having to bind the blur
event to onTouched()
at the template level (which can be error-prone) it is possible to expose the ControlValueAccessor
as a Directive
and bind the event there.
import { Directive, forwardRef } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; export const CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR: any = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CustomInputAccessor), multi: true }; @Directive({ selector: 'my-custom-input', host: {'(blur)': 'onTouched($event)'}, providers: [CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR], }) export class CustomInputAccessor implements ControlValueAccessor { // The internal data model private _value: any = ''; public onChange: any = (_) => { /*Empty*/ } public onTouched: any = () => { /*Empty*/ } get value(): any { return this._value; }; set value(v: any) { if (v !== this._value) { this._value = v; this.onChange(v); } } writeValue(value: any) { this._value = value; } registerOnChange(fn: any) { this.onChange = fn; } registerOnTouched(fn: any) { this.onTouched = fn; } }
This way you should be able to use the component without having to bind the blur
event each time you use it.
Hope it helps!
I was making two mistakes, like a knobhead. So the template needs to be:
<div class="form-group"> <label>CUSTOM INPUT</label> <input type="text" class="form-control" [(ngModel)]="value" (blur)="onTouched($event)" required> <p *ngIf="control?.errors?.required && control?.touched">Field is required</p> <strong>Has input been touched: {{control.touched ? 'Yes' : 'No'}}</strong><br> <strong>Is input untouched: {{control.untouched ? 'Yes' : 'No'}}</strong><br> <strong>Is input dirty: {{control.dirty ? 'Yes' : 'No'}}</strong> <br> <strong>Is input pristine: {{control.pristine ? 'Yes' : 'No'}}</strong> </div> <div> In Custom Component: {{value}} </div>
So the two things where the (blur)="onTouched($event)"
on the input, and the <p *ngIf="control?.errors?.required && control?.touched">
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With