Selayang pandang
Terkadang suatu bisnis proses membutuhkan form yang dinamis. Misalnya form untuk menambahkan daftar keluarga dari seseorang. Kita bisa saja membuat form tersebut hanya menerima satu buah input saja. Maka jika terdapat lima orang keluarga user harus 5x mensubmit form tersebut. Secara UX(user experience) hal seperti ini kurang baik.
Seharusnya ketika terdapat form yang datannya “dinamis” a.k.a kita tidak tahu jumlah pastinya misal pada contoh diatas adalah daftar keluarga maka penggunaan dynamic multi form input ini diperlukan sehingga user hanya perlu submit 1x form dengan jumlah data yang mereka inginkan. Pada ulasan ini kita akan coba membuat mekanisme form dinamis dengan menggunakan angular.
What will we build
Form yang akan kita buat adalah mengenai daftar keluarga. User dapat menambahkan berapapun jumlah keluargannya dalam satu form. Artinnya pada saat disubmit seluruh data tersebut dapat langsung diproses. Karena form ini dinamis maka setiap input atau item harus dapat diubah, dihapus(delete) maupun ditambahkan.
Ini adalah metode pertama untuk membuat dyanmic form yakni tanpa menggunakan formArray. Ulasan selanjutnya adalah kita akan menggunakan formArray. Tujuan kenapa dibuat dua jenis adalah karena keduannya dapat dimanfaatkan di situasi yang berbeda dan bisa menjadi alternatif. Perlu diketahui bahwa metode pertama tanpa formArray ini jauh lebih rumit. karena kita harus melakukan reset ulang terhadap formgroup untuk mengurutkan indexnya.
Berikut ini adalah penampakan UI (user interfaces) dari form yang akan kita buat.
“tambah keluarga” jika diklik akan menambahkan satu field dibawahnya. Misalnya terdapat [keluarga1 ,keluarga2] maka ketika ditambah akan menjadi [keluarga1,keluarga2,keluarga3].
Hapus keluarga
“hapus” jika diklik akan menghapus field yang bersangkutan. Misalkan terdapat [keluarga1,keluarga2 keluarga3]. Kemudian keluarga2 didelete maka akan menjaid [keluarga1,keluarga2] dengan nilai value tetap. Berikut ini saat keluarga 2 dihapus
Online demo
anda dapat mengakses demo form diatas pada link dibawah ini :
http://demo.degananda.com/angular/dyanmic-form-nonformarray/
Persiapan
Pastikan anda telah menginstall beberapa tools berikut ini
- Angular-cli, kami menggunakan versi 1.2.7. Versi berapapun angular anda tidak akan masalah karena kita hanya akan menggunakan module reactiveFormModule dari package @angular/form.
- Nodejs. Saran kami gunakanlah versi stable 6.x jangan menggunakan yang terbaru karena belum teruji tingkat kestabilannya.
- Texteditor, kami sarankan menggunakan microsoft visual studio code karena angular menggunakan typescript dan microsoft visual studio code disupport oleh typescript secara penuh (pengembangannya) maka editor tersebut sangat direkomendasikan dan editor ini free. Tidak seperti angular IDE dari web storm.
Jika ketiga hal diatas telah siap maka kita dapat langsung mengimplementasikan dynamic multi form input dengan menggunakan angular.
Implementasi
pertama kita buat project angular denggan perintah
ng new nama_project
setelah itu kita buat komponen
ng g component dyanmic-input
dyanmic-input.component.html
kemudian kita buat view (komponen html) dari komponen diatas
inputan yang dinamis kita buat dengan menggunakan ngFor atau looping dari variabel keluarga yang bertipe array. Link untuk menghapus field atau input/keluarga hanya akan muncul apabila terdapat lebih dari satu field input. Ini karena minimal harus terdapat satu buah field keluarga. FormControlName akan didapatkan dari item hasil iterasi (looping) yang dibuat berurutan berdasarkan jumlah dari keluarga.
dyanmic-input.component.ts
Kemudian pada class dyanmic-input.component.ts kita membutuhkan tiga buah modul dari package angular/forms yaitu formbuilder, validator dan formgroup
import { Component, OnInit } from '@angular/core'; import { FormBuilder, Validators, FormGroup } from '@angular/forms';
setelah itu kita inisisasi variabel – variabel yang dibutuhkan dan melakukan DI(Dependency injection) terhadap formbuilder agar dapat digunakan pada class ini dengan access modifier / encapsulation (private). Dan pada lifeCycleHooks ngOnInit kita lakukan inisiasi form (blank tanpa formcontrol) karena formcontrol akan kita definisikan dengan looping.
formKeluarga : FormGroup; tmpFormKeluarga : FormGroup; // default jumlah keluarga adalah satu jumlahKeluarga : number = 1; keluarga : Array<Object> = []; constructor( private fb : FormBuilder ) { }
untuk membuat jumlah field keluarga kita melakukan iterasi sebanyak nilai default dari variabel jumlah keluarga. Karena looping (ngFor) pada angular membutuhkan array maka kita buat satu variabel bernama “keluarga” bertipe array yang nantinnya di iterasi dengan ngFor pada dynamic-input.component.html. Kita berikan index dan formControlName berdasarkan urutannya.
createFormInput(){ this.keluarga = []; // inisiasi array for(let i = 1;i<=this.jumlahKeluarga;i++){ this.keluarga.push({ index : 1, formControlName : 'keluarga'+i }); // add form control pertama kali sesuai dengan jumlah default this.formKeluarga.addControl('keluarga'+i, this.fb.control('', Validators.required)); } }
selanjutnya untuk menambahkan field keluarga yang kita lakukan adalah menambahkan formcontrol baru dan melakukan push satu Object baru pada variabel/array keluarga.
tambahKeluarga(){ // add form control this.formKeluarga.addControl('keluarga'+(this.keluarga.length+1), this.fb.control('', Validators.required)); this.keluarga.push({ index : this.keluarga.length+1, formControlName : 'keluarga'+(this.keluarga.length+1) }) }
pada bagian terahir yakni menghapus salah satu field pada form yang kita lakukan adalah
- hapus array member / item pada variabel keluarga dengan menggunakana Arraysplice. Terdapat fungsi findIndexKeluarga() untuk mendapatkan index dari array member yang akan dihapus.
- Setelah itu kita akan memanggi fungsi updateKeluargaFormControl() dengan input berupa object keluarga.
- done
findIndexKeluarga(label){ return this.keluarga.indexOf(label); } updateKeluargaFormControl(keluarga){ // reset all if(this.keluarga[this.keluarga.length-1]['index'] == this.keluarga.length){ this.formKeluarga.removeControl(keluarga.formControlName); } else { // buat temporary form keluarga karena kita akan delete formkeluarga this.tmpFormKeluarga = this.formKeluarga; // delete form keluarga this.formKeluarga = this.fb.group({}); for(let i = 1; i<= this.keluarga.length;i++){ // mendapatkan old index/formControl let oldIndex = this.keluarga[i-1]['index']; // update index dan formControlName keluarga this.keluarga[i-1] = { index : i, formControlName : 'keluarga'+(i) } // add control baru ke formKeluarga this.formKeluarga.addControl('keluarga'+i, this.fb.control(this.tmpFormKeluarga.controls['keluarga'+oldIndex].value, Validators.required)); } this.tmpFormKeluarga = this.fb.group({}); } } deleteKeluarga(keluarga){ // delete indexitem dari array this.keluarga.splice(this.findIndexKeluarga(keluarga), 1); this.updateKeluargaFormControl(keluarga); }
Berikut ini adalah penjelasan mengenai update form saat setelah salah satu field dihapus.
- jika yang dihapus adalah item paling bawah misalkan terdapat keluarga1,2,3. Item yang dihapus adalah keluarga tiga maka yang kita lakukan hanyalah menghapus formControl yang terkait dengan field tersebut. Jika tidak maka kita melakukan step 2 sampai terahir.
- Kita buat temporary formgroup untuk menyimpan semua nilai yang ada diformgroup form keluarga.
- Kita kosongkan formkeluarga (remove semua formcontrol)
- kita lakukan iterasi terhadap nilai array keluarga saat ini
- kita buat form control baru pada formkeluarga yang sudah kosong dengan nama baru (berurutan kembali 1,2,3,dst) dan nilai pada formgroup lama kita masukan ke form control baru ini.
Kekurangan
kekurangan dari metode seperti ini adalah hanya cocok jika terdapat satu buah nama field. Contoh diatas hanya ada satu field yang dinamis yaitu field mengenani keluarga. Bayangkan jika terdapat dua field yang dinamis misalkan nama keluarga dan umurnya maka metode seperti ini tidak dapat dilakukan.
Lalu bagaimana jika terdapat lebih dari satu field yang dinamis ? jawabannya adalah dengan menggunakan formArray. Dengan formArray kita dapat mendesign form lebih dinamis. Tujuan dari ulasan ini adalah menunjukan bahwa untuk form dengan banyak field yang dinamis kita harus menggunakan formArray. Jika formt tersebut “simpel(hanya terdapat satu field yang dinamis)” maka metode seperti ini dapat digunakan.
Keuntungan
kita tidak perlu mempelajari formArray.
Full code component
import { Component, OnInit } from '@angular/core'; import { FormBuilder, Validators, FormGroup } from '@angular/forms'; @Component({ selector: 'app-dynamic-input', templateUrl: './dynamic-input.component.html', styleUrls: ['./dynamic-input.component.css'] }) export class DynamicInputComponent implements OnInit { formKeluarga : FormGroup; tmpFormKeluarga : FormGroup; // default jumlah keluarga adalah satu jumlahKeluarga : number = 1; keluarga : Array<Object> = []; constructor( private fb : FormBuilder ) { } ngOnInit() { // inisiasi form group blank tanpa formcontrol this.formKeluarga = this.fb.group({ }); // inisiasi form input this.createFormInput(); } createFormInput(){ this.keluarga = []; // inisiasi array for(let i = 1;i<=this.jumlahKeluarga;i++){ this.keluarga.push({ index : 1, formControlName : 'keluarga'+i }); // add form control pertama kali sesuai dengan jumlah default this.formKeluarga.addControl('keluarga'+i, this.fb.control('', Validators.required)); } } onSubmit(value){ console.log(value); } tambahKeluarga(){ // add form control this.formKeluarga.addControl('keluarga'+(this.keluarga.length+1), this.fb.control('', Validators.required)); this.keluarga.push({ index : this.keluarga.length+1, formControlName : 'keluarga'+(this.keluarga.length+1) }) } findIndexKeluarga(label){ return this.keluarga.indexOf(label); } updateKeluargaFormControl(keluarga){ // reset all if(this.keluarga[this.keluarga.length-1]['index'] == this.keluarga.length){ this.formKeluarga.removeControl(keluarga.formControlName); } else { // buat temporary form keluarga karena kita akan delete formkeluarga this.tmpFormKeluarga = this.formKeluarga; // delete form keluarga this.formKeluarga = this.fb.group({}); for(let i = 1; i<= this.keluarga.length;i++){ // mendapatkan old index/formControl let oldIndex = this.keluarga[i-1]['index']; // update index dan formControlName keluarga this.keluarga[i-1] = { index : i, formControlName : 'keluarga'+(i) } // add control baru ke formKeluarga this.formKeluarga.addControl('keluarga'+i, this.fb.control(this.tmpFormKeluarga.controls['keluarga'+oldIndex].value, Validators.required)); } this.tmpFormKeluarga = this.fb.group({}); } } deleteKeluarga(keluarga){ // delete indexitem dari array this.keluarga.splice(this.findIndexKeluarga(keluarga), 1); this.updateKeluargaFormControl(keluarga); } }