У меня есть собственный элемент:
<div formControlName="surveyType">
<div *ngFor="let type of surveyTypes"
(click)="onSelectType(type)"
[class.selected]="type === selectedType">
<md-icon>{{ type.icon }}</md-icon>
<span>{{ type.description }}</span>
</div>
</div>
Когда я пытаюсь добавить formControlName, я получаю сообщение об ошибке:
ОШИБКА Ошибка: нет средства доступа к значению для элемента управления формы с именем: 'SurveyType'
Пробовал добавить ngDefaultControl
безуспешно. Кажется, это потому, что нет ввода / выбора ... и я не знаю, что делать.
Я хотел бы привязать свой щелчок к этому formControl, чтобы, когда кто-то щелкает по всей карточке, мой «тип» вставлялся в formControl. Является ли это возможным?
Ответы:
Вы можете использовать
formControlName
только те директивы, которые реализуютControlValueAccessor
.Реализуйте интерфейс
Итак, чтобы делать то, что вы хотите, вам нужно создать компонент, который реализует
ControlValueAccessor
, что означает реализацию следующих трех функций :writeValue
(сообщает Angular, как записать значение из модели в представление)registerOnChange
(регистрирует функцию-обработчик, которая вызывается при изменении представления)registerOnTouched
(регистрирует обработчик, который будет вызываться, когда компонент получает событие касания, полезно для определения, был ли компонент сфокусирован).Зарегистрируйте провайдера
Затем вы должны сообщить Angular, что эта директива является
ControlValueAccessor
(интерфейс не будет вырезать ее, поскольку она удаляется из кода, когда TypeScript компилируется в JavaScript). Вы делаете это, регистрируя провайдера .Провайдер должен предоставить
NG_VALUE_ACCESSOR
и использовать существующее значение . Вам также понадобитсяforwardRef
здесь. Обратите внимание, чтоNG_VALUE_ACCESSOR
должен быть мульти-провайдер .Например, если ваша настраиваемая директива называется MyControlComponent, вы должны добавить что-то в следующих строках внутри объекта, переданного в
@Component
декоратор:providers: [ { provide: NG_VALUE_ACCESSOR, multi: true, useExisting: forwardRef(() => MyControlComponent), } ]
Применение
Ваш компонент готов к использованию. С формами на основе шаблонов ,
ngModel
связывание теперь будет работать должным образом.С реактивными формами теперь вы можете правильно использовать
formControlName
и элемент управления формы будет вести себя .Ресурсы
источник
Я думаю, вам следует использовать
formControlName="surveyType"
на,input
а не наdiv
источник
<ng-content>
компонент-оболочку, и позволить родительскому компоненту, который определяет,formControls
просто поместить <input> внутри <wrapper>Ошибка означает, что 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'], // a) copy paste this providers property (adjust the component name in the forward ref) providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CustomInputComponent), multi: true } ] }) // b) Add "implements ControlValueAccessor" export class CustomInputComponent implements ControlValueAccessor { // c) copy paste this code onChange: any = () => {} onTouch: any = () => {} registerOnChange(fn: any): void { this.onChange = fn; } registerOnTouched(fn: any): void { this.onTouch = fn; } // d) copy paste this code writeValue(input: string) { // TODO }
Так что же делают отдельные части?
ControlValueAccessor
интерфейсControlValueAccessor
интерфейс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 знать, что элемент управления формы был затронут.Собираем все вместе
Итак, как это выглядит, когда все вместе? Должно получиться так:
// custom-input.component.ts 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'], // Step 1: copy paste this providers property providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CustomInputComponent), multi: true } ] }) // Step 2: Add "implements ControlValueAccessor" export class CustomInputComponent implements ControlValueAccessor { // Step 3: Copy paste this stuff here onChange: any = () => {} onTouch: any = () => {} registerOnChange(fn: any): void { this.onChange = fn; } registerOnTouched(fn: any): void { this.onTouch = fn; } // Step 4: Define what should happen in this component, if something changes outside input: string; writeValue(input: string) { this.input = input; } // Step 5: Handle what should happen on the outside, if something changes on the inside // in this simple case, we've handled all of that in the .html // a) we've bound to the local variable with ngModel // b) we emit to the ouside by calling onChange on ngModelChange }
// custom-input.component.html <input type="text" [(ngModel)]="input" (ngModelChange)="onChange($event)" (blur)="onTouch()">
// parent.component.html <app-custom-input [formControl]="inputTwo"></app-custom-input> // OR <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Источники
источник
Для меня это произошло из-за атрибута "множественный" в элементе управления выбранным вводом, поскольку Angular имеет другой ValueAccessor для этого типа элемента управления.
const countryControl = new FormControl();
И внутри шаблона используйте это
<select multiple name="countries" [formControl]="countryControl"> <option *ngFor="let country of countries" [ngValue]="country"> {{ country.name }} </option> </select>
Подробнее см. Официальные документы
источник