Angular retaining input value after form reset
Asked Answered
C

1

6

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

Chicory answered 2/6, 2023 at 10:47 Comment(4)
Just to thank you on great question documentation.Asquith
In html change [value]="value" by [(ngModel)]="value". Very nice question by the wayUnbuild
Just noticed the space you put after writing "First ". If you do without a space it does not work as expected. But I don't know why :)Solana
@BrunoMiguel well... you just solved a sleeping bug in my code too. Thank you! –Asquith
D
4

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.

Destroy answered 2/6, 2023 at 11:30 Comment(5)
stackblitz.com/edit/…Destroy
Hello, Anton. Thank you for your answer. I did not know that I can incorporate ngModel with Reactive Form, thanks for that. So, on your code, we are directly setting the input's value (natively in DOM) using @ViewChild? I need to dig into it deeper as I am newbie to Angular. Thanks again mateChicory
@Chicory You are not supposed to use ngModel with ReactiveForms, because it already provides you binded ngModels, your formControlName is double way binded, it is your ngModel. You are supposted to get input value by calling form.controls.<formControlName>.valueWhilom
Actually, you are using Reactive forms only for wrapper component, but for inner html you can do whatever you want. For example <input [ngModel]="value" (ngModelChanged)="changed($event)" /> And everything works well. That's just about the level of abstraction you use.Destroy
And if you want to know how to get control from wrapper, you can go over my article at @Medium: medium.com/gitconnected/…Destroy

© 2022 - 2024 — McMap. All rights reserved.