10

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>
1

2 Answers 2

19

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!

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks AJT_82 for your answer! The fact is I'd like to have Phone details as a reusable Component as I have in phone-detail.component.ts, and use this in organization-detail.component.
Sorry, I'm not really following on what you wish to have? I understand what you mean with reusable yes, but not on how generic this should be.
Hi AJT_82, sorry for late answer! Thanks for your answer, I was my misunderstanding. Below I described a solution with exactly what I needed, also I have another question: How can I extract Phone object from phone-detail.component.ts?
2

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();
  }

}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.