Kesalahan berarti, bahwa Angular tidak tahu apa yang harus dilakukan ketika Anda memakai formControl
a div
. Untuk memperbaikinya, Anda memiliki dua opsi.
- Anda meletakkan
formControlName
elemen, yang didukung oleh Angular di luar kotak. Mereka adalah: input
, textarea
dan select
.
- Anda mengimplementasikan
ControlValueAccessor
antarmuka. Dengan melakukannya, Anda memberi tahu Angular "cara mengakses nilai kendali Anda" (karena itu namanya). Atau secara sederhana: Apa yang harus dilakukan, ketika Anda meletakkan formControlName
elemen, itu tidak secara alami memiliki nilai yang terkait dengannya.
Sekarang, mengimplementasikan ControlValueAccessor
antarmuka bisa sedikit menakutkan pada awalnya. Terutama karena tidak ada dokumentasi yang bagus tentang ini di luar sana dan Anda perlu menambahkan banyak boilerplate ke kode Anda. Jadi izinkan saya mencoba untuk memecah ini dalam beberapa langkah mudah diikuti.
Pindahkan kontrol formulir Anda ke komponennya sendiri
Untuk menerapkannya ControlValueAccessor
, Anda perlu membuat komponen baru (atau arahan). Pindahkan kode yang terkait dengan kontrol formulir Anda di sana. Seperti ini juga akan mudah digunakan kembali. Memiliki kontrol yang sudah ada di dalam komponen mungkin menjadi alasan di tempat pertama, mengapa Anda perlu mengimplementasikan ControlValueAccessor
antarmuka, karena jika tidak, Anda tidak akan dapat menggunakan komponen kustom Anda bersama-sama dengan bentuk Angular.
Tambahkan boilerplate ke kode Anda
Menerapkan ControlValueAccessor
antarmuka cukup verbose, inilah boilerplate yang menyertainya:
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
}
Jadi apa yang dilakukan masing-masing bagian?
- a) Memungkinkan Angular tahu selama runtime bahwa Anda mengimplementasikan
ControlValueAccessor
antarmuka
- b) Pastikan Anda mengimplementasikan
ControlValueAccessor
antarmuka
- c) Ini mungkin bagian yang paling membingungkan. Pada dasarnya yang Anda lakukan adalah, Anda memberi Angular sarana untuk mengganti properti / metode kelas Anda
onChange
dan onTouch
dengan implementasinya sendiri selama runtime, sehingga Anda dapat memanggil fungsi-fungsi tersebut. Jadi poin ini penting untuk dipahami: Anda tidak perlu mengimplementasikan onChange dan onTouch sendiri (selain dari implementasi kosong awal). Satu-satunya hal yang Anda lakukan dengan (c) adalah membiarkan Angular melampirkan fungsinya sendiri ke kelas Anda. Mengapa? Sehingga Anda kemudian dapat memanggil yang onChange
dan onTouch
metode yang disediakan oleh angular pada waktu yang tepat. Kita akan melihat bagaimana ini bekerja di bawah.
- d) Kita juga akan melihat bagaimana
writeValue
metode ini bekerja di bagian selanjutnya, ketika kita mengimplementasikannya. Saya telah meletakkannya di sini, jadi semua properti yang diperlukan ControlValueAccessor
diimplementasikan dan kode Anda masih dikompilasi.
Terapkan writeValue
Apa yang writeValue
dilakukan, adalah melakukan sesuatu di dalam komponen kustom Anda, ketika kontrol formulir diubah di luar . Jadi misalnya, jika Anda telah menamai komponen kontrol formulir kustom app-custom-input
Anda dan Anda akan menggunakannya dalam komponen induk seperti ini:
<form [formGroup]="form">
<app-custom-input formControlName="myFormControl"></app-custom-input>
</form>
kemudian writeValue
akan terpicu setiap kali komponen induk mengubah nilai myFormControl
. Ini bisa misalnya selama inisialisasi form ( this.form = this.formBuilder.group({myFormControl: ""});
) atau pada form reset this.form.reset();
.
Apa yang biasanya ingin Anda lakukan jika nilai kontrol formulir berubah di luar, adalah menulisnya ke variabel lokal yang mewakili nilai kontrol formulir. Misalnya, jika Anda CustomInputComponent
berputar di sekitar kontrol bentuk berbasis teks, itu bisa terlihat seperti ini:
writeValue(input: string) {
this.input = input;
}
dan dalam html dari CustomInputComponent
:
<input type="text"
[ngModel]="input">
Anda juga bisa menulisnya langsung ke elemen input seperti yang dijelaskan dalam dokumen Angular.
Sekarang Anda telah menangani apa yang terjadi di dalam komponen Anda ketika sesuatu berubah di luar. Sekarang mari kita lihat ke arah yang lain. Bagaimana Anda memberi tahu dunia luar ketika ada sesuatu yang berubah di dalam komponen Anda?
Memanggil di Ubah
Langkah selanjutnya adalah memberi tahu komponen induk tentang perubahan di dalam Anda CustomInputComponent
. Di sinilah onChange
dan onTouch
fungsi dari (c) dari atas ikut bermain. Dengan memanggil fungsi-fungsi itu, Anda dapat memberi tahu pihak luar tentang perubahan di dalam komponen Anda. Untuk menyebarkan perubahan nilai ke luar, Anda perlu memanggil onChange dengan nilai baru sebagai argumen . Misalnya, jika pengguna mengetik sesuatu di input
bidang di komponen khusus Anda, Anda menelepon onChange
dengan nilai yang diperbarui:
<input type="text"
[ngModel]="input"
(ngModelChange)="onChange($event)">
Jika Anda memeriksa implementasi (c) dari atas lagi, Anda akan melihat apa yang terjadi: Angular terikat penerapannya sendiri ke onChange
properti kelas. Implementasi itu mengharapkan satu argumen, yang merupakan nilai kontrol yang diperbarui. Apa yang Anda lakukan sekarang adalah Anda memanggil metode itu dan dengan demikian membiarkan Angular tahu tentang perubahan itu. Angular sekarang akan maju dan mengubah nilai formulir di luar. Ini adalah bagian penting dari semua ini. Anda memberi tahu Angular kapan harus memperbarui kontrol formulir dan dengan nilai apa dengan menelepononChange
. Anda telah memberinya sarana untuk "mengakses nilai kontrol".
Ngomong-ngomong: Nama onChange
itu dipilih oleh saya. Anda dapat memilih apa pun di sini, misalnya propagateChange
atau serupa. Namun apa pun nama Anda, itu akan menjadi fungsi yang sama yang mengambil satu argumen, yang disediakan oleh Angular dan yang terikat ke kelas Anda dengan registerOnChange
metode selama runtime.
Memanggil onTouch
Karena kontrol formulir dapat "disentuh", Anda juga harus memberikan sudut kepada Angular cara untuk memahami ketika kontrol bentuk kustom Anda disentuh. Anda dapat melakukannya, Anda dapat menebaknya, dengan memanggil onTouch
fungsi. Jadi untuk contoh kami di sini, jika Anda ingin tetap mematuhi bagaimana Angular melakukannya untuk kontrol bentuk out-of-the-box, Anda harus memanggil onTouch
ketika bidang input kabur:
<input type="text"
[(ngModel)]="input"
(ngModelChange)="onChange($event)"
(blur)="onTouch()">
Sekali lagi, onTouch
adalah nama yang dipilih oleh saya, tetapi apa fungsi sebenarnya disediakan oleh Angular dan tidak membutuhkan argumen. Yang masuk akal, karena Anda hanya memberi tahu Angular, bahwa kontrol bentuk telah disentuh.
Menyatukan semuanya
Jadi, bagaimana itu terlihat ketika datang bersama-sama? Seharusnya terlihat seperti ini:
// 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>
Lebih banyak contoh
Formulir Bersarang
Perhatikan bahwa Access Control Value BUKAN alat yang tepat untuk grup formulir bersarang. Untuk grup formulir bersarang Anda bisa menggunakan @Input() subform
saja. Access Control Value dimaksudkan untuk membungkus controls
, bukan groups
! Lihat contoh ini cara menggunakan input untuk formulir bersarang: https://stackblitz.com/edit/angular-nested-forms-input-2
Sumber