user11352561
user11352561

Reputation: 2637

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.

Upvotes: 0

Views: 1589

Answers (2)

Yong Shun
Yong Shun

Reputation: 51135

  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

Upvotes: 1

paliz
paliz

Reputation: 353

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

import {ActivatedRoute, Params, Router} from '@angular/router';
import {Subscription} from 'rxjs';


import {IQuery} from '../../../../shared/interfaces/query.interface';

import {LocationChangeListener} from "@angular/common";
import {faAsterisk} from "@fortawesome/free-solid-svg-icons";
import {IGroupPermission} from "../group.permission.interface";
import {GroupPermissionService} from "../group.permission.service";
import {GroupPermission} from "../group.permission.model";
import {IGroup} from "../../group/group.interface";
import {IPermission} from "../../permission/permission.interface";
import {GroupService} from "../../group/group.service";
import {PermissionService} from "../../permission/permission.service";

@Component({
  selector: 'app-permission-group-edit',
  templateUrl: './edit.component.html',
  styleUrls: ['./edit.component.scss']
})
export class EditComponent implements OnInit, OnDestroy {
  faIcon = {faAsterisk};
  formGroup!: FormGroup;
  submitted: boolean;
  editId: number;
  subscription: Subscription[];
  permissionGroupDetail!: IGroupPermission;
  groupRows!: IGroup;
  permissionRows!: IPermission;

  isGet: boolean;
  isPut: boolean;
  isPost: boolean;
  isDelete: boolean;
  isCheck: boolean;

  @HostListener('window:popstate', ['$event'])
  onPopState(event: LocationChangeListener): void {

    let params: IQuery = {};


    this.subscription.push(this.permissionGroupService.getQueryArgumentObservable().subscribe((qParams: IQuery) => {

      params = qParams;
    }));

    this.permissionGroupService.setQueryArgument(params);
    this.router.navigate(['./admin/group-permission/list'], {
      queryParams: params,

    });
  }

  constructor(private formBuilder: FormBuilder,
              private permissionGroupService: GroupPermissionService,
              private aRoute: ActivatedRoute,
              private router: Router,
              private groupService: GroupService,
              private  permissionService: PermissionService,
  ) {
    this.submitted = false;
    this.editId = 0;

    this.isDelete = false;
    this.isGet = false;
    this.isPost = false;
    this.isPut = false;
    this.isCheck = false;
    this.subscription = [];

  }

  ngOnInit(): void {

    this.formGroup = this.formBuilder.group({

      permissionId: new FormControl('', [
        Validators.required,
        Validators.maxLength(255)
      ]),
      groupId: new FormControl('', [
        Validators.required,
        Validators.maxLength(255)
      ]),
      actions: this.formBuilder.array([], [])

    });


    this.subscription.push(this.aRoute.params.pipe().subscribe((params: Params) => {

      this.editId = +params.id;

    }));


    this.groupService.query();

    this.subscription.push(
      this.groupService.getDataObservable().subscribe((groups: IGroup) => {
        this.groupRows = groups;

      })
    );
    this.permissionService.query();
    this.subscription.push(
      this.permissionService.getDataObservable().subscribe((permision: IPermission) => {
        this.permissionRows = permision;
      })
    );
    this.permissionGroupService.query(this.editId);
    this.subscription.push(this.permissionGroupService.getDataObservable().subscribe((permission: IGroupPermission) => {
      this.permissionGroupDetail = permission;

      this.formGroup.controls.groupId.setValue(permission.data![0].groupId);
      this.formGroup.controls.permissionId.setValue(permission.data![0].permissionId);
    }));

    this.permissionGroupDetail.data![0].actions.split("-").forEach(value => {

      if (value === "get")
        this.isGet = true;
      else if (value === "post")
        this.isPost = true;

      else if (value === "put")
        this.isPut = true;

      else if (value === "delete")
        this.isDelete = true;

    });
  }

  onSubmit(): void {


    if (this.formGroup.invalid) {
      return;
    }

    this.submitted = true;
    const actions: FormArray = this.formGroup.get('actions') as FormArray;
    let combineAction = '';

    actions.controls.forEach(ctl => combineAction += ctl.value);

    const permissionGroup = new GroupPermission({
      id: this.editId,
      groupId: this.formGroup.value.groupId,
      permissionId: this.formGroup.value.permissionId,
      actions: combineAction,

    });

    this.permissionGroupService.clearAlert();
    this.permissionGroupService.update(permissionGroup);

  }

  ngOnDestroy(): void {

    this.subscription.forEach(sub => sub.unsubscribe());
    this.permissionGroupService.unsubscribe();


  }

  onCheckboxChange(e: any) {

    const actions: FormArray = this.formGroup.get('actions') as FormArray;

    if (!this.isCheck) {
      this.isCheck = true;
      const actBox = this.permissionGroupDetail.data![0].actions.split("-");
      actBox.forEach(value => {
        if (value)
          actions.push(new FormControl("-" + value));

      });
    }
    if (e.target.checked) {

      const index = actions.controls.findIndex(x => x.value === e.target.value);
      if (index == -1)
        actions.push(new FormControl(e.target.value));
    } else {

      const index = actions.controls.findIndex(x => x.value === e.target.value);

      actions.removeAt(index);
    }


  }

}




<form novalidate (ngSubmit)="onSubmit()" [formGroup]="formGroup">


  <div class="form-group">
    <app-alert></app-alert>
  </div>
  <div class="form-group">
    <div class="input-group">
      <div class="input-group-addon">{{ 'filed.group' | translate }}</div>
      <select

        id="groupId"
        name="groupId"
        class="form-control"
        formControlName="groupId"
        required
        [ngClass]="{ 'is-invalid': formGroup.controls.groupId.invalid  && formGroup.controls.groupId.touched }"

      >
        <option disabled selected value>{{ 'common.selectInputMessage' | translate }}</option>
        <option *ngFor="let group of this. permissionRows?.data; let i=index" value="{{group.id}}">{{group.name}}</option>

      </select>
      <div class="input-group-addon">
        <fa-icon  [icon]="faIcon.faAsterisk" ></fa-icon>
      </div>

      <div
        *ngIf=" !submitted &&formGroup.controls.groupId.invalid ||formGroup.controls.groupId.touched"
        class="invalid-feedback">
        <div *ngIf="formGroup.controls.groupId.hasError('required')" class="pull-right"> {{ 'common.required' | translate }}
        </div>

      </div>
    </div>
  </div>

  <div class="form-group">
    <div class="input-group">
      <div class="input-group-addon">{{ 'filed.permissionId' | translate }}</div>
      <select

        id="permissionId"
        name="permissionId"
        class="form-control"
        formControlName="permissionId"
        required
        [ngClass]="{ 'is-invalid': formGroup.controls.permissionId.invalid  && formGroup.controls.permissionId.touched }"

      >
        <option disabled selected value>{{ 'common.selectInputMessage' | translate }}</option>
        <option *ngFor="let group of this.groupRows?.data; let i=index" value="{{group.id}}">{{group.name}}</option>

      </select>
      <div class="input-group-addon">
        <fa-icon  [icon]="faIcon.faAsterisk" ></fa-icon>
      </div>

      <div
        *ngIf=" !submitted &&formGroup.controls.permissionId.invalid ||formGroup.controls.permissionId.touched"
        class="invalid-feedback">
        <div *ngIf="formGroup.controls.permissionId.hasError('required')" class="pull-right"> {{ 'common.required' | translate }}
        </div>

      </div>
    </div>
  </div>

  <div class="form-group">
    <div class="input-group">
      <div class="input-group-addon">{{ 'filed.actions' | translate }}</div>

      <div class="form-control form-check-inline form-check" style="border: none">
        <!--        <label *ngFor="let act of this.actionsArray; let i = index"-->
        <!--               for="inline-checkbox1"-->
        <!--               class="switch switch-text switch-primary switch-pill m-r-10 m-l-20">-->
        <!--          <input-->
        <!--            (change)="onCheckboxChange($event)"-->
        <!--            type="checkbox"-->
        <!--            id="inline-checkbox1"-->
        <!--            value="{{act.value}}"-->
        <!--            class="form-check-input witch-input">{{ act.name}}-->

        <!--        </label>-->

        <label style="  transform: scale(1)" class="switch switch-text switch-primary switch-pill lg m-r-10 m-l-20">
          <input  value="-get" (change)="onCheckboxChange($event)" type="checkbox" class="switch-input " [checked]="isGet" >
          <span data-on="Get" data-off="Get" class="switch-label"></span>
          <span class="switch-handle"></span>
        </label>



        <label style="  transform: scale(1)" class="switch switch-text switch-success switch-pill m-r-5 m-l-5">
          <input value="-post" (change)="onCheckboxChange($event)" type="checkbox" class="switch-input" [checked]="isPost" >
          <span data-on="Pst" data-off="Pst" class="switch-label"></span>
          <span class="switch-handle"></span>
        </label>

        <label style="  transform: scale(1)"  class="switch switch-text switch-warning switch-pill m-r-5 m-l-5">
          <input value="-put" (change)="onCheckboxChange($event)" type="checkbox" class="switch-input" [checked]="isPut" >
          <span data-on="Put" data-off="Put" class="switch-label"></span>
          <span class="switch-handle"></span>
        </label>
        <label  style="  transform: scale(1)" class="switch switch-text switch-danger switch-pill  m-l-5">
          <input value="-delete" (change)="onCheckboxChange($event)" type="checkbox" class="switch-input"  [checked]="isDelete" >
          <span style="font-size: 10px !important;" data-on="Del" data-off="Del" class="switch-label"></span>
          <span class="switch-handle"></span>
        </label>





      </div>
      <div class="input-group-addon">
        <fa-icon [icon]="faIcon.faAsterisk"></fa-icon>
      </div>

    </div>
  </div>


  <div class="form-actions form-group ">
    <button  [disabled]="formGroup.invalid"  type="submit" class="btn btn-primary btn-sm">{{ 'common.submit' | translate }}</button>
  </div>

</form>



Upvotes: 0

Related Questions