Создание custom form field control (ControlValueAccessor)

  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();
  }
}

stackblitz demo


Похожие записи

Как добавить ng-bootstrap компоненты в проект Angular-CLI?

Покажу на примере нового проекта.

ng new project_name
cd project_name
npm install --save bootstrap@next
npm install --save @ng-bootstrap/ng-bootstrap

В angular-cli.json в секцию style надо добавить наш CSS, чтобы глобально подключить стили.

  "styles": [
    "styles.css",
    "../node_modules/bootstrap/dist/css/bootstrap.min.css"
  ],
17 августа 2017 г. в Angular

let-* $implicit in Angular template

Синтаксис let-* позволяет объявить переменную в шаблоне <ng-template>, использования ключа $implicit позволяет устанавливать значение по-умолчанию для объявленной переменной.

29 августа 2018 г. в Angular