Создание собственных валидаторов
В этом видео мы разберем, как добавлять свои собственные синхронные и асинхронные валидаторы в angular приложении.
Также мы добавим механизм отписок от Observable.
Также мы добавим механизм отписок от Observable.
Сначала мы добавим отписку от Observable. Это полезно делать в любом приложении, чтобы избежать утечек памяти. В современном Angular есть оператор takeUntilDestroyed(). В качестве параметра он принимает объект встроенного класса DestroyRef. Сам класс позволяет регистрировать коллбэки в случае «уничтожения» компонентов (или директив).
Также мы добавим валидаторы. В Angular валидаторы бывают синхронные и асинхронные. Фреймворк имеет несколько встроенных синхронных валидаторов (основаны на html5 валидации). Валидатор в Angular — это функция (ValidatorFn), которая принимает контрол (AbstractControl) и возвращает либо null, либо ValidationError (объект ошибки).
common-validators.ts
import { AbstractControl, ValidationErrors } from '@angular/forms';
export class CommonValidators {
static preventEmptyValue(control: AbstractControl): ValidationErrors | null {
return control.value.trim() ? null : { emptyValue: true };
}
}
is-unuqiue-post-title.ts
import { map, Observable, of } from 'rxjs';
import { AbstractControl, AsyncValidator, ValidationErrors } from '@angular/forms';
import { inject, Injectable } from '@angular/core';
import { PostService } from '../../features/posts/services/post-service';
@Injectable({ providedIn: 'root' })
export class IsUnuqiuePostTitleValidator implements AsyncValidator {
private postService = inject(PostService);
validate(control: AbstractControl): Observable<ValidationErrors | null> {
const postTitle = control.value.trim();
if (!postTitle) {
return of(null);
}
return this.postService
.checkPostTitle(postTitle)
.pipe(map((result) => (result.length ? { titleExists: true } : null)));
}
}
post-service.ts
import { HttpClient, HttpParams } from '@angular/common/http';
import { inject, Injectable } from '@angular/core';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class PostService {
private readonly api = 'https://jsonplaceholder.typicode.com';
private http = inject(HttpClient);
createPost(title: string, body: string, userId: number): Observable<void> {
return this.http.post<void>(`${this.api}/posts`, { title, body, userId });
}
checkPostTitle(title: string): Observable<any> {
const params = new HttpParams().set('title', title);
return this.http.get<any>(`${this.api}/posts`, {params});
}
}
post-form.ts
import { Component, DestroyRef, inject, Input, OnInit } from '@angular/core';
import { MatInputModule } from '@angular/material/input';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatButtonModule } from '@angular/material/button';
import {
FormBuilder,
FormControl,
FormGroup,
ReactiveFormsModule,
Validators,
} from '@angular/forms';
import { PostService } from '../../../../../posts/services/post-service';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { CommonValidators } from '../../../../../../core/validators/common-validators';
import { IsUnuqiuePostTitleValidator } from '../../../../../../core/validators/is-unuqiue-post-title';
interface PostFormGroup {
postTitle: FormControl<string>;
postContent: FormControl<string>;
}
@Component({
selector: 'app-post-form',
imports: [MatFormFieldModule, MatInputModule, MatButtonModule, ReactiveFormsModule],
templateUrl: './post-form.html',
styleUrl: './post-form.scss',
})
export class PostForm implements OnInit {
protected form!: FormGroup<PostFormGroup>;
private destroyRef = inject(DestroyRef);
private isUniquePostTitleValidator = inject(IsUnuqiuePostTitleValidator);
@Input() userId = 0;
constructor(
private formBuilder: FormBuilder,
private postService: PostService,
) {}
ngOnInit(): void {
this.form = this.formBuilder.group({
postTitle: new FormControl<string>('', {
nonNullable: true,
validators: [Validators.required, CommonValidators.preventEmptyValue],
asyncValidators: [
this.isUniquePostTitleValidator.validate.bind(this.isUniquePostTitleValidator),
],
updateOn: 'blur',
}),
postContent: new FormControl<string>('', {
nonNullable: true,
validators: [Validators.required],
}),
});
}
protected createPost() {
if (this.form.invalid || !this.userId) {
return;
}
const formValue = this.form.getRawValue();
this.postService
.createPost(formValue.postTitle, formValue.postContent, this.userId)
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe();
}
}
post-form.html
<h1>Create new post</h1>
<form [formGroup]="form" (ngSubmit)="createPost()">
<div>
<mat-form-field>
<mat-label>Enter post title</mat-label>
<input matInput placeholder="PostService title" formControlName="postTitle" />
@if (form.controls.postTitle.hasError('emptyValue')) {
<mat-error>Invalid value</mat-error>
}
@if (form.controls.postTitle.hasError('titleExists')) {
<mat-error>Title is not unique</mat-error>
}
</mat-form-field>
</div>
<mat-form-field class="example-full-width">
<mat-label>Enter post content</mat-label>
<textarea matInput placeholder="PostService content" formControlName="postContent"></textarea>
</mat-form-field>
<div>
<button [disabled]="form.invalid" matButton="filled">Save</button>
</div>
</form>
0 Comments