Can't get through with async validators

148 Views Asked by At

So, I have a blog, and I'm trying to setup a check, on the creation of a new text, to lock the form if the title already exists.

I got a TextService

 export class TextService {

  private _url = "http://localhost:8080";
  private _textsUrl = "http://localhost:8080/texts";

  findAll(): Observable<Text[]> {
    return this._hc.get<Text[]>(this._textsUrl);
  }

  checkIfTitleExists(testedTitle: string) {
    var existing_titles: String[] = [];
    this.findAll().subscribe(texts => existing_titles = texts.map(t => t.title));
    return of(existing_titles.includes(testedTitle));
  }

I got a TextAddComponent

export class TextAddComponent implements OnInit {

  text: Text = new Text();
  form: any;

  constructor(
    private _ts: TextService,
    private fb: FormBuilder
  ) {}

  ngOnInit(): void {
    this.form = this.fb.group({
      title: ["", this.alreadyExistingTitle(this._ts)],
      subtitle: [""],
      content: [""] ,
    });
  }

  alreadyExistingTitle(ts: TextService): AsyncValidatorFn {
    return (control: AbstractControl): Observable<ValidationErrors | null> => {
      return ts.checkIfTitleExists(control.value).pipe(
        map((result: boolean) =>
          result ? { titleAlreadyExists: true } : null
        )
      )
      }
  }

  onSubmit() {
    this.text= Object.assign(this.text, this.form.value);
    this._ts.save(this.text).subscribe();
  }

}

And I got a template

<form [formGroup]="form" (ngSubmit)="onSubmit()">
    <div>
        <label>
            Title
        </label>
    </div>
    <div>
        <input type="text" formControlName="title">
        <div *ngIf="form.controls.title.errors?.alreadyExistingTitle">
            Title already exists
        </div>
    </div>

    <div>
        <label>
            Subtitle
        </label>
    </div>
    <div>
        <input type="text" formControlName="subtitle">
    </div>

    <div>
        <label>
            Content
        </label>
    </div>
    <div>
        <input type="text" formControlName="content">
    </div>

    <p>
        <button type="submit" [disabled]="!form.valid">Sauvegarder le texte</button>
    </p>
</form>

As you can see, I declare an async validator in the component, and this async validator uses a method from the service

I got two issues here:

  • When console.logging, I witness that my "existing_titles" is always empty, when clearly console logging the findAll shows there are texts and titles. Why? How?
  • Why is my submit button locked, but no error message is displaying? Submit button locked but no error message
1

There are 1 best solutions below

2
Yong Shun On BEST ANSWER
  1. From the below line:
this.findAll().subscribe(texts => existing_titles = texts.map(t => t.title));

It is asynchronous. Thus the next line:

return of(existing_titles.includes(testedTitle));

will be executed without waiting for the Observable to be returned.

Fix: Migrate the checking logic into Observable.

export class TextService {
  ...

  checkIfTitleExists(testedTitle: string): Observable<boolean> {
    return this.findAll().pipe(
      map((texts) => texts.findIndex((t) => t.title == testedTitle) > -1)
    );
  }
}
  1. Fix: As your custom validator is AsyncValidatorFn, pass it into the asyncValidators parameter for the constructor.
this.form = this.fb.group({
  title: ['', { asyncValidators: this.alreadyExistingTitle(this._ts) }],
  ...
});
  1. The custom validator return ValidatorError with the titleAlreadyExists property.
map((result: boolean) =>
  result ? { titleAlreadyExists: true } : null
)

Fix: The error should be "titleAlreadyExists" but not "alreadyExistingTitle".

<div *ngIf="form.controls.title.errors?.titleAlreadyExists">
    Title already exists
</div>

Demo @ StackBlitz