Ошибка означает, что Angular не знает, что делать, когда вы добавляете formControlфайл div. Чтобы исправить это, у вас есть два варианта.
- Вы устанавливаете
formControlNameэлемент, который поддерживается Angular из коробки. Это: input, textareaи select.
- Вы реализуете
ControlValueAccessorинтерфейс. Поступая так, вы сообщаете Angular, «как получить доступ к значению вашего элемента управления» (отсюда и название). Или, проще говоря: что делать, когда вы помещаете formControlNameэлемент, который, естественно, не имеет значения, связанного с ним.
Теперь реализация ControlValueAccessorинтерфейса поначалу может показаться немного сложной. Тем более, что там не так много хорошей документации, и вам нужно добавить много шаблонов в свой код. Итак, позвольте мне попытаться разбить это на несколько простых шагов.
Переместите элемент управления формы в отдельный компонент
Чтобы реализовать ControlValueAccessor, вам необходимо создать новый компонент (или директиву). Переместите туда код, связанный с вашим элементом управления формой. Таким образом, его можно будет легко использовать повторно. Наличие элемента управления уже внутри компонента может быть причиной, в первую очередь, почему вам нужно реализовать ControlValueAccessorинтерфейс, потому что в противном случае вы не сможете использовать свой пользовательский компонент вместе с формами Angular.
Добавьте шаблон в свой код
Реализация ControlValueAccessor интерфейса довольно подробна, вот шаблон, который прилагается:
import {Component, OnInit, forwardRef} from '@angular/core';
import {ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR} from '@angular/forms';
@Component({
selector: 'app-custom-input',
templateUrl: './custom-input.component.html',
styleUrls: ['./custom-input.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CustomInputComponent),
multi: true
}
]
})
export class CustomInputComponent implements ControlValueAccessor {
onChange: any = () => {}
onTouch: any = () => {}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouch = fn;
}
writeValue(input: string) {
}
Так что же делают отдельные части?
- а) Сообщает Angular во время выполнения, что вы реализовали
ControlValueAccessor интерфейс
- б) Убедитесь, что вы реализуете
ControlValueAccessor интерфейс
- в) Это, наверное, самая запутанная часть. По сути, вы даете Angular возможность переопределить свойства / методы вашего класса
onChangeи onTouchсобственную реализацию во время выполнения, чтобы затем вы могли вызывать эти функции. Поэтому важно понять этот момент: вам не нужно самостоятельно реализовывать onChange и onTouch (кроме исходной пустой реализации). Единственное, что вы делаете с (c), - это позволяете Angular прикреплять свои собственные функции к вашему классу. Зачем? Тогда вы можете позвонить вonChange и onTouchметодам , предоставляемых угловым в соответствующее время. Ниже мы увидим, как это работает.
- г) Мы также увидим, как этот
writeValueметод работает, в следующем разделе, когда мы его реализуем. Я поместил его здесь, поэтому все необходимые свойства ControlValueAccessorреализованы, а ваш код все еще компилируется.
Реализовать writeValue
Что writeValueделает, так это делать что-то внутри вашего пользовательского компонента, когда элемент управления формы изменяется снаружи . Так, например, если вы назвали свой компонент управления настраиваемой формой app-custom-inputи будете использовать его в родительском компоненте следующим образом:
<form [formGroup]="form">
<app-custom-input formControlName="myFormControl"></app-custom-input>
</form>
затем writeValueсрабатывает всякий раз, когда родительский компонент каким-либо образом изменяет значение myFormControl. Это может быть, например, во время инициализации формы ( this.form = this.formBuilder.group({myFormControl: ""});) или при сбросе формы this.form.reset();.
Что вы обычно хотите сделать, если значение элемента управления формы изменяется снаружи, так это записать его в локальную переменную, которая представляет значение элемента управления формы. Например, если вы CustomInputComponentвращаетесь вокруг элемента управления текстовой формой, это может выглядеть так:
writeValue(input: string) {
this.input = input;
}
и в html CustomInputComponent:
<input type="text"
[ngModel]="input">
Вы также можете записать его непосредственно в элемент ввода, как описано в документации по Angular.
Теперь вы справились с тем, что происходит внутри вашего компонента, когда что-то меняется снаружи. Теперь посмотрим в другом направлении. Как вы информируете внешний мир, когда что-то меняется внутри вашего компонента?
Вызов onChange
Следующим шагом будет информирование родительского компонента об изменениях внутри вашего CustomInputComponent. Это где onChangeи onTouchфункция из (с) сверху вступает в игру. Вызывая эти функции, вы можете информировать внешний мир об изменениях внутри вашего компонента. Чтобы распространить изменения значения наружу, вам необходимо вызвать onChange с новым значением в качестве аргумента . Например, если пользователь вводит что-то в inputполе вашего настраиваемого компонента, вы вызываете onChangeс обновленным значением:
<input type="text"
[ngModel]="input"
(ngModelChange)="onChange($event)">
Если вы снова проверите реализацию (c) сверху, вы увидите, что происходит: Angular привязывает свою реализацию к onChangeсвойству класса. Эта реализация ожидает один аргумент - обновленное значение элемента управления. Сейчас вы вызываете этот метод и тем самым сообщаете Angular об изменении. Angular теперь продолжит работу и изменит значение формы снаружи. Это ключевая часть всего этого. Вы сказали Angular, когда он должен обновить элемент управления формы и с каким значением, вызвав егоonChange . Вы дали ему возможность «получить доступ к контрольному значению».
Кстати: Название onChangeвыбрано мной. Вы можете выбрать здесь что угодно, например propagateChangeили подобное. Как бы вы его ни называли, это будет та же функция, которая принимает один аргумент, который предоставляется Angular и который привязан к вашему классу с помощьюregisterOnChange методом во время выполнения.
Вызов onTouch
Так как элементы управления формы можно «коснуться», вы также должны дать Angular возможность понимать, когда происходит касание настраиваемого элемента управления формы. Вы догадались, что это можно сделать, вызвав onTouchфункцию. Итак, для нашего примера здесь, если вы хотите соответствовать тому, как Angular делает это для готовых элементов управления формой, вы должны вызвать, onTouchкогда поле ввода размыто:
<input type="text"
[(ngModel)]="input"
(ngModelChange)="onChange($event)"
(blur)="onTouch()">
Еще раз, onTouch это имя выбрано мной, но реальная функция предоставляется Angular и не требует аргументов. Это имеет смысл, поскольку вы просто даете Angular знать, что элемент управления формы был затронут.
Собираем все вместе
Итак, как это выглядит, когда все вместе? Должно получиться так:
import {Component, OnInit, forwardRef} from '@angular/core';
import {ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR} from '@angular/forms';
@Component({
selector: 'app-custom-input',
templateUrl: './custom-input.component.html',
styleUrls: ['./custom-input.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CustomInputComponent),
multi: true
}
]
})
export class CustomInputComponent implements ControlValueAccessor {
onChange: any = () => {}
onTouch: any = () => {}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouch = fn;
}
input: string;
writeValue(input: string) {
this.input = input;
}
}
<input type="text"
[(ngModel)]="input"
(ngModelChange)="onChange($event)"
(blur)="onTouch()">
<app-custom-input [formControl]="inputTwo"></app-custom-input>
<form [formGroup]="form" >
<app-custom-input formControlName="myFormControl"></app-custom-input>
</form>
Больше примеров
Вложенные формы
Обратите внимание, что аксессоры управляющих значений НЕ подходят для вложенных групп форм. Для вложенных групп форм вы можете просто использовать @Input() subformвместо. Аксессоры контрольного значения предназначены для обертывания controls, а не groups! Посмотрите этот пример, как использовать ввод для вложенной формы: https://stackblitz.com/edit/angular-nested-forms-input-2
Источники