Search code examples
angulartypescriptangular-reactive-forms

Angular retaining input value after form reset


I have a reactive form with two fields.First is custom input using ControlValueAccessor, and last is just regular HTML input.

Problem is, after performing form.reset(), the value of custom input is retained event its value in reactive form is null already.

enter image description here

As you can see in image, the first time I input and clear the values, it is working well.

But, as second time and onwards, the input value is STILL retained in custom input component. While, the normal HTML input is cleared and working well regardless of how many times I click Clear Input. Can you help me, please? Did I miss to put something?

Files:

  • app.component.ts/html: where the form lives
  • custom-input.component.ts/html: custom input component

Here is the form:

ts file

import { Component } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
})
export class AppComponent {
  form: FormGroup;

  constructor(private fb: FormBuilder) {
    this.form = this.fb.group({
      firstName: [{ value: '', disabled: false }],
      lastName: [{ value: '', disabled: false }],
    });
  }

  clearInput() {
    this.form.reset();
  }
}

html file:

<form [formGroup]="form">
  <app-custom-input formControlName="firstName"></app-custom-input>
  <input formControlName="lastName" placeholder="Last name" />
  <button (click)="clearInput()">Clear Input</button>
</form>

<br />
<pre>{{ form.value | json }}</pre>

Here is the custom input file:

ts file:

import { Component, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

@Component({
  selector: 'app-custom-input',
  templateUrl: './custom-input.component.html',
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => CustomInputComponent),
      multi: true,
    },
  ],
})
export class CustomInputComponent implements ControlValueAccessor {
  value: string;
  changed: (value: any) => void;
  touched: () => void;
  isDisabled: boolean;

  writeValue(value: string): void {
    this.value = value;
  }

  registerOnChange(fn: any): void {
    this.changed = fn;
  }

  registerOnTouched(fn: any): void {
    this.touched = fn;
  }

  setDisabledState(isDisabled: boolean): void {
    this.isDisabled = isDisabled;
  }

  onChange(event: Event): void {
    const value: string = (<HTMLInputElement>event.target).value;

    this.changed(value);
  }
}

html file:

<input
  placeholder="First name"
  [disabled]="isDisabled"
  [value]="value"
  (input)="onChange($event)"
  (blur)="touched()"
/>

Full working code is here


Solution

  • Ok, you doing things well, but, you have to research a little bit deeper about this problem. If you go through HTML specification, you may find that the value attribute for the input html element is just an initial value. And that's why you get only first change if you push the reset button (actually you assign value there and writeValue method invokes).

    So the solutions are several, the simplest and relative to your code style is to get the reference to the input and assign value manually:

    custom-input.component.html

    <input
      placeholder="First name"
      [disabled]="isDisabled"
      (input)="onChange($event)"
      (blur)="touched()"
      #inputRef
    />
    
    

    custom-input.component.ts

    export class CustomInputComponent implements ControlValueAccessor {
      @ViewChild('inputRef')
      inputRef: ElementRef<HTMLInputElement>;
      changed: (value: any) => void;
      touched: () => void;
      isDisabled: boolean;
    
      writeValue(value: string): void {
        if (this.inputRef) {
          this.inputRef.nativeElement.value = value;
        }
      }
    
      ...
    

    Another solution is to use ngModel and then it can work with value property binded to the input.