Ошибка означает, что 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
Источники