Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically adding Components to a FormArray that is in a Form (Angular 2/4)

Tags:

angular

I have a registration form where an user could add one/more Address(es), one/more phone(s) to a new or existing Organization, Address and Phone are actually (reusable) components.

For example, Phone component is similar with that one from Android contact details, it has 3 FormControls: a Type (drop-down list), number (input) and a remove button.

At the end, the entire form must be submitted to save the information.

Question: how can I dynamically add Phones to the form and display already existing ones? (code & markup below). I'd much appreciate your time and effort answering me!

organization-detail.component.ts

import { Component, Input } from '@angular/core';
import { FormBuilder, FormGroup, FormArray, FormControl, Validators } from '@angular/forms';

import { Organization } from './organization';
import { PhoneDetailComponent } from "../phone-detail/phone-detail.component";

@Component({
    selector: 'organization-detail',
    templateUrl: './organization-detail.component.html',
    styleUrls: ['./organization-detail.component.css']
})

export class OrganizationDetailComponent {
    organizationForm: FormGroup;

    constructor (private fb: FormBuilder){
      this.createForm();
    }

    createForm() {
        this.organizationForm = this.fb.group({
        accountingId: [''],
        externalId: '',
        isHost: false,
        logoPath: '',
        name: ['', 
            [Validators.required,
            Validators.minLength(4),
            Validators.maxLength(24)]
            ], // <--- the FormControl called "name"
        notes: '',
        registrationNo: '',
        vATId: '',
        webSite: '',
        phones: new FormArray([])
        //phones: new Array<PhoneDetailComponent>
        });
    }

    onSubmit() {
      console.log(this.organizationForm);  
    }

    onAddPhone() {
      const control = new PhoneDetailComponent();
      (<FormArray>this.organizationForm.get('phones')).push(control);
    }

}

organization-detail.component.html

<div class="container">
  <div class="row">
    <div class="col-xs-12 col-sm-10 col-md-8 col-sm-offset-1 col-md-offset-2">
      <h2>Organization Detail</h2>
      <form [formGroup]="organizationForm" (ngSubmit)="onSubmit()" class="form-horizontal">
        <div class="form-group">
          <label for="isHost"> Is host:</label>
          <input type="checkbox" id="isHost" formControlName="isHost" class="check" />
        </div>
        <div class="form-group">
          <label for="name">Name:</label>
          <input type="text" id="name" class="form-control" formControlName="name" placeholder="Organization name" />
          <span 
            *ngIf="!organizationForm.get('name').valid && organizationForm.get('name').touched" 
            class="help-block">Please enter a valid name!</span>
        </div>
        <div class="form-group">
          <label for="accountingId">Accounting Id:</label>
          <input type="text" id="accountingId" class="form-control" formControlName="accountingId" />
        </div>
        <div class="form-group">
          <label for="externalId">External Id:</label>
          <input type="text" id="externalId" class="form-control" formControlName="externalId" />
        </div>
        <div class="form-group">
          <label for="registrationNo">Registration No:</label>
          <input type="text" id="registrationNo" class="form-control" formControlName="registrationNo" />
        </div>
        <div class="form-group">
          <label for="vATId">VAT Id:</label>
          <input type="text" id="vATId" class="form-control" formControlName="vATId" />
        </div>
        <div class="form-group">
          <label for="webSite">Web site:</label>
          <input type="text" id="webSite" class="form-control" formControlName="webSite" />
        </div>
        <div class="form-group">
          <label for="logoPath">Logo path:</label>
          <input type="text" id="logoPath" class="form-control" formControlName="logoPath" />
        </div>
        <div class="form-group">
          <label for="notes">Notes:</label>
          <textarea id="notes" class="form-control" formControlName="notes" rows="3"></textarea>
        </div>
        <div formArrayName="phones">
          <h4>Phones:</h4>
          <button class="btn btn-default" type="button"
            (click)="onAddPhone()">
            Add phone
          </button>
          <div class="form-group"
            *ngFor="let phoneControl of organizationForm.get('phones').controls; let i = index">
            <!--<input type="text" class="form-control" [formControlName]="i">-->
            <phone-detail></phone-detail>
          </div>
        </div>
        <div class="form-group">
          <div class="input-group">
            <input type="text" class="form-control">
            <span class="input-group-btn">
                  <button class="btn btn-default" type="button">Go!</button>
            </span>
          </div>
        </div>
        <button class="btn btn-primary" type="submit">Submit</button>
      </form>
    </div>
  </div>
</div>
<p>Form value: {{ organizationForm.value | json }}</p>
<p>Form status: {{ organizationForm.status | json }}</p>

phone-detail.component.ts

import { Component, OnInit, Input } from '@angular/core';

import { Phone } from "./phone";

@Component({
  selector: 'phone-detail',
  templateUrl: './phone-detail.component.html',
  styleUrls: ['./phone-detail.component.css']
})
export class PhoneDetailComponent implements OnInit {
  @Input('phoneItem') item: Phone;

  constructor() { }

  ngOnInit() {
  }
}

phone-detail.component.html

<div class="container">
  <div class="row">
    <div class="col-xs-3">
      <select class="form-control">
        <option>1</option>
        <option>2</option>
        <option>3</option>
        <option>4</option>
        <option>5</option>
      </select> 
    </div>
    <div class="col-xs-7">
      <input type="text" class="form-control">
    </div>
    <div class="col-xs-2">
      <button class="btn">X</button>
    </div>
  </div>
</div>
like image 236
Dan Avatar asked Jun 10 '17 09:06

Dan


2 Answers

In your parent, have the formarray:

phones: this.fb.array([])

Then in parent template pass each formgroup inside this formarray to the child:

<button (click)="addPhone()">Add phone</button>
<div formArrayName="phones">
  <div *ngFor="let ctrl of organizationForm.controls.phones.controls; let i=index">
    <button (click)="removePhone(i)">X</button>
    <phone-detail [group]="ctrl"></phone-detail>
  </div>
</div>

in child use @Input:

@Input() group: FormGroup;

and in the template add the form controls and formgroup:

<div [formGroup]="group">
  <select formControlName="type">
    <option>1</option>
    <option>2</option>
    <option>3</option>
    <option>4</option>
    <option>5</option>
  </select> 
  <input type="text" formControlName="num">
</div>

And finally the removing and adding of form groups which are placed in parent:

initPhone() {
  return this.fb.group({
    type: [''],
    num: ['']
  });
}

addPhone() {
    const control = this.organizationForm.controls.phones;
    control.push(this.initPhone());
}

removePhone(i: number) {
    const control = this.organizationForm.controls.phones;
    control.removeAt(i);
}

This article is worth reading regarding this question!

like image 55
AT82 Avatar answered Nov 11 '22 14:11

AT82


Thanks a lot for your responses, helped a lot! I used an event to trigger removing a phone registration. Solution suitable for my situation is:

In parent organization-edit.component.html:

<div formArrayName="phones">
  <h4>Phones:</h4>
  <button class="btn btn-default" type="button"
    (click)="onAddPhone()">
    Add phone
  </button>
  <div class="form-group"
     *ngFor="let phoneControl of organizationForm.get('phones').controls; let i = index">
      <phone-detail [formGroupName]="i" (phoneDeleted)="onDeletePhone(i)"></phone-detail>
  </div>
</div>

organization-edit.component.ts (former detail!):

createForm() {
  this.organizationForm = this.fb.group({
    accountingId: [''],
  ......
  phones: new FormArray([]) 
});

onAddPhone() {
  const control = new FormControl(null, Validators.required);
  (<FormArray>this.organizationForm.get('phones')).push(control);
}

onDeletePhone(index: any){
    (<FormArray>this.organizationForm.get('phones')).removeAt(index);
}

phone-detail.component.html

<div class="row">
<div class="col-xs-3">
  <select class="form-control">
    <option>1</option>
    <option>2</option>
    <option>3</option>
    <option>4</option>
    <option>5</option>
  </select> 
</div>
<div class="col-xs-7">
  <input type="number" class="form-control">
</div>
<div class="col-xs-2">
  <button 
    type="button"
    class="btn btn-danger"
    (click)="onDeletePhone()">X</button>
</div>

phone-detail.component.ts:

export class PhoneDetailComponent implements OnInit {
  phoneForm: FormGroup;
  @Input('phoneItem') item: Phone;
  @Output() phoneDeleted = new EventEmitter<void>();
  @Output() phone: Phone;

  constructor(private fb: FormBuilder) {
    this.phoneForm = this.fb.group({
       type: [''],
       number: ['']
    });
  }

  onDeletePhone(){
    this.phoneDeleted.emit();
  }

}
like image 43
Dan Avatar answered Nov 11 '22 13:11

Dan