0

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
  • I think you are returning existing_titles before the api call has finished so it's always empty. try making it a promise and awaiting your call before continuing Commented Feb 1, 2023 at 0:16

1 Answer 1

0
  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

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

2 Comments

Thank you so much for your answer. Your solution works, but it raises another issue: if I try to also apply a "classic" validator to a field, like that: title: ["", [Validators.required, { asyncValidators: this.alreadyExistingTitle(this._ts) }]], then, nothing works anymore. The submission button is never locked, and no error message are displayed. Do you know how I can work around that?
You pass the validator incorrectly. It should be: title: ['', { validators: Validators.required, asyncValidators: this.alreadyExistingTitle(this._ts) } ], Pass the (synchronous) validator with validators parameter into constructor. You may check the latest demo.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.