меню
  1. ControlValueAccessor - связующее звено между Angular forms API и нативным DOM элементом
  2. Чтобы Angular знал об новом field control необходимо зарегистрировать NG_VALUE_ACCESSOR провайдер.

Интерфейс ControlValueAccessor

В компоненте должны реализовываться 3 обязательных метода и 1 один необязательный:

  
export interface ControlValueAccessor {
    /**
     * Записать значение в компонент (из ts в html), основная функция
     */
    writeValue(obj: any): void;
    /**
     * Обработать значение из компонента (из html в ts), основная функция fn
     */
    registerOnChange(fn: any): void;
    /**
     * Обработать, когда потрогали поле (потеря фокуса)
     */
    registerOnTouched(fn: any): void;
    /**
     * Обработка недоступности
     */
    setDisabledState?(isDisabled: boolean): void;
}
 

Регистрация NG_VALUE_ACCESSOR провайдера

Используется мультипровайдер с токеном NG_VALUE_ACCESSOR.

 
const NG_VALUE_ACCESSOR: InjectionToken<ControlValueAccessor>;
 

Функция forwardRef необходима потому, что в провайдере идёт обращение к классу, который еще не определен (объявлен ниже). Иначе ошибка (ERROR in : Cannot instantiate cyclic dependency! NgControl)

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

@Component({
  ...
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => TyapkComponent),
    multi: true
  }]
}) export class TyapkComponent implements ControlValueAccessor {}
  

Custom control

Миниальный

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

@Component({
  selector: 'app-quantity-input',
  template: `
    <button 
      (click)="updateValue(+value - 10)" 
      [disabled]="disabled">--</button>
    <button 
      (click)="updateValue(+value - 1)" 
      [disabled]="disabled">-</button>
    <input
      [ngModel]="value"
      (ngModelChange)="updateValue($event)"
      [disabled]="disabled"
      type="number"
    />
    <button 
      (click)="updateValue(+value + 1)" 
      [disabled]="disabled">+</button>
   <button 
      (click)="updateValue(+value + 10)" 
      [disabled]="disabled">++</button>
  `,
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => QuantityInputComponent),
    multi: true,
  }],
})
export class QuantityInputComponent implements ControlValueAccessor {
  value = 0;
  disabled = false;
  private onChange = (value: any) => {};
  private onTouched = () => {};

  registerOnChange(fn: any) {
    this.onChange = fn;
  }

  registerOnTouched(fn: () => {}): void {
    this.onTouched = fn;
  }

  writeValue(outsideValue: number) {
    // получить из Forms API
    this.value = outsideValue;
  }

  setDisabledState(isDisabled: boolean) {
    this.disabled = isDisabled;
  }

  updateValue(insideValue: number) {
    this.value = insideValue; // html
    this.onChange(insideValue); // уведомить Forms API
    this.onTouched();
  }
}
  

Супер простой компонент переключателя

  
@Component({
  selector: 'toggle',
  template: `{{ value }}`,
  styles: [`
    :host {
      display: block;
      height: 20px;
      width: 285px;
      cursor: pointer;
      border: 1px solid #ccc;
      border-radius: 4px;
      padding: 5px;
      text-align: center;
    }

    :host(.disabled) {
      opacity: 0.35;
      pointer-events: none;
    }  
  `],
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => ToggleComponent),
    multi: true
  }]
})
export class ToggleComponent implements ControlValueAccessor {
  value: boolean;
  disabled: boolean;
  onChange = (value: boolean) => {};
  onTouched = () => {};

  registerOnChange(fn) {
    this.onChange = fn;
  }

  registerOnTouched(fn) {
    this.onTouched = fn;
  }

  @HostBinding('class.disabled')
  get isDisabled() {
    return this.disabled;
  }

  @HostListener('click') 
  click() {
    this.writeValue(!this.value);
  }

  writeValue(value: boolean) {
    this.value = value;
    this.onChange(this.value);
  }

  setDisabledState(disabled: boolean) {
    this.disabled = disabled;
  }
}
 

Материал был взят с сайта: tyapk.ru


Возможно, вам будет интересно

10 лучших UI библиотек для Angular

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

Появление нового функционала в Angular 16 и Angular 17

Angular предоставляет новые возможности для замены директив ngFor и ngIf. Все, что вам нужно сделать, это заиспользовать @for и @if, как вы используете директивы ngFor и ngIf, но с немного другим синтаксисом.

Динамическое добавление элемента script в Angular

Не часто при разработке SPA приложения появляется задача с динамическим добавлением JS файла на страницу. В данной статье рассмотрим несколько возможных способов динамического добавления script в Angular.

Динамические шаблоны в Angular

В данном руководстве рассмотрим двухстороннее связывание данных. Создание динамических шаблонов, меняющих свое состояние в зависимости от действий пользователя, с помощью двухстороннего связывания данных и директивы ngIf.

Оформление заявки

Документы на создание сайта

Изучите наше коммерческое предложение, заполните БРИФ и отправьте его на почту maxidebox@list.ru. Изучив все пожелания из БРИФ-а, обратным ответом оповестим Вас по стоимости разработке, ответим на вопросы.

КП на создание сайта Коммерческое предложение на созданеи сайта

Мы берем на себя ответственность за все стадии работы и полностью избавляем клиентов от забот и необходимости вникать в тонкости.

Скачать БРИФ (акета) на создание сайта Скачать БРИФ (акета) на создание сайта

Зополните у БРИФ-а все необходимые поля. Сделайте краткое описание к каждому из пунктов анкеты, привидите примеры в соответсвующий пунктах - это позволит лучше понять Ваши ожидания и требования к сайту