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


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

Route. Обновление шаблона

Обновление шаблона при динамическом роутинге и отписка от наблюдения

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

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

Формы. Шаблонный подход.

Работа с формами. Шаблонный подход (Template-driven)

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

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

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

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

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

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

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

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

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