1

I created a custom text field with Angular Material. Everything seems to be working correctly except when I try to reset the FormGroup I get RangeError: Maximum call stack size exceeded.

I set up the custom text field using this StackOverflow answer: https://stackoverflow.com/a/60071669/2026659.

Here's my custom text field component typescript:

import { Component, Input, Optional, Self, AfterViewInit } from '@angular/core';
import { ControlValueAccessor, NgControl, FormControl } from '@angular/forms';
import { of } from 'rxjs';
import { skipWhile, take } from 'rxjs/operators';

export class RequiredFormControl extends FormControl {
  required = true;
}
@Component({
  selector: 'input-text-field',
  templateUrl: './input-text-field.component.html',
  styleUrls: ['./input-text-field.component.scss']
})
export class InputTextFieldComponent implements AfterViewInit, ControlValueAccessor {
  public _formControl = new FormControl(); 
  public onChange = (value: any) => {};

  @Input() public fieldId: string;
  @Input() public isRequired: boolean;
  @Input() public fieldClass: string;
  @Input() public labelText: string;
  @Input() public tabNumber: number;

  constructor(@Self() @Optional() public ngControl: NgControl) {
    if(this.ngControl) {
      this.ngControl.valueAccessor = this;
    }
  }
  
  ngAfterViewInit(): void {
    if (this.ngControl) {
      of(this.ngControl.control)
        .pipe(
          skipWhile(fc => !fc),
          take(1)
        )
        .subscribe(fc => {
          this.formControl = fc as FormControl;
        });
    }
  }

  get formControl() :FormControl|RequiredFormControl {
    return this._formControl;
  }
  set formControl(forControl:FormControl|RequiredFormControl)  {
    this._formControl = forControl;
  }

  registerOnChange(fn: (value: any) => void): void {
    this.onChange = fn;
  }

  registerOnTouched(fn: (value: any) => void): void {}

  writeValue(value: any): void {
    if(this.formControl) this.formControl.setValue(value, { emitEvent: false });
  }

}

Here's my custom text field html:

<mat-form-field>
  <mat-label><span *ngIf="isRequired" class="input-required">*</span> {{labelText}}</mat-label>
  <input type="text"
    matInput
    [formControl]="formControl"
    [id]="fieldId" 
    [class]="fieldClass" 
    [tabIndex]="tabNumber"
>
</mat-form-field>

I create the associated FormGroup and FormControl in my parent component:

const userProfile = new FormGroup({
      firstName: new FormControl(null, Validators.required),
      lastName: new FormControl(null, Validators.required),
      ....
});

I have a button in the parent component that triggers a function that resets the form:

public resetFormEventClick() {
  this.userProfile.reset();
}

I looked at the stack trace and I found the error occurs when the code gets to the writeValue function in my custom text field component.

I tried adding a form reset function to this Stackblitz and got the same RangeError. I was trying to see if the error was something peculiar to my code.

It seems the error is when the reset function tries to change the value of the custom text field. All the function is doing relating to the value is changing it back to null. I don't understand why it would throw an error relating to the call stack. Any help would be appreciated. I've been having a tough time trying to get this custom component to completely work.

UPDATE: I tried using patchValue instead of setValue in the writeValue function like this: this.formControl.patchValue(value, {onlySelf: true, emitEvent: false}); but still got the same RangeError.

I added a console.log inside the writeValue function and when this.userProfile.reset() is triggered the console.log message keeps repeating then the RangeError occurs. There seems to be an infinite loop where the code keeps triggering the writeValue function.

mdailey77
  • 1,673
  • 4
  • 26
  • 52
  • your'e saying -in the function ngAfterViewInit-, that the "formControl" **inside** your component is the ngControl **outside** the component- I ignore what is the reason to do it, but this is the reason you get a recursive error. I imagine this code is about a component that not implements ControlValueAccessor but I'm not sure – Eliseo Jun 22 '21 at 13:51
  • Oh interesting. I went with that approach because the other approach I tried using in creating the custom control, implementing MatFormFieldControl, made the reactive form validation not work. I need to bind the form control to an NgControl and that's the way that seemed to work best. – mdailey77 Jun 22 '21 at 14:00
  • 1
    I see that the code go from https://stackoverflow.com/questions/60062444/binding-formcontrol-validators-to-a-custom-form-material-select-component/60071352#60071352. perhafs you can solve simple make an if in write value: `if(this.formControl && this.formControl.value!=value) this.formControl.setValue(value, { emitEvent: false });` – Eliseo Jun 22 '21 at 14:09

1 Answers1

1

Thanks to @Eliseo's suggestion I was able to solve the error.

I changed the writeValue function to this:

writeValue(value: any): void {
    if(this.formControl && this.formControl.value!=value) this.formControl.setValue(value, { emitEvent: false });
  }

Updating the if statement stopped the infinite loop.

mdailey77
  • 1,673
  • 4
  • 26
  • 52