Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

touched/untouched not updating in custom input component - Angular 2

Tags:

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!

like image 807
sharpmachine Avatar asked Jul 19 '16 00:07

sharpmachine


2 Answers

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!

like image 154
llekn Avatar answered Oct 26 '22 23:10

llekn


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">

like image 21
sharpmachine Avatar answered Oct 26 '22 22:10

sharpmachine