admin管理员组

文章数量:1323730

I'm currently facing an issue with infinite calls to a getter method in my Angular component that handles form validation. The getter is used to determine the validity of a form control, but it seems to be causing an infinite loop due to Angular's change detection mechanism.

text-inputponent.ts

import { CommonModule } from '@angular/common';
import { IonLabel, IonInput } from '@ionic/angular/standalone';
import { Component, inject, Input, OnDestroy, OnInit } from '@angular/core';
import { ControlContainer, FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';

@Component({
  standalone: true,
  selector: 'app-text-input',
  imports: [ReactiveFormsModule, IonLabel, IonInput, CommonModule],
  viewProviders: [
    {
      provide: ControlContainer,
      useFactory: () => inject(ControlContainer, { skipSelf: true }),
    },
  ],
  template: `
    <div class="input-section ion-padding-top">
      <!-- label -->
      <ion-label class="input-label">
        {{ label }}
        @if (required) {
          <span class="required-star">*</span>
        }
      </ion-label>

      <!-- input -->
      <ion-input
        mode="md"
        fill="outline"
        [ngClass]="inputClass"
        [placeholder]="placeholderText"
        [formControlName]="controlName"
      />

      <!-- validation -->
      @if (isControlInvalid) {
        <ion-label class="invalid-label">Please enter the {{ label.toLowerCase() }}.</ion-label>
      }
    </div>
  `,
})
export class TextInputComponent implements OnInit, OnDestroy {
  @Input() required = true;
  @Input() formSubmitted = false;
  @Input({ required: true }) label = '';
  @Input({ required: true }) controlName = '';
  @Input({ required: true }) placeholderText = '';

  constructor(
    private formBuilder: FormBuilder,
    private parentContainer: ControlContainer,
  ) {}

  get parentFormGroup() {
    return this.parentContainer.control as FormGroup;
  }

  // Getter to check if the control is invalid
  get isControlInvalid() {
    console.log('called 1');
    return this.formSubmitted && this.parentFormGroup.get(this.controlName)?.invalid;
  }

  // Getter to get the input class
  get inputClass() {
    console.log('called 2');
    return this.isControlInvalid ? 'invalid-input' : 'valid-input';
  }

  ngOnInit() {
    this.parentFormGroup.addControl(this.controlName, this.formBuilder.control('', Validators.required));
  }

  ngOnDestroy() {
    this.parentFormGroup.removeControl(this.controlName);
  }
}

parentponent.ts

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

@Component({
  selector: 'app-parent',
  templateUrl: './parentponent.html',
})
export class ParentComponent implements OnInit {
  eventForm!: FormGroup;
  formSubmitted: boolean = false;

  constructor(private fb: FormBuilder) {}

  ngOnInit() {
    // automatically add the location when call the app-text-input component
    this.eventForm = this.fb.group({});
  }
}

parentponent.html

<form [formGroup]="eventForm">
  <app-text-input 
    label="Venue"
    controlName="location" 
    [formSubmitted]="formSubmitted" 
    placeholderText="Venue Name" />
</form>

What strategies can I implement to prevent infinite calls to the getter method? Are there best practices for managing state in Angular reactive forms that could help in this situation?

I'm currently facing an issue with infinite calls to a getter method in my Angular component that handles form validation. The getter is used to determine the validity of a form control, but it seems to be causing an infinite loop due to Angular's change detection mechanism.

text-inputponent.ts

import { CommonModule } from '@angular/common';
import { IonLabel, IonInput } from '@ionic/angular/standalone';
import { Component, inject, Input, OnDestroy, OnInit } from '@angular/core';
import { ControlContainer, FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';

@Component({
  standalone: true,
  selector: 'app-text-input',
  imports: [ReactiveFormsModule, IonLabel, IonInput, CommonModule],
  viewProviders: [
    {
      provide: ControlContainer,
      useFactory: () => inject(ControlContainer, { skipSelf: true }),
    },
  ],
  template: `
    <div class="input-section ion-padding-top">
      <!-- label -->
      <ion-label class="input-label">
        {{ label }}
        @if (required) {
          <span class="required-star">*</span>
        }
      </ion-label>

      <!-- input -->
      <ion-input
        mode="md"
        fill="outline"
        [ngClass]="inputClass"
        [placeholder]="placeholderText"
        [formControlName]="controlName"
      />

      <!-- validation -->
      @if (isControlInvalid) {
        <ion-label class="invalid-label">Please enter the {{ label.toLowerCase() }}.</ion-label>
      }
    </div>
  `,
})
export class TextInputComponent implements OnInit, OnDestroy {
  @Input() required = true;
  @Input() formSubmitted = false;
  @Input({ required: true }) label = '';
  @Input({ required: true }) controlName = '';
  @Input({ required: true }) placeholderText = '';

  constructor(
    private formBuilder: FormBuilder,
    private parentContainer: ControlContainer,
  ) {}

  get parentFormGroup() {
    return this.parentContainer.control as FormGroup;
  }

  // Getter to check if the control is invalid
  get isControlInvalid() {
    console.log('called 1');
    return this.formSubmitted && this.parentFormGroup.get(this.controlName)?.invalid;
  }

  // Getter to get the input class
  get inputClass() {
    console.log('called 2');
    return this.isControlInvalid ? 'invalid-input' : 'valid-input';
  }

  ngOnInit() {
    this.parentFormGroup.addControl(this.controlName, this.formBuilder.control('', Validators.required));
  }

  ngOnDestroy() {
    this.parentFormGroup.removeControl(this.controlName);
  }
}

parentponent.ts

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

@Component({
  selector: 'app-parent',
  templateUrl: './parentponent.html',
})
export class ParentComponent implements OnInit {
  eventForm!: FormGroup;
  formSubmitted: boolean = false;

  constructor(private fb: FormBuilder) {}

  ngOnInit() {
    // automatically add the location when call the app-text-input component
    this.eventForm = this.fb.group({});
  }
}

parentponent.html

<form [formGroup]="eventForm">
  <app-text-input 
    label="Venue"
    controlName="location" 
    [formSubmitted]="formSubmitted" 
    placeholderText="Venue Name" />
</form>

What strategies can I implement to prevent infinite calls to the getter method? Are there best practices for managing state in Angular reactive forms that could help in this situation?

Share Improve this question asked Jan 12 at 6:14 Ravi GaudRavi Gaud 5231 silver badge12 bronze badges 1
  • calling getters durin change detection is fine, but what is causing that change detection to run? I think you should try to find the reason of that and eliminate the problem cause – Andrei Commented Jan 12 at 17:26
Add a comment  | 

1 Answer 1

Reset to default 1

The getter methods are called on every change detection cycle, you can try ChangeDetectionStrategy.OnPush to reduce the number of calls, this will reduce the number of change detection cycles.

@Component({
  standalone: true,
  selector: 'app-text-input',
  imports: [ReactiveFormsModule, IonLabel, IonInput, CommonModule],
  changeDetection: ChangeDetectionStrategy.OnPush
})

Also I think you are trying to implement angular custom form control, maybe check this tutorial, the code will be a little different.

Angular Custom Form Controls: Complete Guide

本文标签: How to Prevent Infinite Calls to Angular Getter Methods in Form ValidationStack Overflow