Поиск по сайту:

Как использовать ControlValueAccessor для создания пользовательских элементов управления формы в Angular


Введение

При создании форм в Angular иногда вы хотите иметь ввод, который не является стандартным текстовым вводом, выбором или флажком. Внедрив интерфейс ControlValueAccessor и зарегистрировав компонент как NG_VALUE_ACCESSOR, вы можете беспрепятственно интегрировать свой настраиваемый элемент управления формы в управляемые шаблоном или реактивные формы так же, как если бы это был собственный ввод. !

В этой статье вы преобразуете базовый компонент ввода рейтинга в ControlValueAccessor.

Предпосылки

Для выполнения этого урока вам понадобятся:

  • Node.js установлен локально, что можно сделать, следуя инструкциям по установке Node.js и созданию локальной среды разработки.
  • Некоторое знакомство с использованием компонентов Angular может быть полезным.

Это руководство было проверено с помощью Node v16.4.2, npm v7.18.1, angular v12.1.1.

Шаг 1 — Настройка проекта

Сначала создайте новый RatingInputComponent.

Это можно сделать с помощью @angular/cli:

  1. ng generate component rating-input --inline-template --inline-style --skip-tests --flat --prefix

Это добавит новый компонент в приложение declarations и создаст файл rating-input.component.ts:

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'rating-input',
  template: `
    <p>
      rating-input works!
    </p>
  `,
  styles: [
  ]
})
export class RatingInputComponent implements OnInit {

  constructor() { }

  ngOnInit(): void {
  }

}

Добавьте шаблон, стили и логику:

import { Component } from '@angular/core';

@Component({
  selector: 'rating-input',
  template: `
    <span
      <^>*ngFor="let starred of stars; let i = index"
      (click)="rate(i + (starred ? (value > i + 1 ? 1 : 0) : 1))"<^>
    >
      <ng-container *ngIf="starred; else noStar">⭐</ng-container>
      <ng-template #noStar>·</ng-template>
    </span>
  `,
  styles: [`
    span {
      display: inline-block;
      width: 25px;
      line-height: 25px;
      text-align: center;
      cursor: pointer;
    }
  `]
})
export class RatingInputComponent {
  stars: boolean[] = Array(5).fill(false);

  get value(): number {
    return this.stars.reduce((total, starred) => {
      return total + (starred ? 1 : 0);
    }, 0);
  }

  rate(rating: number) {
    this.stars = this.stars.map((_, i) => rating > i);
  }
}

Мы можем получить value компонента (от 0 до 5) и установить значение компонента, вызвав rate или щелкнув желаемое количество звездочек.

Вы можете добавить компонент в приложение:

<rating-input></rating-input>

И запустите приложение:

  1. ng serve

И взаимодействуйте с ним в веб-браузере.

Это здорово, но мы не можем просто добавить этот ввод в форму и ожидать, что все сразу заработает. Нам нужно сделать его ControlValueAccessor.

Шаг 2 — Создание пользовательского элемента управления формы

Чтобы заставить RatingInputComponent вести себя так, как если бы он был собственным вводом (и, следовательно, настоящим пользовательским элементом управления формы), нам нужно сообщить Angular, как сделать несколько вещей:

  • Запишите значение во входные данные — writeValue
  • Зарегистрируйте функцию, чтобы сообщать Angular, когда значение ввода изменяется — registerOnChange
  • Зарегистрируйте функцию, чтобы сообщать Angular о касании ввода — registerOnTouched
  • Отключить ввод — setDisabledState

Эти четыре элемента составляют интерфейс ControlValueAccessor, мост между элементом управления формы и собственным элементом или пользовательским компонентом ввода. Как только наш компонент реализует этот интерфейс, нам нужно сообщить об этом Angular, предоставив его как NG_VALUE_ACCESSOR, чтобы его можно было использовать.

Снова откройте rating-input.component.ts в редакторе кода и внесите следующие изменения:

import { Component, forwardRef, HostBinding, Input } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

@Component({
  selector: 'rating-input',
  template: `
    <span
      *ngFor="let starred of stars; let i = index"
      (click)="onTouched(); rate(i + (starred ? (value > i + 1 ? 1 : 0) : 1))"
    >
      <ng-container *ngIf="starred; else noStar">⭐</ng-container>
      <ng-template #noStar>·</ng-template>
    </span>
  `,
  styles: [`
    span {
      display: inline-block;
      width: 25px;
      line-height: 25px;
      text-align: center;
      cursor: pointer;
    }
  `],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => RatingInputComponent),
      multi: true
    }
  ]
})
export class RatingInputComponent implements ControlValueAccessor {
  stars: boolean[] = Array(5).fill(false);

  // Allow the input to be disabled, and when it is make it somewhat transparent.
  @Input() disabled = false;
  @HostBinding('style.opacity')
  get opacity() {
    return this.disabled ? 0.25 : 1;
  }

  // Function to call when the rating changes.
  onChange = (rating: number) => {};

  // Function to call when the input is touched (when a star is clicked).
  onTouched = () => {};

  get value(): number {
    return this.stars.reduce((total, starred) => {
      return total + (starred ? 1 : 0);
    }, 0);
  }

  rate(rating: number) {
    if (!this.disabled) {
      this.writeValue(rating);
    }
  }

  // Allows Angular to update the model (rating).
  // Update the model and changes needed for the view here.
  writeValue(rating: number): void {
    this.stars = this.stars.map((_, i) => rating > i);
    this.onChange(this.value);
  }

  // Allows Angular to register a function to call when the model (rating) changes.
  // Save the function as a property to call later here.
  registerOnChange(fn: (rating: number) => void): void {
    this.onChange = fn;
  }

  // Allows Angular to register a function to call when the input has been touched.
  // Save the function as a property to call later here.
  registerOnTouched(fn: () => void): void {
    this.onTouched = fn;
  }

  // Allows Angular to disable the input.
  setDisabledState(isDisabled: boolean): void {
    this.disabled = isDisabled;
  }
}

Этот код позволит отключить ввод, а когда он будет, сделать его несколько прозрачным.

Запустите приложение:

  1. ng serve

И взаимодействуйте с ним в веб-браузере.

Вы также можете отключить элементы управления вводом:

<rating-input [disabled]="true"></rating-input>

Теперь мы можем сказать, что наш RatingInputComponent является настраиваемым компонентом формы! Он будет работать так же, как и любой другой нативный ввод (Angular предоставляет для них ControlValueAccessors!) в управляемых шаблонами или реактивных формах.

Заключение

В этой статье вы преобразовали базовый компонент ввода рейтинга в ControlValueAccessor.

Вы заметите, что сейчас:

  • ngModel просто «работает».
  • Мы можем добавить пользовательскую проверку.
  • Состояние управления и достоверность становятся доступными с помощью классов ngModel, таких как ng-dirty и ng-touched.

Если вы хотите узнать больше об Angular, ознакомьтесь с нашей темой по Angular, где вы найдете упражнения и проекты по программированию.