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:
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)
);
}
}
AsyncValidatorFn
, pass it into the asyncValidators
parameter for the constructor.this.form = this.fb.group({
title: ['', { asyncValidators: this.alreadyExistingTitle(this._ts) }],
...
});
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>