Wydajność komponentu walidacji

Wydajność komponentu walidacji

Wątek przeniesiony 2025-04-01 20:10 z JavaScript przez Riddle.

bbhzp
  • Rejestracja:około rok
  • Ostatnio:około 6 godzin
  • Postów:72
1

Cześć,
na wstępie chciałbym zaznaczyć, że Angular i TypeScript to dla mnie kompletnie nowe technologie.

W swoim projekcie korzystam z ReactiveForms. Żeby nie musieć pod każdym polem pisać tej samej logiki, stworzyłem sobie komponent ValidationMessage:

Kopiuj
import { Component, Input } from '@angular/core';
import { AbstractControl, FormControl, ValidationErrors } from '@angular/forms';

@Component({
  selector: 'app-validation-message',
  imports: [],
  templateUrl: './validation-message.component.html',
  styleUrl: './validation-message.component.scss'
})
export class ValidationMessageComponent {
  readonly defaultErrors: Record<string, (error: ValidationErrors) => string> = {
    required: () => "Należy uzupełnić to pole!",
    minlength: (error) => `Należy podać przynajmniej ${error['requiredLength']} znaków.`,
    maxlength: (error) => `Przekroczono dozwolony limit znaków (${error['requiredLength']})!`,
    email: () => "Należy podać prawidłowy adres E-Mail!"
  };

  @Input() showFor: AbstractControl = new FormControl();

  get errorMessage(): string {
    if (this.showFor.invalid && (this.showFor.dirty || this.showFor.touched)) {
      const firstKey = Object.keys(this.showFor.errors!)[0];
      const errorFn = this.defaultErrors[firstKey];

      if (errorFn)
        return errorFn(this.showFor.errors![firstKey]);
    }

    return "";
  }
}

Kopiuj
<div class="validation-error">
    {{ errorMessage }}
</div>

Przykładowe wykorzystanie:

<input type="password" formControlName="password" id="password" class="form-input" placeholder="Hasło">
<app-validation-message [showFor]="frmLogin.controls['password']"></app-validation-message>

Czy użycie gettera w tym komponencie nie będzie sprawiać problemów z wydajnością?

Początkowo starałem się użyć signali, ale w żaden sposób nie mogłem zmusić jej do automatycznego aktualizowania treści błędu.

Z góry bardzo dziękuję!

edytowany 1x, ostatnio: Riddle
Riddle
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:minuta
  • Postów:10099
1

Technicznie rzecz biorąc, jeśli to jest getter, to on się uruchomi tyle razy ile był użyty podczas rendera, natomiast sygnał by się odpalił raz. Ale biorąc pod uwagę że to jest zwykły if i operacje na słowniku, to nawet jakbyś to odpalił 10 tys. razy to i tak nie wpłynęłoby to na wydajność, nawet byś tego nie zauważył. Musiałbyś tam robić coś ciężkiego, jak sortowanie dużej ilości danych żeby to miało znaczenie.

A co do Twojego problemu z sygnałem - no jasno widać że error message ma zależeć od stanu inputa showFor, więc musiałbyś nasłuchać na zmianę pola w showFor i wtedy zmienić wartość w sygnale który to rozpropaguje do widoku.

PS: Chociaż tak jak teraz myślę, to to jest trochę słaby design że ten komponent przyjmuje kontrolkę. IMHO to byłby lepszy design gdyby po prostu przyjął rodzaj błędu - miałbyś wtedy trochę luźniejsze powiązanie, co przekłada się na lepszy design.

edytowany 2x, ostatnio: Riddle
bbhzp
  • Rejestracja:około rok
  • Ostatnio:około 6 godzin
  • Postów:72
0
Riddle napisał(a)

Dziękuję za odpowiedź :)

PS: Chociaż tak jak teraz myślę, to to jest trochę słaby design że ten komponent przyjmuje kontrolkę. IMHO to byłby lepszy design gdyby po prostu przyjął rodzaj błędu - miałbyś wtedy trochę luźniejsze powiązanie, co przekłada się na lepszy design.

Rozumiem, że wtedy przyjmowany typ to byłby ValidationErrors? Faktycznie, byłoby to o wiele lepsze rozwiązanie, ale wtedy nie miałbym jak sprawdzić, czy kontrolka została już "ubrudzona", bo błędy chcę pokazywać dopiero po pierwszej utracie fokusu:

Kopiuj
if (this.showFor.dirty || this.showFor.touched) {

A co do Twojego problemu z sygnałem - no jasno widać że error message ma zależeć od stanu inputa showFor, więc musiałbyś nasłuchać na zmianę pola w showFor i wtedy zmienić wartość w sygnale który to rozpropaguje do widoku.

Tak też zrobiłem :)

Kopiuj
@Input() showFor: AbstractControl = new FormControl();
errorMessage = signal<string>("");

private showForValueChangesSub: Subscription | null = null;

ngOnInit(): void {
  this.showForValueChangesSub = this.showFor.valueChanges.subscribe(() => this.updateErrorMessageState());
}

ngOnDestroy(): void {
  this.showForValueChangesSub?.unsubscribe();
}

updateErrorMessageState(): void {
  if (this.showFor.invalid && (this.showFor.dirty || this.showFor.touched)) {
    alert("Test");

    const firstKey = Object.keys(this.showFor.errors!)[0];
    const errorFn = this.defaultErrors[firstKey];

    if (errorFn)
      this.errorMessage.set(errorFn(this.showFor.errors![firstKey]));
  }

  this.errorMessage.set("");
}
Kopiuj
<div class="validation-error">
    {{ errorMessage() }}
</div>

I o ile alert("Test") się pokazuje, to errorMessage() w HTML już się nie zmienia.

edytowany 2x, ostatnio: bbhzp
obscurity
  • Rejestracja:około 6 lat
  • Ostatnio:około 15 godzin
1

na końcu zawsze ustawiasz na puste

Kopiuj
this.errorMessage.set("");

skoro już używasz signal to zamiast używać ngOnDestroy i unsubscribe możesz użyć takeUntilDestroyed


"A car won't take your job, another horse driving a car will." - Horse influencer, 1910
edytowany 1x, ostatnio: obscurity
bbhzp
  • Rejestracja:około rok
  • Ostatnio:około 6 godzin
  • Postów:72
0
obscurity napisał(a):

na końcu zawsze ustawiasz na puste

Kopiuj
this.errorMessage.set("");

Faktycznie :) Z rozpędzenia zamieniłem returny na errorMessage.set bez dalszego sprawdzania :D

skoro już używasz signal to zamiast używać ngOnDestroy i unsubscribe możesz użyć takeUntilDestroyed

Dziękuję za radę, poczytam.

edytowany 1x, ostatnio: bbhzp
Riddle
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:minuta
  • Postów:10099
0
bbhzp napisał(a):
Riddle napisał(a)

PS: Chociaż tak jak teraz myślę, to to jest trochę słaby design że ten komponent przyjmuje kontrolkę. IMHO to byłby lepszy design gdyby po prostu przyjął rodzaj błędu - miałbyś wtedy trochę luźniejsze powiązanie, co przekłada się na lepszy design.

Rozumiem, że wtedy przyjmowany typ to byłby ValidationErrors? Faktycznie, byłoby to o wiele lepsze rozwiązanie, ale wtedy nie miałbym jak sprawdzić, czy kontrolka została już "ubrudzona", bo błędy chcę pokazywać dopiero po pierwszej utracie fokusu:

Naturalnie kod Twojej funkcji errorMessage która sprawdza this.showFor.invalid && (this.showFor.dirty || this.showFor.touched musi być gdzieś, ale nie koniecznie musi być w ValidationMessageComponent.

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.