Помилка означає, що Angular не знає, що робити, коли ви ставите formControl
на div
. Щоб виправити це, у вас є два варіанти.
- Ви поміщаєте
formControlName
елемент, який підтримується кутовим з поля. Це: input
, textarea
і select
.
- Ви реалізувати
ControlValueAccessor
інтерфейс. Роблячи це, ви говорите Angular "як отримати доступ до значення свого контролю" (звідси і назва). Або простіше кажучи: що робити, коли ви кладете formControlName
на елемент, який, природно, не має з ним пов'язаного значення.
Тепер реалізація ControlValueAccessor
інтерфейсу може бути трохи непростим спочатку. Тим більше, що там не так вже й багато хорошої документації, і вам потрібно додати до коду багато котлована. Тож дозвольте мені спробувати розбити це в декількох простих для виконання кроках.
Перемістіть контроль форми у свій власний компонент
Для того, щоб реалізувати ControlValueAccessor
, вам потрібно створити новий компонент (або директиву). Перемістіть туди код, пов’язаний із вашим контролем форми. Як це, це також буде легко використати повторно. Маючи керування вже всередині компонента, це може бути причиною, в першу чергу, чому вам потрібно реалізувати ControlValueAccessor
інтерфейс, оскільки в іншому випадку ви не зможете використовувати свій спеціальний компонент разом із кутовими формами.
Додайте до коду плиту
Реалізація 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
}
То що роблять окремі частини?
- a) Повідомляє Angular під час виконання, що ви реалізували
ControlValueAccessor
інтерфейс
- b) Переконайтеся, що ви реалізуєте
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">
Ви також можете записати його безпосередньо на вхідний елемент, як описано в кутових документах.
Тепер ви вирішили, що відбувається всередині вашого компонента, коли зовні щось змінюється. Тепер давайте подивимось в інший бік. Як ви інформуєте зовнішній світ, коли щось змінюється всередині вашого компонента?
Виклик onChange
Наступним кроком є інформування батьківського компонента про зміни всередині вашого CustomInputComponent
. Тут onChange
і onTouch
грають функції і функції (с) зверху. Зателефонувавши до цих функцій, ви можете повідомити зовні про зміни всередині вашого компонента. Для того, щоб поширити зміни значення назовні, вам потрібно зателефонувати onChange з новим значенням в якості аргументу . Наприклад, якщо користувач вводить щось у input
полі у вашому користувальницькому компоненті, ви дзвоните onChange
з оновленим значенням:
<input type="text"
[ngModel]="input"
(ngModelChange)="onChange($event)">
Якщо ви ще раз перевірте реалізацію (c), ви побачите, що відбувається: Angular прывязаний власною реалізацією до onChange
властивості класу. Ця реалізація очікує одного аргументу, який є оновленим контрольним значенням. Що ви зараз робите, це ви називаєте цей метод і тим самим повідомляєте 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>
Більше прикладів
Вкладені форми
Зауважте, що контрольні значення Accessors НЕ є правильним інструментом для вкладених груп форм. Для вкладених груп форм ви можете просто використовувати @Input() subform
замість цього. Принципи контрольного значення призначені для завершення controls
, а не groups
! Дивіться цей приклад, як використовувати вхід для вкладеної форми: https://stackblitz.com/edit/angular-nested-forms-input-2
Джерела