0

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.

2 Answers 2

1
  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

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

2 Comments

It highlights : this.getContactFormGroup(i) object is possibly 'null' and says
Hi, updated my answer. As AbstractControl.get() returns value of AbstractControl or null type. This change this.getFormGroup(i).get('is_primary_contact_number')?.setValue(false); will fix the warning.
0
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>



1 Comment

Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.

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.