меню
  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


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

Observable. Subscribe и Unsubscribe

Stream и работа с Observable в Angular. Пример отписки в ngOnDestroy()

Шаблон и стили компонента

Стилилизация компонента в Angular, способы определения стилей, подключени внешнего файла стилей, вынесение кода шаблона во внешний файл html

Инкапсуляция стилей и ссылки

Режимы инкапсуляции стилей, локальная ссылка в шаблоне Angular. Доступ к шаблону и DOM из компонента с помощью @ViewChild и @ContentChild.

Route Resolving

Разрешение данных это процесс их получения перед активацией роута. В приложении возможна задержка перед тем, как данные будут предоставлены сервером.

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

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

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

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

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

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

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