Создание собственных валидаторов

Главная » Видеоуроки » JavaScript » Создание собственных валидаторов
В этом видео мы разберем, как добавлять свои собственные синхронные и асинхронные валидаторы в angular приложении.
Также мы добавим механизм отписок от 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

Submit a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *


Срок проверки reCAPTCHA истек. Перезагрузите страницу.

Pin It on Pinterest

Share This