Search code examples
angularangular-reactive-formsangular12formarray

Angular - How to allow only one checkbox to be selected in dynamic FormArray


I have Angular-12 dynamic FormArray:

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

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  contactInfoForm: FormGroup;
  constructor(private fb: FormBuilder) {}
  ngOnInit(): void {
    this.updateContact();
  }

  get contacts() {
    return this.contactInfoForm.controls['contacts'] as FormArray;
  }

  getFormGroup(index: number): FormGroup {
    return this.contacts.at(index) as FormGroup;
  }

  updateContact() {
    this.contactInfoForm = this.fb.group({
      id: [''],
      current_residential_address: [
        '', [
          Validators.required,
          Validators.minLength(2),
          Validators.maxLength(500)
        ]
      ],
      contacts: this.fb.array([this.addContactFormGroup()])
    });
  }

  addContactFormGroup(): FormGroup {
    return this.fb.group({
      phone_type_id: ['', Validators.required],
      phone_number: ['', [Validators.required, Validators.maxLength(15)]],
      is_primary_contact_number: ['']
    });
  }

  public addContactButtonClick() {
    const contacts = this.contactInfoForm.get('contacts') as FormArray
    contacts.push(this.addContactFormGroup())
  }

  get fc() {
    return this.contactInfoForm.controls;
  }
}
<form [formGroup]="contactInfoForm">
  <div class="row">
    <div class="col-12 col-md-12">
      <div class="form-group">
        <label for="current_residential_address">Current Residential Address:<span style="color:red;">*</span></label>
        <textarea rows="2" formControlName="current_residential_address" name="description" type="text" placeholder="22, Alexander Close ..." class="form-control mb-3" required>
                            </textarea>
      </div>
      <div *ngIf="fc.current_residential_address.touched && fc.current_residential_address.invalid">
        <div *ngIf="fc.current_residential_address.hasError('required')">
          <div class="text-danger">
            Current Residential Address is required!
          </div>
        </div>
        <div *ngIf="fc.current_residential_address.hasError('minlength')">
          <div class="text-danger">
            Current Residential Address cannot be less than 2 characters!
          </div>
        </div>
        <div *ngIf="fc.current_residential_address.hasError('maxlength')">
          <div class="text-danger">
            Current Residential Address cannot be more than 500 characters!
          </div>
        </div>
      </div>
    </div>
  </div>
  <div class="row">
    <div formArrayName="contacts" class="col-md-12" *ngFor="let contact of contacts.controls; let i = index">
      <div [formGroupName]="i" class="row">
        <div class="col-12 col-md-4">
          <div class="form-group">
            <label for="phone_number">Phone Number:<span style="color:red;">*</span></label>
            <div class="input-group mb-4">
              <input type="text" formControlName="phone_number">
            </div>
          </div>
          <div *ngIf="getFormGroup(i).get('phone_number').touched && getFormGroup(i).get('phone_number').invalid">
            <div *ngIf="getFormGroup(i).get('phone_number').hasError('required')">
              <div class="text-danger">
                Phone Number is required!
              </div>
            </div>
            <div *ngIf="getFormGroup(i).get('phone_number').hasError('validatePhoneNumber')">
              <div class="text-danger">
                Invalid Phone Number!
              </div>
            </div>
          </div>
        </div>
        <div class="col-12 col-md-4">
          <div class="form-group">
            <label for="phone_type_id">Phone Type:<span style="color:red;">*</span></label>
            <input type="text" formControlName="phone_type_id">

          </div>
          <div *ngIf="getFormGroup(i).get('phone_type_id').touched && getFormGroup(i).get('phone_type_id').invalid">
            <div *ngIf="getFormGroup(i).get('phone_type_id').hasError('required')">
              <div class="text-danger">
                Phone Type is required!
              </div>
            </div>
          </div>
        </div>
        <div class="col-12 col-md-2">
          <div class="form-group">
            <label for="is_primary_contact_number">Is Primary Line?:</label><br>
            <input type="checkbox" class="form-check-input" id="exampleCheck2">
          </div>
        </div>
        <div class="col-12 col-md-2">
          <div class="form-group">
            <button type="button" class="btn btn-danger float-right"><i class="fas fa-minus"></i> Remove</button>
          </div>
        </div>
      </div>
      <button type="button" class="btn btn-primary float-right" (click)="addContactButtonClick()" matTooltip="Add"><i class="fas fa-plus"></i> Add item</button>
    </div>
  </div>
  {{ "Is Form Valid : "+contactInfoForm.valid}}
</form>

A user can only have one primary line. So in the FormArray, I want the user to be able to check only one of the checkboxes for is_primary_contact_number to only be selected or checked once.

How do I achieve this?

Thanks.


Solution

    1. For the checkbox element, add (change) event with onIsPrimaryContactChecked(i) by parsing the FormArray index.
    2. In onIsPrimaryContactChecked method, iterate with this.contacts FormArray, skip current checkbox index, otherwise set the remaining checkboxes to false.

    app.component.html

    <input type="checkbox" class="form-check-input" id="exampleCheck2" formControlName="is_primary_contact_number" (change)="onIsPrimaryContactChecked(i)">
    

    app.component.ts

    onIsPrimaryContactChecked(index: number) {
      for (let i = 0; i < this.contacts.length; i++) {
        if (i == index) continue;
    
        this.getFormGroup(i)
          .get('is_primary_contact_number')
          ?.setValue(false);
      }
    }
    

    Sample Solution on StackBlitz