Bekerja dengan Angular 4 Forms: Nesting dan Input Validation
Diterbitkan: 2022-03-11Di web, beberapa elemen input pengguna yang paling awal adalah tombol, kotak centang, input teks, dan tombol radio. Sampai hari ini, elemen-elemen ini masih digunakan dalam aplikasi web modern meskipun standar HTML telah berkembang jauh dari definisi awalnya dan sekarang memungkinkan segala macam interaksi mewah.
Memvalidasi input pengguna adalah bagian penting dari setiap aplikasi web yang kuat.
Formulir dalam aplikasi Angular dapat menggabungkan status semua input yang berada di bawah formulir itu dan memberikan status keseluruhan seperti status validasi formulir lengkap. Ini bisa sangat berguna untuk memutuskan apakah input pengguna akan diterima atau ditolak tanpa memeriksa setiap input secara terpisah.
Dalam artikel ini, Anda akan mempelajari bagaimana Anda bisa bekerja dengan formulir dan melakukan validasi formulir dengan mudah di aplikasi Angular Anda.
Di Angular 4, ada dua jenis formulir berbeda yang tersedia untuk digunakan: formulir berbasis template dan reaktif. Kami akan membahas setiap jenis formulir dengan menggunakan contoh yang sama untuk melihat bagaimana hal yang sama dapat diimplementasikan dengan cara yang berbeda. Nanti, dalam artikel, kita akan melihat pendekatan baru tentang cara mengatur dan bekerja dengan formulir bersarang.
Sudut 4 Bentuk
Di Angular 4, empat status berikut biasanya digunakan oleh formulir:
valid – status validitas semua kontrol formulir, benar jika semua kontrol valid
tidak valid – kebalikan dari
valid
; benar jika beberapa kontrol tidak validmurni – memberikan status tentang "kebersihan" formulir; benar jika tidak ada kontrol yang dimodifikasi
kotor – kebalikan dari
pristine
; benar jika beberapa kontrol dimodifikasi
Mari kita lihat contoh dasar formulir:
<form> <div> <label>Name</label> <input type="text" name="name"/> </div> <div> <label>Birth Year</label> <input type="text" name="birthYear"/> </div> <div> <h3>Location</h3> <div> <label>Country</label> <input type="text" name="country"/> </div> <div> <label>City</label> <input type="text" name="city"/> </div> </div> <div> <h3>Phone numbers</h3> <div> <label>Phone number 1</label> <input type="text" name="phoneNumber[1]"/> <button type="button">remove</button> </div> <button type="button">Add phone number</button> </div> <button type="submit">Register</button> <button type="button">Print to console</button> </form>
Spesifikasi untuk contoh ini adalah sebagai berikut:
nama - wajib dan unik di antara semua pengguna terdaftar
tahun lahir - harus berupa angka yang valid dan pengguna harus berusia minimal 18 tahun dan kurang dari 85 tahun
negara - adalah wajib, dan hanya untuk membuat segalanya menjadi sedikit rumit, kami memerlukan validasi bahwa jika negaranya adalah Prancis, maka kotanya harus Paris (katakanlah bahwa layanan kami hanya ditawarkan di Paris)
phoneNumber – setiap nomor telepon harus mengikuti pola tertentu, harus ada setidaknya satu nomor telepon, dan pengguna diperbolehkan untuk menambah atau menghapus nomor telepon yang ada.
Tombol "Daftar" diaktifkan hanya jika semua input valid dan, setelah diklik, ia mengirimkan formulir.
"Cetak ke Konsol" hanya mencetak nilai semua input ke konsol saat diklik.
Tujuan utamanya adalah untuk sepenuhnya mengimplementasikan spesifikasi yang ditentukan.
Formulir berbasis template
Formulir yang digerakkan oleh template sangat mirip dengan formulir di AngularJS (atau Angular 1, seperti yang dirujuk beberapa orang). Jadi, seseorang yang telah bekerja dengan formulir di AngularJS akan sangat akrab dengan pendekatan ini untuk bekerja dengan formulir.
Dengan diperkenalkannya modul di Angular 4, diberlakukan bahwa setiap jenis formulir tertentu berada dalam modul terpisah dan kita harus secara eksplisit menentukan jenis mana yang akan kita gunakan dengan mengimpor modul yang tepat. Modul untuk formulir berbasis template itu adalah FormsModule. Karena itu, Anda dapat mengaktifkan formulir berbasis template sebagai berikut:
import {FormsModule} from '@angular/forms' import {NgModule} from '@angular/core' import {BrowserModule} from '@angular/platform-browser' import {AppComponent} from 'src/app.component'; @NgModule({ imports: [ BrowserModule, FormsModule ], declarations: [ AppComponent], bootstrap: [ AppComponent ] }) export class AppModule {}
Seperti yang disajikan dalam cuplikan kode ini, pertama-tama kita harus mengimpor modul browser karena modul tersebut “menyediakan layanan yang penting untuk meluncurkan dan menjalankan aplikasi browser”. (dari dokumen Angular 4). Kemudian kami mengimpor FormsModule yang diperlukan untuk mengaktifkan formulir berbasis template. Dan terakhir adalah deklarasi komponen root, AppComponent, dimana pada langkah selanjutnya kita akan mengimplementasikan form.
Ingatlah bahwa dalam contoh ini dan contoh berikut, Anda harus memastikan bahwa aplikasi di-bootstrap dengan benar menggunakan metode platformBrowserDynamic
.
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; import {AppModule} from './app.module'; platformBrowserDynamic().bootstrapModule(AppModule);
Kita dapat berasumsi bahwa AppComponent (app.component.ts) kita terlihat seperti ini:
import {Component} from '@angular/core' @Component({ selector: 'my-app', templateUrl: 'src/app.component.tpl.html' }) export class AppComponent { }
Dimana template komponen ini ada di app.component.tpl.html dan kita bisa mengcopy template awal ke file ini.
Perhatikan bahwa setiap elemen input harus memiliki atribut name
untuk diidentifikasi dengan benar di dalam formulir. Meskipun ini tampak seperti formulir HTML sederhana, kami telah mendefinisikan formulir yang didukung Angular 4 (mungkin Anda belum melihatnya). Saat FormsModule diimpor, Angular 4 secara otomatis mendeteksi elemen HTML form
dan melampirkan komponen NgForm ke elemen tersebut (oleh selector
komponen NgForm). Itulah yang terjadi dalam contoh kita. Meskipun bentuk Angular 4 ini dideklarasikan, pada titik ini tidak ada input yang didukung Angular 4 apa pun. Angular 4 tidak terlalu invasif untuk mendaftarkan setiap elemen HTML input
ke leluhur form
terdekat.
Kunci yang memungkinkan elemen input diperhatikan sebagai elemen Angular 4 dan didaftarkan ke komponen NgForm adalah direktif NgModel. Jadi, kita dapat memperluas template app.component.tpl.html sebagai berikut:
<form> .. <input type="text" name="name" ngModel> .. <input type="text" name="birthYear" ngModel > .. <input type="text" name="country" ngModel/> .. <input type="text" name="city" ngModel/> .. <input type="text" name="phoneNumber[1]" ngModel/> </form>
Dengan menambahkan direktif NgModel, semua input terdaftar ke komponen NgForm. Dengan ini, kami telah mendefinisikan formulir Angular 4 yang berfungsi penuh dan sejauh ini, sangat bagus, tetapi kami masih belum memiliki cara untuk mengakses komponen NgForm dan fungsionalitas yang ditawarkannya. Dua fungsi utama yang ditawarkan oleh NgForm adalah:
Mengambil nilai dari semua kontrol input yang terdaftar
Mengambil status keseluruhan dari semua kontrol
Untuk mengekspos NgForm, kita dapat menambahkan berikut ini ke elemen <form>:
<form #myForm="ngForm"> .. </form>
Ini dimungkinkan berkat properti exportAs
dari dekorator Component
.
Setelah ini selesai, kita dapat mengakses nilai dari semua kontrol input dan memperluas template ke:
<form #myForm="ngForm"> .. <pre>{{myForm.value | json}}</pre> </form>
Dengan myForm.value
kita mengakses data JSON yang berisi nilai dari semua input yang terdaftar, dan dengan {{myForm.value | json}}
{{myForm.value | json}}
, kami cukup mencetak JSON dengan nilai.
Bagaimana jika kita ingin memiliki sub-grup input dari konteks tertentu yang dibungkus dalam wadah dan objek terpisah dalam nilai JSON misalnya, lokasi yang berisi negara dan kota atau nomor telepon? Jangan stres—formulir yang digerakkan oleh template di Angular 4 juga mencakup hal itu. Cara untuk mencapai ini adalah dengan menggunakan direktif ngModelGroup
.
<form #myForm="ngForm"> .. <div ngModelGroup="location"> .. </div> </div ngModelGroup="phoneNumbers"> .. <div> .. </form>
Apa yang kita kurang sekarang adalah cara untuk menambahkan beberapa nomor telepon. Cara terbaik untuk melakukannya adalah dengan menggunakan larik, sebagai representasi terbaik dari wadah yang dapat diubah dari beberapa objek, tetapi pada saat penulisan artikel ini, fitur tersebut tidak diterapkan untuk formulir yang digerakkan oleh templat. Jadi, kita harus menerapkan solusi untuk membuat ini berhasil. Bagian nomor telepon perlu diperbarui sebagai berikut:
<div ngModelGroup="phoneNumbers"> <h3>Phone numbers</h3> <div *ngFor="let phoneId of phoneNumberIds; let i=index;"> <label>Phone number {{i + 1}}</label> <input type="text" name="phoneNumber[{{phoneId}}]" #phoneNumber="ngModel" ngModel/> <button type="button" (click)="remove(i); myForm.control.markAsTouched()">remove</button> </div> <button type="button" (click)="add(); myForm.control.markAsTouched()">Add phone number</button> </div>
myForm.control.markAsTouched()
digunakan untuk membuat formulir touched
sehingga kami dapat menampilkan kesalahan pada saat itu. Tombol tidak mengaktifkan properti ini saat diklik, hanya inputnya. Untuk membuat contoh berikutnya lebih jelas, saya tidak akan menambahkan baris ini pada handler klik untuk add()
dan remove()
. Bayangkan saja itu ada. (Ini ada di Plunkers.)
Kami juga perlu memperbarui AppComponent
untuk memuat kode berikut:
private count:number = 1; phoneNumberIds:number[] = [1]; remove(i:number) { this.phoneNumberIds.splice(i, 1); } add() { this.phoneNumberIds.push(++this.count); }
Kami harus menyimpan ID unik untuk setiap nomor telepon baru yang ditambahkan, dan di *ngFor
, lacak kontrol nomor telepon dengan id mereka (saya akui itu tidak terlalu bagus, tetapi sampai tim Angular 4 mengimplementasikan fitur ini, saya khawatir , itu yang terbaik yang bisa kita lakukan)
Oke, apa yang kita miliki sejauh ini, kita telah menambahkan formulir yang didukung Angular 4 dengan input, menambahkan pengelompokan input tertentu (lokasi dan nomor telepon) dan membuka formulir di dalam template. Tetapi bagaimana jika kita ingin mengakses objek NgForm dalam beberapa metode dalam komponen? Kita akan melihat dua cara untuk melakukan ini.
Untuk cara pertama, NgForm, yang diberi label myForm
dalam contoh saat ini, dapat diteruskan sebagai argumen ke fungsi yang akan berfungsi sebagai pengendali untuk acara onSubmit dari formulir. Untuk integrasi yang lebih baik, acara onSubmit wrapped
oleh Angular 4, acara khusus NgForm bernama ngSubmit
, dan ini adalah cara yang tepat jika kita ingin menjalankan beberapa tindakan saat pengiriman. Jadi, contoh sekarang akan terlihat seperti ini:
<form #myForm="ngForm" (ngSubmit)="register(myForm)"> … </form>
Kita harus memiliki register
metode yang sesuai, yang diimplementasikan di AppComponent. Sesuatu seperti:
register (myForm: NgForm) { console.log('Successful registration'); console.log(myForm); }
Dengan cara ini, dengan memanfaatkan acara onSubmit, kami memiliki akses ke komponen NgForm hanya ketika pengiriman dijalankan.
Cara kedua adalah menggunakan kueri tampilan dengan menambahkan dekorator @ViewChild ke properti komponen.
@ViewChild('myForm') private myForm: NgForm;
Dengan pendekatan ini, kami diizinkan mengakses formulir terlepas dari apakah acara onSubmit diaktifkan atau tidak.
Besar! Sekarang kami memiliki formulir Angular 4 yang berfungsi penuh dengan akses ke formulir di komponen. Tapi, apakah Anda melihat ada yang kurang? Bagaimana jika pengguna memasukkan sesuatu seperti "ini-bukan-tahun" di input "tahun"? Ya, Anda mengerti, kami kekurangan validasi input dan kami akan membahasnya di bagian berikut.
Validasi
Validasi sangat penting untuk setiap aplikasi. Kami selalu ingin memvalidasi input pengguna (kami tidak dapat mempercayai pengguna) untuk mencegah pengiriman/penyimpanan data yang tidak valid dan kami harus menunjukkan beberapa pesan yang berarti tentang kesalahan untuk memandu pengguna memasukkan data yang valid dengan benar.
Agar beberapa aturan validasi diterapkan pada beberapa input, validator yang tepat harus dikaitkan dengan input tersebut. Angular 4 sudah menawarkan satu set validator umum seperti: required
, maxLength
, minLength
...
Jadi, bagaimana kita bisa mengaitkan validator dengan input? Yah, cukup mudah; cukup tambahkan arahan validator ke kontrol:
<input name="name" ngModel required/>
Contoh ini membuat input "nama" menjadi wajib. Mari tambahkan beberapa validasi ke semua input dalam contoh kita.
<form #myForm="ngForm" (ngSubmit)="actionOnSubmit(myForm)" novalidate> <p>Is "myForm" valid? {{myForm.valid}}</p> .. <input type="text" name="name" ngModel required/> .. <input type="text" name="birthYear" ngModel required pattern="\\d{4,4}"/> .. <div ngModelGroup="location"> .. <input type="text" name="country" ngModel required/> .. <input type="text" name="city" ngModel/> </div> <div ngModelGroup="phoneNumbers"> .. <input type="text" name="phoneNumber[{{phoneId}}]" ngModel required/> .. </div> .. </form>
Catatan:
novalidate
digunakan untuk menonaktifkan validasi formulir asli browser.
Kami telah membuat "nama" yang diperlukan, bidang "tahun" harus diisi dan harus hanya terdiri dari angka, input negara diperlukan dan juga nomor telepon diperlukan. Juga, kami mencetak status validitas formulir dengan {{myForm.valid}}
.
Peningkatan pada contoh ini juga akan menunjukkan apa yang salah dengan input pengguna (tidak hanya menunjukkan keadaan keseluruhan). Sebelum kita melanjutkan untuk menambahkan validasi tambahan, saya ingin mengimplementasikan komponen pembantu yang memungkinkan kita untuk mencetak semua kesalahan untuk kontrol yang disediakan.
// show-errors.component.ts import { Component, Input } from '@angular/core'; import { AbstractControlDirective, AbstractControl } from '@angular/forms'; @Component({ selector: 'show-errors', template: ` <ul *ngIf="shouldShowErrors()"> <li *ngFor="let error of listOfErrors()">{{error}}</li> </ul> `, }) export class ShowErrorsComponent { private static readonly errorMessages = { 'required': () => 'This field is required', 'minlength': (params) => 'The min number of characters is ' + params.requiredLength, 'maxlength': (params) => 'The max allowed number of characters is ' + params.requiredLength, 'pattern': (params) => 'The required pattern is: ' + params.requiredPattern, 'years': (params) => params.message, 'countryCity': (params) => params.message, 'uniqueName': (params) => params.message, 'telephoneNumbers': (params) => params.message, 'telephoneNumber': (params) => params.message }; @Input() private control: AbstractControlDirective | AbstractControl; shouldShowErrors(): boolean { return this.control && this.control.errors && (this.control.dirty || this.control.touched); } listOfErrors(): string[] { return Object.keys(this.control.errors) .map(field => this.getMessage(field, this.control.errors[field])); } private getMessage(type: string, params: any) { return ShowErrorsComponent.errorMessages[type](params); } }
Daftar dengan kesalahan hanya ditampilkan jika ada beberapa kesalahan yang ada dan input disentuh atau kotor.
Pesan untuk setiap kesalahan dicari di peta pesan errorMessages
yang telah ditentukan sebelumnya (Saya telah menambahkan semua pesan di awal).
Komponen ini dapat digunakan sebagai berikut:
<div> <label>Birth Year</label> <input type="text" name="birthYear" #birthYear="ngModel" ngModel required pattern="\\d{4,4}"/> <show-errors [control]="birthYear"></show-errors> </div>
Kita perlu mengekspos NgModel untuk setiap input dan meneruskannya ke komponen yang membuat semua kesalahan. Anda dapat melihat bahwa dalam contoh ini kami telah menggunakan sebuah pola untuk memeriksa apakah datanya adalah sebuah angka; bagaimana jika pengguna memasukkan "0000"? Ini akan menjadi input yang tidak valid. Juga, kami kehilangan validator untuk nama unik, batasan aneh negara (jika negara = 'Prancis', maka kota harus 'Paris'), pola untuk nomor telepon yang benar dan validasi bahwa setidaknya satu nomor telepon ada. Ini adalah waktu yang tepat untuk melihat validator khusus.
Angular 4 menawarkan antarmuka yang harus diimplementasikan oleh setiap validator khusus, antarmuka Validator (sangat mengejutkan!). Antarmuka Validator pada dasarnya terlihat seperti ini:
export interface Validator { validate(c: AbstractControl): ValidationErrors | null; registerOnValidatorChange?(fn: () => void): void; }
Dimana setiap implementasi konkrit HARUS menerapkan metode 'validasi'. Metode validate
ini sangat menarik tentang apa yang dapat diterima sebagai input, dan apa yang harus dikembalikan sebagai output. Inputnya adalah AbstractControl, yang berarti bahwa argumen dapat berupa jenis apa pun yang memperluas AbstractControl (FormGroup, FormControl, dan FormArray). Output dari metode validate
harus null
atau undefined
(tidak ada output) jika input pengguna valid, atau mengembalikan objek ValidationErrors
jika input pengguna tidak valid. Dengan pengetahuan ini, sekarang kami akan mengimplementasikan validator birthYear
.
import { Directive } from '@angular/core'; import { NG_VALIDATORS, FormControl, Validator, ValidationErrors } from '@angular/forms'; @Directive({ selector: '[birthYear]', providers: [{provide: NG_VALIDATORS, useExisting: BirthYearValidatorDirective, multi: true}] }) export class BirthYearValidatorDirective implements Validator { validate(c: FormControl): ValidationErrors { const numValue = Number(c.value); const currentYear = new Date().getFullYear(); const minYear = currentYear - 85; const maxYear = currentYear - 18; const isValid = !isNaN(numValue) && numValue >= minYear && numValue <= maxYear; const message = { 'years': { 'message': 'The year must be a valid number between ' + minYear + ' and ' + maxYear } }; return isValid ? null : message; } }
Ada beberapa hal yang perlu dijelaskan di sini. Pertama, Anda mungkin memperhatikan bahwa kami mengimplementasikan antarmuka Validator. Metode validate
memeriksa apakah pengguna berusia antara 18 dan 85 tahun pada tahun lahir yang dimasukkan. Jika inputnya valid, maka null
dikembalikan, atau objek yang berisi pesan validasi dikembalikan. Dan bagian terakhir dan terpenting adalah mendeklarasikan arahan ini sebagai Validator. Itu dilakukan di parameter "penyedia" dari dekorator @Directive. Validator ini disediakan sebagai satu nilai dari NG_VALIDATORS multi-penyedia. Juga, jangan lupa untuk mendeklarasikan arahan ini di NgModule. Dan sekarang kita dapat menggunakan validator ini sebagai berikut:
<input type="text" name="birthYear" #year="ngModel" ngModel required birthYear/>
Ya, sesederhana itu!
Untuk nomor telepon, kami dapat memvalidasi format nomor telepon seperti ini:
import { Directive } from '@angular/core'; import { NG_VALIDATORS, Validator, FormControl, ValidationErrors } from '@angular/forms'; @Directive({ selector: '[telephoneNumber]', providers: [{provide: NG_VALIDATORS, useExisting: TelephoneNumberFormatValidatorDirective, multi: true}] }) export class TelephoneNumberFormatValidatorDirective implements Validator { validate(c: FormControl): ValidationErrors { const isValidPhoneNumber = /^\d{3,3}-\d{3,3}-\d{3,3}$/.test(c.value); const message = { 'telephoneNumber': { 'message': 'The phone number must be valid (XXX-XXX-XXX, where X is a digit)' } }; return isValidPhoneNumber ? null : message; } }
Sekarang datang dua validasi, untuk negara dan nomor telepon. Perhatikan sesuatu yang umum untuk mereka berdua? Keduanya membutuhkan lebih dari satu kontrol untuk melakukan validasi yang tepat. Nah, Anda ingat antarmuka Validator, dan apa yang kami katakan tentangnya? Argumen dari metode validate
adalah AbstractControl, yang dapat berupa input pengguna atau formulir itu sendiri. Ini menciptakan peluang untuk mengimplementasikan validator yang menggunakan beberapa kontrol untuk menentukan status validasi konkret.
import { Directive } from '@angular/core'; import { NG_VALIDATORS, Validator, FormGroup, ValidationErrors } from '@angular/forms'; @Directive({ selector: '[countryCity]', providers: [{provide: NG_VALIDATORS, useExisting: CountryCityValidatorDirective, multi: true}] }) export class CountryCityValidatorDirective implements Validator { validate(form: FormGroup): ValidationErrors { const countryControl = form.get('location.country'); const cityControl = form.get('location.city'); if (countryControl != null && cityControl != null) { const country = countryControl.value; const city = cityControl.value; let error = null; if (country === 'France' && city !== 'Paris') { error = 'If the country is France, the city must be Paris'; } const message = { 'countryCity': { 'message': error } }; return error ? message : null; } } }
Kami telah menerapkan validator baru, validator negara-kota. Anda dapat melihat bahwa sekarang sebagai argumen, metode validasi menerima FormGroup dan dari FormGroup itu kita dapat mengambil input yang diperlukan untuk validasi. Hal-hal lainnya sangat mirip dengan validator input tunggal.
Validator untuk jumlah nomor telepon akan terlihat seperti ini:
import { Directive } from '@angular/core'; import { NG_VALIDATORS, Validator, FormGroup, ValidationErrors, FormControl } from '@angular/forms'; @Directive({ selector: '[telephoneNumbers]', providers: [{provide: NG_VALIDATORS, useExisting: TelephoneNumbersValidatorDirective, multi: true}] }) export class TelephoneNumbersValidatorDirective implements Validator { validate(form: FormGroup): ValidationErrors { const message = { 'telephoneNumbers': { 'message': 'At least one telephone number must be entered' } }; const phoneNumbers = <FormGroup> form.get('phoneNumbers'); const hasPhoneNumbers = phoneNumbers && Object.keys(phoneNumbers.controls).length > 0; return hasPhoneNumbers ? null : message; } }
Kita dapat menggunakannya seperti ini:
<form #myForm="ngForm" countryCity telephoneNumbers> .. </form>
Sama dengan validator input, kan? Baru saja diterapkan ke formulir.
Apakah Anda ingat komponen ShowErrors? Kami menerapkannya untuk bekerja dengan AbstractControlDirective, yang berarti kami dapat menggunakannya kembali untuk menampilkan semua kesalahan yang terkait langsung dengan formulir ini juga. Ingatlah bahwa pada titik ini satu-satunya aturan validasi yang terkait langsung dengan formulir adalah Country-city
dan Telephone numbers
(validator lain terkait dengan kontrol formulir tertentu). Untuk mencetak semua kesalahan formulir, cukup lakukan hal berikut:
<form #myForm="ngForm" countryCity telephoneNumbers > <show-errors [control]="myForm"></show-errors> .. </form>
Hal terakhir yang tersisa adalah validasi untuk nama unik. Ini sedikit berbeda; untuk memeriksa apakah namanya unik, kemungkinan besar panggilan ke back-end diperlukan untuk memeriksa semua nama yang ada. Ini diklasifikasikan sebagai operasi asinkron. Untuk tujuan ini, kita dapat menggunakan kembali teknik sebelumnya untuk validator khusus, cukup buat validate
mengembalikan objek yang akan diselesaikan di masa mendatang (janji atau yang dapat diamati). Dalam kasus kami, kami akan menggunakan janji:
import { Directive } from '@angular/core'; import { NG_ASYNC_VALIDATORS, Validator, FormControl, ValidationErrors } from '@angular/forms'; @Directive({ selector: '[uniqueName]', providers: [{provide: NG_ASYNC_VALIDATORS, useExisting: UniqueNameValidatorDirective, multi: true}] }) export class UniqueNameValidatorDirective implements Validator { validate(c: FormControl): ValidationErrors { const message = { 'uniqueName': { 'message': 'The name is not unique' } }; return new Promise(resolve => { setTimeout(() => { resolve(c.value === 'Existing' ? message : null); }, 1000); }); } }
Kami menunggu selama 1 detik dan kemudian mengembalikan hasilnya. Mirip dengan validator sinkronisasi, jika janji diselesaikan dengan null
, itu berarti validasi lulus; jika janji diselesaikan dengan hal lain, maka validasi gagal. Perhatikan juga bahwa sekarang validator ini terdaftar ke multi-penyedia lain, NG_ASYNC_VALIDATORS
. Salah satu properti yang berguna dari formulir mengenai validator async adalah properti yang pending
. Ini dapat digunakan seperti ini:
<button [disabled]="myForm.pending">Register</button>
Ini akan menonaktifkan tombol sampai validator async diselesaikan.
Berikut adalah Plunker yang berisi AppComponent lengkap, komponen ShowErrors, dan semua Validator.
Dengan contoh-contoh ini, kami telah membahas sebagian besar kasus untuk bekerja dengan formulir berbasis template. Kami telah menunjukkan bahwa formulir berbasis template sangat mirip dengan formulir di AngularJS (akan sangat mudah bagi pengembang AngularJS untuk bermigrasi). Dengan jenis formulir ini, cukup mudah untuk mengintegrasikan formulir Angular 4 dengan pemrograman minimal, terutama dengan manipulasi dalam template HTML.
Formulir Reaktif
Bentuk reaktif juga dikenal sebagai bentuk "model-driven", tapi saya suka menyebutnya bentuk "terprogram", dan segera Anda akan melihat alasannya. Formulir reaktif adalah pendekatan baru terhadap dukungan formulir Angular 4, jadi tidak seperti template-driven, pengembang AngularJS tidak akan terbiasa dengan jenis ini.
Kita bisa mulai sekarang, ingat bagaimana formulir yang digerakkan oleh template memiliki modul khusus? Nah, formulir reaktif juga memiliki modulnya sendiri, yang disebut ReactiveFormsModule dan harus diimpor untuk mengaktifkan jenis formulir ini.
import {ReactiveFormsModule} from '@angular/forms' import {NgModule} from '@angular/core' import {BrowserModule} from '@angular/platform-browser' import {AppComponent} from 'src/app.component'; @NgModule({ imports: [ BrowserModule, ReactiveFormsModule ], declarations: [ AppComponent], bootstrap: [ AppComponent ] }) export class AppModule {}
Juga, jangan lupa untuk bootstrap aplikasi.
Kita bisa memulai dengan AppComponent dan template yang sama seperti di bagian sebelumnya.
Pada titik ini, jika FormsModule tidak diimpor (dan harap pastikan bahwa itu tidak), kami hanya memiliki elemen formulir HTML biasa dengan beberapa kontrol formulir, tidak ada keajaiban Angular di sini.
Kami sampai pada titik di mana Anda akan melihat mengapa saya suka menyebut pendekatan ini "terprogram." Untuk mengaktifkan formulir Angular 4, kita harus mendeklarasikan objek FormGroup secara manual dan mengisinya dengan kontrol seperti ini:

import { FormGroup, FormControl, FormArray, NgForm } from '@angular/forms'; import { Component, OnInit } from '@angular/core'; @Component({ selector: 'my-app', templateUrl: 'src/app.component.html' }) export class AppComponent implements OnInit { private myForm: FormGroup; constructor() { } ngOnInit() { this.myForm = new FormGroup({ 'name': new FormControl(), 'birthYear': new FormControl(), 'location': new FormGroup({ 'country': new FormControl(), 'city': new FormControl() }), 'phoneNumbers': new FormArray([new FormControl('')]) }); } printMyForm() { console.log(this.myForm); } register(myForm: NgForm) { console.log('Registration successful.'); console.log(myForm.value); } }
Metode printForm
dan register
sama dengan contoh sebelumnya, dan akan digunakan pada langkah selanjutnya. Jenis kunci yang digunakan di sini adalah FormGroup, FormControl dan FormArray. Ketiga tipe ini adalah semua yang kita butuhkan untuk membuat FormGroup yang valid. FormGroup itu mudah; itu adalah wadah kontrol yang sederhana. FormControl juga mudah; itu adalah kontrol apa pun (misalnya, input). Dan terakhir, FormArray adalah bagian dari teka-teki yang kami lewatkan dalam pendekatan berbasis template. FormArray memungkinkan untuk memelihara sekelompok kontrol tanpa menentukan kunci konkret untuk setiap kontrol, pada dasarnya array kontrol (sepertinya hal yang sempurna untuk nomor telepon, bukan?).
Saat membangun salah satu dari ketiga jenis ini, ingat aturan 3 ini. Konstruktor untuk setiap jenis menerima tiga argumen— value
, validator atau daftar validator, dan validator async atau daftar validator async, yang didefinisikan dalam kode:
constructor(value: any, validator?: ValidatorFn | ValidatorFn[], asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[]);
Untuk FormGroup, value
adalah objek di mana setiap kunci mewakili nama kontrol dan nilainya adalah kontrol itu sendiri.
Untuk FormArray, value
adalah array kontrol.
Untuk FormControl, value
adalah nilai awal atau status awal (objek yang berisi value
dan properti yang disabled
) dari kontrol.
Kami telah membuat objek FormGroup, tetapi template masih belum mengetahui objek ini. Tautan antara FormGroup dalam komponen dan template dilakukan dengan empat arahan: formGroup
, formControlName
, formGroupName
, dan formArrayName
, digunakan seperti ini:
<form [formGroup]="myForm" (ngSubmit)="register(myForm)"> <div> <label>Name</label> <input type="text" name="name" formControlName="name"> </div> <div> <label>Birth Year</label> <input type="text" name="birthYear" formControlName="birthYear"> </div> <div formGroupName="location"> <h3>Location</h3> <div> <label>Country</label> <input type="text" name="country" formControlName="country"> </div> <div> <label>City</label> <input type="text" name="city" formControlName="city"> </div> </div> <div formArrayName="phoneNumbers"> <h3>Phone numbers</h3> <div *ngFor="let phoneNumberControl of myForm.controls.phoneNumbers.controls; let i=index;"> <label>Phone number {{i + 1}}</label> <input type="text" name="phoneNumber[{{phoneId}}]" [formControlName]="i"> <button type="button" (click)="remove(i)">remove</button> </div> <button type="button" (click)="add()">Add phone number</button> </div> <pre>{{myForm.value | json}}</pre> <button type="submit">Register</button> <button type="button" (click)="printMyForm()">Print to console</button> </form>
Sekarang kita memiliki FormArray, Anda dapat melihat bahwa kita dapat menggunakan struktur itu untuk merender semua nomor telepon.
Dan sekarang untuk menambahkan dukungan untuk menambah dan menghapus nomor telepon (dalam komponen):
remove(i: number) { (<FormArray>this.myForm.get('phoneNumbers')).removeAt(i); } add() { (<FormArray>this.myForm.get('phoneNumbers')).push(new FormControl('')); }
Sekarang, kami memiliki bentuk reaktif Angular 4 yang berfungsi penuh. Perhatikan perbedaan dari formulir berbasis template di mana FormGroup "dibuat dalam template" (dengan memindai struktur template) dan diteruskan ke komponen, dalam bentuk reaktif sebaliknya, FormGroup lengkap dibuat di komponen, lalu "diteruskan ke template" dan ditautkan dengan kontrol yang sesuai. Namun, sekali lagi kami memiliki masalah yang sama dengan validasi, masalah yang akan diselesaikan di bagian selanjutnya.
Validasi
Dalam hal validasi, formulir reaktif jauh lebih fleksibel daripada formulir berbasis template. Tanpa perubahan tambahan, kami dapat menggunakan kembali validator yang sama yang telah diterapkan sebelumnya (untuk template-driven). Jadi, dengan menambahkan arahan validator, kita dapat mengaktifkan validasi yang sama:
<form [formGroup]="myForm" (ngSubmit)="register(myForm)" countryCity telephoneNumbers novalidate> <input type="text" name="name" formControlName="name" required uniqueName> <show-errors [control]="myForm.controls.name"></show-errors> .. <input type="text" name="birthYear" formControlName="birthYear" required birthYear> <show-errors [control]="myForm.controls.birthYear"></show-errors> .. <div formGroupName="location"> .. <input type="text" name="country" formControlName="country" required> <show-errors [control]="myForm.controls.location.controls.country"></show-errors> .. <input type="text" name="city" formControlName="city"> .. </div> <div formArrayName="phoneNumbers"> <h3>Phone numbers</h3> .. <input type="text" name="phoneNumber[{{phoneId}}]" [formControlName]="i" required telephoneNumber> <show-errors [control]="phoneNumberControl"></show-errors> .. </div> .. </form>
Perlu diingat bahwa sekarang kita tidak memiliki direktif NgModel untuk diteruskan ke komponen ShowErrors, tetapi FormGroup lengkap sudah dibuat dan kita dapat meneruskan AbstractControl yang benar untuk mengambil kesalahan.
Inilah Plunker yang berfungsi penuh dengan jenis validasi ini untuk formulir reaktif.
Tetapi tidak akan menyenangkan jika kita hanya menggunakan kembali validatornya, bukan? Kita akan melihat bagaimana menentukan validator saat membuat grup formulir.
Ingat aturan "aturan 3s" yang kami sebutkan tentang konstruktor untuk FormGroup, FormControl, dan FormArray? Ya, kami mengatakan bahwa konstruktor dapat menerima fungsi validator. Jadi, mari kita coba pendekatan itu.
Pertama, kita perlu mengekstrak fungsi validate
dari semua validator ke dalam kelas yang mengeksposnya sebagai metode statis:
import { FormArray, FormControl, FormGroup, ValidationErrors } from '@angular/forms'; export class CustomValidators { static birthYear(c: FormControl): ValidationErrors { const numValue = Number(c.value); const currentYear = new Date().getFullYear(); const minYear = currentYear - 85; const maxYear = currentYear - 18; const isValid = !isNaN(numValue) && numValue >= minYear && numValue <= maxYear; const message = { 'years': { 'message': 'The year must be a valid number between ' + minYear + ' and ' + maxYear } }; return isValid ? null : message; } static countryCity(form: FormGroup): ValidationErrors { const countryControl = form.get('location.country'); const cityControl = form.get('location.city'); if (countryControl != null && cityControl != null) { const country = countryControl.value; const city = cityControl.value; let error = null; if (country === 'France' && city !== 'Paris') { error = 'If the country is France, the city must be Paris'; } const message = { 'countryCity': { 'message': error } }; return error ? message : null; } } static uniqueName(c: FormControl): Promise<ValidationErrors> { const message = { 'uniqueName': { 'message': 'The name is not unique' } }; return new Promise(resolve => { setTimeout(() => { resolve(c.value === 'Existing' ? message : null); }, 1000); }); } static telephoneNumber(c: FormControl): ValidationErrors { const isValidPhoneNumber = /^\d{3,3}-\d{3,3}-\d{3,3}$/.test(c.value); const message = { 'telephoneNumber': { 'message': 'The phone number must be valid (XXX-XXX-XXX, where X is a digit)' } }; return isValidPhoneNumber ? null : message; } static telephoneNumbers(form: FormGroup): ValidationErrors { const message = { 'telephoneNumbers': { 'message': 'At least one telephone number must be entered' } }; const phoneNumbers = <FormArray>form.get('phoneNumbers'); const hasPhoneNumbers = phoneNumbers && Object.keys(phoneNumbers.controls).length > 0; return hasPhoneNumbers ? null : message; } }
Now we can change the creation of 'myForm' to:
this.myForm = new FormGroup({ 'name': new FormControl('', Validators.required, CustomValidators.uniqueName), 'birthYear': new FormControl('', [Validators.required, CustomValidators.birthYear]), 'location': new FormGroup({ 'country': new FormControl('', Validators.required), 'city': new FormControl() }), 'phoneNumbers': new FormArray([this.buildPhoneNumberComponent()]) }, Validators.compose([CustomValidators.countryCity, CustomValidators.telephoneNumbers]) );
Melihat? The rule of “3s,” when defining a FormControl, multiple validators can be declared in an array, and if we want to add multiple validators to a FormGroup they must be “merged” using Validators.compose (also Validators.composeAsync is available). And, that's it, validation should be working completely. There's a Plunker for this example as well.
This goes out to everybody that hates the “new” word. For working with the reactive forms, there's a shortcut provided—a builder, to be more precise. The FormBuilder allows creating the complete FormGroup by using the “builder pattern.” And that can be done by changing the FormGroup construction like this:
constructor(private fb: FormBuilder) { } ngOnInit() { this.myForm = this.fb.group({ 'name': ['', Validators.required, CustomValidators.uniqueName], 'birthYear': ['', [Validators.required, CustomValidators.birthYear]], 'location': this.fb.group({ 'country': ['', Validators.required], 'city': '' }), 'phoneNumbers': this.fb.array([this.buildPhoneNumberComponent()]) }, { validator: Validators.compose([CustomValidators.countryCity, CustomValidators.telephoneNumbers]) } ); }
Not a very big improvement from the instantiation with “new,” but there it is. And, don't worry, there's a Plunker for this also.
In this second section, we had a look at reactive forms in Angular 4. As you may notice, it is a completely new approach towards adding support for forms. Even though it seems verbose, this approach gives the developer total control over the underlying structure that enables forms in Angular 4. Also, since the reactive forms are created manually in the component, they are exposed and provide an easy way to be tested and controlled, while this was not the case with the template-driven forms.
Nesting Forms
Nesting forms is in some cases useful and a required feature, mainly when the state (eg, validity) of a sub-group of controls needs to determined. Think about a tree of components; we might be interested in the validity of a certain component in the middle of that hierarchy. That would be really hard to achieve if we had a single form at the root component. But, oh boy, it is a sensitive manner on a couple of levels. First, nesting real HTML forms, according to the HTML specification, is not allowed. We might try to nest <form> elements. In some browsers it might actually work, but we cannot be sure that it will work on all browsers, since it is not in the HTML spec. In AngularJS, the way to work around this limitation was to use the ngForm
directive, which offered the AngularJS form functionalities (just grouping of the controls, not all form
capabilities like posting to the server) but could be placed on any element. Also, in AngularJS, nesting of forms (when I say forms, I mean NgForm) was available out of the box. Just by declaring a tree of couple of elements with the ngForm
directive, the state of each form was propagated upwards to the root element.
In the next section, we will have a look at a couple options on how to nest forms. I like to point out that we can differentiate two types of nesting: within the same component and across different components.
Nesting within the Same Component
If you take a look at the example that we implemented with the template-driven and the reactive approach, you will notice that we have two inner containers of controls, the “location” and the “phone numbers.” To create that container, to store the values in a separate property object, we used the NgModelGroup, FormGroupName, and the FormArrayName directives. If you have a good look at the definition of each directive, you may notice that each one of them extends the ControlContainer class (directly or indirectly). Well, what do you know, it turns out this is enough to provide the functionality that we require, wrapping up the state of all inner controls and propagating that state to the parent.
For the template-driven form, we need to do the following changes:
<form #myForm="ngForm" (ngSubmit)="register(myForm)" novalidate> .. <div ngModelGroup="location" #location="ngModelGroup" countryCity> .. <show-errors [control]="location"></show-errors> </div> <div ngModelGroup="phoneNumbers" #phoneNumbers="ngModelGroup" telephoneNumbers> .. <show-errors [control]="phoneNumbers"></show-errors> </div> </form>
We added the ShowErrors component to each group, to show the errors directly associated with that group only. Since we moved the countryCity
and telephoneNumbers
validators to a different level, we also need to update them appropriately:
// country-city-validator.directive.ts let countryControl = form.get('country'); let cityControl = form.get('city');
And telephone-numbers-validator.directive.ts to:
let phoneNumbers = form.controls; let hasPhoneNumbers = phoneNumbers && Object.keys(phoneNumbers).length > 0;
You can try the full example with template-driven forms in this Plunker.
And for the reactive forms, we will need some similar changes:
<form [formGroup]="myForm" (ngSubmit)="register(myForm)" novalidate> .. <div formGroupName="location"> .. <show-errors [control]="myForm.controls.location"></show-errors> </div> <div formArrayName="phoneNumbers"> .. <show-errors [control]="myForm.controls.phoneNumbers"></show-errors> </div> .. </form>
The same changes from country-city-validator.directive.ts
and telephone-numbers-validator.directive.ts
are required for the countryCity
and telephoneNumbers
validators in CustomValidators to properly locate the controls.
And lastly, we need to modify the construction of the FormGroup to:
this.myForm = new FormGroup({ 'name': new FormControl('', Validators.required, CustomValidators.uniqueName), 'birthYear': new FormControl('', [Validators.required, CustomValidators.birthYear]), 'location': new FormGroup({ 'country': new FormControl('', Validators.required), 'city': new FormControl() }, CustomValidators.countryCity), 'phoneNumbers': new FormArray([this.buildPhoneNumberComponent()], CustomValidators.telephoneNumbers) });
And there you have it—we've improved the validation for the reactive forms as well and as expected, the Plunker for this example.
Nesting across Different Components
It may come as a shock to all AngularJS developers, but in Angular 4, nesting of forms across different component doesn't work out of the box. I'm going to be straight honest with you; my opinion is that nesting is not supported for a reason (probably not because the Angular 4 team just forgot about it). Angular4's main enforced principle is a one-way data flow, top to bottom through the tree of components. The whole framework was designed like that, where the vital operation, the change detection, is executed in the same manner, top to bottom. If we follow this principle completely, we should have no issues, and all changes should be resolved within one full detection cycle. That's the idea, at least. In order to check that one-way data flow is implemented correctly, the nice guys in the Angular 4 team implemented a feature that after each change detection cycle, while in development mode, an additional round of change detection is triggered to check that no binding was changed as a result of reverse data propagation. What this means, let's think about a tree of components (C1, C2, C3, C4) as in Fig. 1, the change detection starts at the C1 component, continues at the C2 component and ends in the C3 component.
If we have some method in C3 with a side effect that changes some binding in C1, that means that we are pushing data upwards, but the change detection for C1 already passed. When working in dev mode, the second round kicks in and notices a change in C1 that came as a result of a method execution in some child component. Then you are in trouble and you'll probably see the “Expression has changed after it was checked” exception. You could just turn off the development mode and there will be no exception, but the problem will not be solved; plus, how would you sleep at night, just sweeping all your problems under the rug like that?
Setelah Anda mengetahuinya, pikirkan tentang apa yang kita lakukan jika kita menggabungkan keadaan bentuk. Itu benar, data didorong ke atas pohon komponen. Bahkan saat bekerja dengan formulir tingkat tunggal, integrasi kontrol formulir ( ngModel
) dan formulir itu sendiri tidak begitu bagus. Mereka memicu siklus deteksi perubahan tambahan saat mendaftar atau memperbarui nilai kontrol (ini dilakukan dengan menggunakan janji yang diselesaikan, tetapi merahasiakannya). Mengapa babak tambahan diperlukan? Nah untuk alasan yang sama, data mengalir ke atas, dari kontrol ke formulir. Namun, mungkin terkadang, formulir bersarang di beberapa komponen adalah fitur yang diperlukan dan kami perlu memikirkan solusi untuk mendukung persyaratan ini.
Dengan apa yang kita ketahui sejauh ini, ide pertama yang muncul di benak adalah menggunakan bentuk reaktif, membuat pohon bentuk lengkap di beberapa komponen akar dan kemudian meneruskan formulir anak ke komponen anak sebagai input. Dengan cara ini Anda telah menggabungkan komponen induk dengan komponen anak secara erat dan mengacaukan logika bisnis komponen root dengan menangani pembuatan semua formulir anak. Ayo, kami profesional, saya yakin kami dapat menemukan cara untuk membuat komponen yang benar-benar terisolasi dengan formulir dan menyediakan cara formulir untuk menyebarkan status kepada siapa pun yang menjadi induknya.
Semua ini dikatakan, inilah arahan yang memungkinkan bentuk Angular 4 bersarang (diimplementasikan karena diperlukan untuk sebuah proyek):
import { OnInit, OnDestroy, Directive, SkipSelf, Optional, Attribute, Injector, Input } from '@angular/core'; import { NgForm, FormArray, FormGroup, AbstractControl } from '@angular/forms'; const resolvedPromise = Promise.resolve(null); @Directive({ selector: '[nestableForm]' }) export class NestableFormDirective implements OnInit, OnDestroy { private static readonly FORM_ARRAY_NAME = 'CHILD_FORMS'; private currentForm: FormGroup; @Input() private formGroup: FormGroup; constructor(@SkipSelf() @Optional() private parentForm: NestableFormDirective, private injector: Injector, @Attribute('rootNestableForm') private isRoot) { } ngOnInit() { if (!this.currentForm) { // NOTE: at this point both NgForm and ReactiveFrom should be available this.executePostponed(() => this.resolveAndRegister()); } } ngOnDestroy() { this.executePostponed(() => this.parentForm.removeControl(this.currentForm)); } public registerNestedForm(control: AbstractControl): void { // NOTE: prevent circular reference (adding to itself) if (control === this.currentForm) { throw new Error('Trying to add itself! Nestable form can be added only on parent "NgForm" or "FormGroup".'); } (<FormArray>this.currentForm.get(NestableFormDirective.FORM_ARRAY_NAME)).push(control); } public removeControl(control: AbstractControl): void { const array = (<FormArray>this.currentForm.get(NestableFormDirective.FORM_ARRAY_NAME)); const idx = array.controls.indexOf(control); array.removeAt(idx); } private resolveAndRegister(): void { this.currentForm = this.resolveCurrentForm(); this.currentForm.addControl(NestableFormDirective.FORM_ARRAY_NAME, new FormArray([])); this.registerToParent(); } private resolveCurrentForm(): FormGroup { // NOTE: template-driven or model-driven => determined by the formGroup input return this.formGroup ? this.formGroup : this.injector.get(NgForm).control; } private registerToParent(): void { if (this.parentForm != null && !this.isRoot) { this.parentForm.registerNestedForm(this.currentForm); } } private executePostponed(callback: () => void): void { resolvedPromise.then(() => callback()); } }
Contoh dalam GIF berikut menunjukkan satu komponen main
yang berisi form-1
dan, di dalam form itu, ada komponen bersarang lainnya, component-2
. component-2
berisi form-2
, yang memiliki form-2.1
bersarang , form-2.2
, dan sebuah komponen ( component-3
) yang memiliki pohon bentuk reaktif di dalamnya dan sebuah komponen ( component-4
) yang berisi form yang terisolasi dari semua bentuk lainnya. Agak berantakan, saya tahu, tetapi saya ingin membuat skenario yang agak rumit untuk menunjukkan fungsionalitas dari direktif ini.
Contohnya diimplementasikan di Plunker ini.
Fitur-fitur yang ditawarkannya adalah:
Mengaktifkan bersarang dengan menambahkan direktif nestableForm ke elemen: form, ngForm, [ngForm], [formGroup]
Bekerja dengan formulir yang digerakkan oleh template dan reaktif
Memungkinkan membangun pohon bentuk yang mencakup banyak komponen
Mengisolasi sub-pohon formulir dengan rootNestableForm="true" (tidak akan mendaftar ke induk nestableForm)
Arahan ini memungkinkan formulir dalam komponen anak untuk mendaftar ke nestableForm induk pertama, terlepas dari apakah formulir induk dideklarasikan dalam komponen yang sama atau tidak. Kami akan masuk ke rincian implementasi.
Pertama, mari kita lihat konstruktornya. Argumen pertama adalah:
@SkipSelf() @Optional() private parentForm: NestableFormDirective
Ini mencari induk NestableFormDirective pertama. @SkipSelf, untuk tidak mencocokkan dirinya sendiri, dan @Optional karena mungkin tidak menemukan induk, dalam hal bentuk root. Sekarang kita memiliki referensi ke bentuk nestable induk.
Argumen kedua adalah:
private injector: Injector
Injektor digunakan untuk mengambil provider
FormGroup saat ini (templat atau reaktif).
Dan argumen terakhir adalah:
@Attribute('rootNestableForm') private isRoot
untuk mendapatkan nilai yang menentukan apakah bentuk ini diisolasi dari pohon bentuk.
Selanjutnya, pada ngInit
sebagai tindakan yang ditunda (ingat aliran data terbalik?), FormGroup saat ini diselesaikan, kontrol FormArray baru bernama CHILD_FORMS
terdaftar ke FormGroup ini (di mana formulir anak akan didaftarkan) dan sebagai tindakan terakhir, FormGroup saat ini terdaftar sebagai anak ke formulir induk yang dapat disarangkan.
Tindakan ngOnDestroy
dijalankan ketika formulir dihancurkan. Pada penghancuran, sekali lagi sebagai tindakan yang ditunda, formulir saat ini dihapus dari induknya (pencabutan pendaftaran).
Arahan untuk formulir yang dapat disarang dapat disesuaikan lebih lanjut untuk kebutuhan khusus—mungkin menghapus dukungan untuk formulir reaktif, mendaftarkan setiap formulir anak dengan nama tertentu (bukan dalam larik CHILD_FORMS), dan seterusnya. Implementasi direktif nestableForm ini memenuhi persyaratan proyek dan disajikan di sini. Ini mencakup beberapa kasus dasar seperti menambahkan formulir baru atau menghapus formulir yang ada secara dinamis (*ngIf) dan menyebarkan status formulir ke induknya. Ini pada dasarnya bermuara pada operasi yang dapat diselesaikan dalam satu siklus deteksi perubahan (dengan menunda atau tidak).
Jika Anda menginginkan beberapa skenario yang lebih maju seperti menambahkan validasi bersyarat ke beberapa input (misalnya [diperlukan]="someCondition") yang memerlukan 2 putaran deteksi perubahan, itu tidak akan berfungsi karena aturan "resolusi siklus satu deteksi" dipaksakan oleh Sudut 4.
Bagaimanapun, jika Anda berencana menggunakan arahan ini, atau menerapkan beberapa solusi lain, berhati-hatilah dengan hal-hal yang disebutkan terkait dengan deteksi perubahan. Pada titik ini, inilah cara Angular 4 diimplementasikan. Itu mungkin berubah di masa depan—kita tidak bisa tahu. Pengaturan saat ini dan pembatasan yang diberlakukan di Angular 4 yang disebutkan dalam artikel ini mungkin merupakan kekurangan atau manfaat. Masih harus dilihat.
Formulir Menjadi Mudah dengan Angular 4
Seperti yang Anda lihat, tim Angular telah melakukan pekerjaan yang sangat baik dalam menyediakan banyak fungsi yang terkait dengan formulir. Saya berharap posting ini akan berfungsi sebagai panduan lengkap untuk bekerja dengan berbagai jenis bentuk di Angular 4, juga memberikan wawasan tentang beberapa konsep yang lebih maju seperti bentuk bersarang dan proses deteksi perubahan.
Terlepas dari semua posting berbeda yang terkait dengan formulir Angular 4 (atau subjek Angular 4 lainnya dalam hal ini), menurut saya, titik awal terbaik adalah dokumentasi resmi Angular 4 . Juga, orang-orang Angular memiliki dokumentasi yang bagus dalam kode mereka. Sering kali, saya menemukan solusi hanya dengan melihat kode sumber dan dokumentasi di sana, tidak ada Googling atau apa pun. Tentang kumpulan formulir, yang dibahas di bagian terakhir, saya percaya bahwa pengembang AngularJS mana pun yang mulai mempelajari Angular 4 akan menemukan masalah ini di beberapa titik, yang merupakan inspirasi saya untuk menulis posting ini.
Seperti yang juga telah kita lihat, ada dua jenis formulir, dan tidak ada aturan ketat bahwa Anda tidak dapat menggunakannya bersama-sama. Sangat bagus untuk menjaga basis kode tetap bersih dan konsisten, tetapi terkadang, sesuatu dapat dilakukan dengan lebih mudah dengan formulir berbasis template dan, terkadang, sebaliknya. Jadi, jika Anda tidak keberatan dengan ukuran bundel yang sedikit lebih besar, saya sarankan untuk menggunakan apa pun yang Anda anggap lebih tepat kasus per kasus. Hanya saja, jangan mencampurnya dalam komponen yang sama karena mungkin akan menimbulkan kebingungan.
Plunker Digunakan di Postingan Ini
Formulir berbasis template
Formulir reaktif, validator template
Formulir reaktif, validator kode
Formulir reaktif, pembuat formulir
Formulir berbasis template, bersarang di dalam komponen yang sama
Bentuk reaktif, bersarang dalam komponen yang sama
Bentuk bersarang melalui pohon komponen