4

I am trying to access the valueChanges of another field (B) in an asynchronous validator (of field A). But the validation is only triggered when the value of field A changes. If only the value of B changes, the status remains "PENDING" until the value of A is changed.

What I want to do is create a validator that triggers when the given observables emits a value AND when the value of field B changes. I can imagine that the problem has something to do with the valueChanges observable not emitting any value, but how do I fix that?

StackBlitz which recreates the problem: https://stackblitz.com/edit/angular-ivy-epoeen?devtoolsheight=33&file=src/app/app.component.ts

Example component:

export class AppComponent {
  public form = new FormGroup({
    fieldA: new FormControl(null, { updateOn: 'blur' }),
    fieldB: new FormControl(null, { updateOn: 'blur' }),
  });

  constructor() {
    this.fieldBControl.setAsyncValidators(
      FieldBValidators.asyncValidateFieldBShouldNotBe(this.fieldA$)
    );

    this.fieldA$.subscribe((value) => console.log('fieldA changed to', value));
    this.fieldB$.subscribe((value) => console.log('fieldB changed to', value));
    this.fieldBStatus$.subscribe((status) => console.log('status changed to', status));
  }

  public get fieldAControl(): FormControl {
    return this.form.get('fieldA') as FormControl;
  }

  public get fieldA$(): Observable<string> {
    return this.fieldAControl.valueChanges;
  }

  public get fieldBControl(): FormControl {
    return this.form.get('fieldB') as FormControl;
  }

  public get fieldB$(): Observable<string> {
    return this.fieldBControl.valueChanges;
  }

  public get fieldBStatus$(): Observable<string> {
    return this.fieldBControl.statusChanges;
  }
}

Validator:

export class FieldBValidators {
  public static asyncValidateFieldBShouldNotBe(
    value$: Observable<string>
  ): AsyncValidatorFn {
    return (control: AbstractControl): Observable<ValidationErrors> =>
      value$.pipe(
        tap((value) => {
          console.log('fieldA', value);
          console.log('fieldB', control.value);
        }),
        take(1),
        map(
          (value): ValidationErrors => {
            if (value === control.value) {
              return { error: true };
            }
            return null;
          }
        )
      );
  }
}

Console output is like this:

app.component.ts:24      fieldA changed to a
fieldB.validators.ts:23  fieldA a
fieldB.validators.ts:24  fieldB null
app.component.ts:25      fieldB changed to a
app.component.ts:27      status changed to PENDING
app.component.ts:25      fieldB changed to b
app.component.ts:27      status changed to PENDING
app.component.ts:24      fieldA changed to b
fieldB.validators.ts:23  fieldA b
fieldB.validators.ts:24  fieldB b
app.component.ts:27      status changed to INVALID

My attempt was to add a startWith pipe to the value, but then the Validator function always gets the initial value.

Thank you very much for any input. Have a nice day!

1 Answer 1

1

I don't get why you try to compare 2 form values in an asynchronous way. There is a concept called Cross form validation. That is a validator function which is installed on the form group instead of the form control. That concept would look like this for you:

(Stackblitz: https://stackblitz.com/edit/angular-ivy-epz6bv?file=src%2Fapp%2Fapp.component.ts)

export class FieldBValidators {
  public static fieldsShouldNotEqual(left: string, right: string): ValidatorFn {
    return (group: FormGroup): ValidationErrors | null =>
      group.get(left).value == group.get(right).value ? { error: true } : null;
  }
}
this.form.setValidators(
      FieldBValidators.fieldsShouldNotEqual("fieldA", "fieldB")
    ); // Field A should not equal Field B
Sign up to request clarification or add additional context in comments.

2 Comments

The reason for this I'm having two different scenarios for the same (reused) form group. In one scenario the value comes from a different field (like described), in the other scenario the value comes from an ngrx store.
Then I honestly think you should add the validators accordingly. For the described scenerio use the cross field validation. For the store scenario use an async validator.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.