1

Here's the StackBlitz showing the problem.

I have a reactive form with an Email field and Send Verification Code button. When the code is sent, the Verification Code field appears, so the user can type in the code and it will be validated immediately. If the code is invalid, the Verification Code field must be marked as invalid. But this doesn't seem to work using signal.

Here's the verification code in the form template:

<mat-form-field appearance="fill" id="veriCodeFld">
  <mat-label>Verification Code</mat-label>
  <input
    autocomplete="off"
    formControlName="code"
    matInput
    placeholder="Code from email"
    required
    type="text"
  />
  @if(isTouchedAndInvalid('code') || (!store.emailVerified() &&
    store.veriCodeIsWrong())) {
    <mat-error>{{ getErrorMessage("code") }}</mat-error>
  } @if (store.emailVerified() && this.codeCrl?.valid) {
    <mat-icon matSuffix>check</mat-icon>
  } @else if (!store.emailVerified() && store.veriCodeIsWrong()) {
    <mat-icon matSuffix>error</mat-icon>
  }
</mat-form-field>

The custom validator:

export function verificationCodeValidator(
  veriCodeIsWrongSignal: Signal<boolean>
): ValidatorFn {
  return (control: AbstractControl): ValidationErrors | null => {
    return veriCodeIsWrongSignal() ? { verificationCodeInvalid: true } : null;
  };
}

And the way it's applied to the field:

frm = this.fb.group({
  email: [
    '',
    [Validators.email, Validators.required],
    null,
    { updateOn: 'blur' },
  ],
  code: [
    '',
    [
      Validators.required,
      Validators.minLength(6),
      verificationCodeValidator(this.store.veriCodeIsWrong),
    ],
    null,
  ],
});

The validation is performed when the user types in the code (from the form code):

constructor() {
  this.validationMessages = this.initValidationMessages();

  this.frm
    .get('code')!
    .valueChanges.pipe(
      debounceTime(400),
      distinctUntilChanged(),
      takeUntilDestroyed()
    )
    .subscribe((value) => this.validateCode(value));
}

How can I make it work?

1
  • Any reason for downvote? Commented Oct 13 at 7:48

2 Answers 2

0

Seems like the signal is not updating the form control state in real-time.

Approach 1: With effect signal

With the effect signal, update the code control when there is a change from the store.veriCodeIsWrong signal.

import { ..., effect } from '@angular/core';

constructor() {
  ...

  effect(() => {
    const veriCodeIsWrong = this.store.veriCodeIsWrong();
    this.frm.get('code')!.updateValueAndValidity({ emitEvent: false });
  });
}

Demo Approach 1 @ StackBlitz


Approach 2: Convert the signal to observable via toObservable() and listen to it

Listen to the store.veriCodeIsWrong signal as observable via toObservable() and update the form control state.

import { ..., toObservable } from '@angular/core/rxjs-interop';

veriCodeIsWrong$ = toObservable(this.store.veriCodeIsWrong);

ngOnInit() {
  this.veriCodeIsWrong$
    .pipe(takeUntilDestroyed())
    .subscribe((_) =>
      this.frm.get('code')!.updateValueAndValidity({ emitEvent: false })
    );
}

Demo Approach 2 @ StackBlitz

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you! I used an effect and everything works
0

Two concepts (but not the solution)

  1. a mat-error inside a form-field ONLY is showed when the formControl is touched and invalid. So has no sense
   @if (emailCrl?.invalid) { //you can remove the `@if`
        <mat-error>{{ getErrorMessage("email") }}</mat-error>
  }
  //NEVER is showed if !isTouchedAndInvalid
  @if(isTouchedAndInvalid('code') || (!store.emailVerified() &&
        store.veriCodeIsWrong())) {
        <mat-error>{{ getErrorMessage("code") }}</mat-error>
        }  
  1. a validator only is executed when you change the input or you updateValue manually using updateValueAndValidity So the approach is use effect as @Yong Sun says.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.