lifelong learner — urip iku urup, currently working on accenture.

angular – dynamic multi form input non form array

0
Degananda.com -

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

  1. 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.
  2. Nodejs. Saran kami gunakanlah versi stable 6.x jangan menggunakan yang terbaru karena belum teruji tingkat kestabilannya.
  3. 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

  1. hapus array member / item pada variabel keluarga dengan menggunakana Arraysplice. Terdapat fungsi  findIndexKeluarga() untuk mendapatkan index dari array member yang akan dihapus.
  2. Setelah itu kita akan memanggi fungsi updateKeluargaFormControl() dengan input berupa object keluarga.
  3. 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.

  1. 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.
  2. Kita buat temporary formgroup untuk menyimpan semua nilai yang ada diformgroup form keluarga.
  3. Kita kosongkan formkeluarga (remove semua formcontrol)
  4. kita lakukan iterasi terhadap nilai array keluarga saat ini
  5. 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);
  }
  

}