Home > Back-end >  Angular: store multiple checkbox values with related textarea value
Angular: store multiple checkbox values with related textarea value

Time:12-11

I have a form having mutiple checkbox and related textarea fields for taking comment. Please check demo here

When user selects checkbox first and then enter value in comment then comment value does not get store. If user enters comment value first and then checks the checkbox then value gets added.

textarea values are optional. If user checks checkbox then comment should get added. if checkbox is checked and then comment is entered then comment should get added. If comment is added first and then checkbox is checked then also comment should get added. If that checkbox is uncheck then that should get remove. The behavior should be same with select/unselect all option.

How should I do this?

Html

<div style="text-align:center">
  <h1>
    {{ title }}
  </h1>
</div>
<div >
  <div >
    <div >
      <div >
        <ul >
          <li >
            <input
              type="checkbox"
              [(ngModel)]="masterSelected"
              name="list_name"
              value="m1"
              (change)="checkUncheckAll()"
            />
            <strong>Select/ Unselect All</strong>
          </li>
        </ul>
        <ul >
          <li  *ngFor="let item of checklist">
            <input
              type="checkbox"
              [(ngModel)]="item.isSelected"
              name="list_name"
              value="{{ item.id }}"
              (change)="isAllSelected()"
            />
            <textarea [(ngModel)]="item.comment">{{ item.comment }}</textarea>
            {{ item.value }}
          </li>
        </ul>
      </div>
      <div >
        <code>{{ checkedList }}</code>
      </div>
    </div>
  </div>
</div>

TS

import { Component } from '@angular/core';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
  name = 'Angular';
  masterSelected: boolean;
  checklist: any;
  checkedList: any;

  constructor() {
    this.masterSelected = false;
    this.checklist = [
      { id: 1, value: 'Elenor Anderson', comment: '', isSelected: false },
      { id: 2, value: 'Caden Kunze', comment: 'test', isSelected: true },
      { id: 3, value: 'Ms. Hortense Zulauf', comment: '123', isSelected: true },
      { id: 4, value: 'Grady Reichert', comment: '', isSelected: false },
      { id: 5, value: 'Dejon Olson', comment: '', isSelected: false },
      { id: 6, value: 'Jamir Pfannerstill', comment: '', isSelected: false },
      { id: 7, value: 'Aracely Renner DVM', comment: '', isSelected: false },
      { id: 8, value: 'Genoveva Luettgen', comment: '', isSelected: false },
    ];
    this.getCheckedItemList();
  }

  // The master checkbox will check/ uncheck all items
  checkUncheckAll() {
    for (var i = 0; i < this.checklist.length; i  ) {
      this.checklist[i].isSelected = this.masterSelected;
    }
    this.getCheckedItemList();
  }

  // Check All Checkbox Checked
  isAllSelected() {
    this.masterSelected = this.checklist.every(function (item: any) {
      return item.isSelected == true;
    });
    this.getCheckedItemList();
  }

  // Get List of Checked Items
  getCheckedItemList() {
    this.checkedList = [];
    for (var i = 0; i < this.checklist.length; i  ) {
      if (this.checklist[i].isSelected)
        this.checkedList.push(this.checklist[i]);
    }
    this.checkedList = JSON.stringify(this.checkedList);
  }
}

Please help and guide.

CodePudding user response:

As I had some time I thought I would write up a possible solution for you... Mentioned in comment, I would suggest a form as you have checkbox required if textarea has value. Here is a reactive approach to that.

I have created a formgroup, you don't necessarily need it, you could also just use a formarray, without a parent form.

Anyways, in this case I declare a formgroup with a formarray where we will store our values, then we populate the formarray based on your array. We listen to changes to each textarea, and if it has a value, we set Validators.requiredTrue (requiredTrue is used for checkboxes), otherwise we clear it with clearValidators. Lastly we update the value and validity of the formcontrol with updateValueAndValidity.

We can use a separate formcontrol for the check / uncheck all checkbox, which I have named here as masterSwitch ;)

We listen to the changes of that field and update the values in the form array as well as clear any possible requiredTrue validators if the value is true.

So the explained above translates to the following TS code:

  myForm!: FormGroup;
  alive = true;

  // our unselect / select all field
  masterSwitch = new FormControl(false);

  checklist = [....];

  constructor(private fb: FormBuilder) {
    this.myForm = this.fb.group({
      checklist: this.fb.array([]),
    });
    // add values to formarray
    this.populate();

    // listen to all comment formcontrols and add / remove required validator
    merge(
      ...this.formChecklist.controls.map(
        (control: AbstractControl, index: number) =>
          control.get('comment').valueChanges.pipe(
            tap((value) => {
              const isSelected = control.get('isSelected');
              if (value.trim()) {
                isSelected.setValidators(Validators.requiredTrue);
              } else {
                isSelected.clearValidators();
              }
              isSelected.updateValueAndValidity();
            })
          )
      )
    )
      .pipe(takeWhile((_) => this.alive))
      .subscribe();

    // listen to the select / unselect all and toggle checkbox as well as handle validators
    this.masterSwitch.valueChanges
      .pipe(takeWhile((_) => this.alive))
      .subscribe((value: boolean) => {
        this.formChecklist.controls.forEach((ctrl: FormGroup) => {
          ctrl.patchValue({ isSelected: value });
          const isSelected = ctrl.get('isSelected');
          if (value) {
            isSelected.clearValidators();
          } else {
            if (ctrl.get('comment').value) {
              isSelected.addValidators(Validators.requiredTrue);
              isSelected.updateValueAndValidity();
            }
          }
        });
      });
  }

  get formChecklist() {
    return this.myForm.get('checklist') as FormArray;
  }

  populate() {
    this.checklist.forEach((x) => {
      this.formChecklist.push(
        this.fb.group({
          id: x.id,
          value: x.value,
          comment: x.comment,
          isSelected: x.isSelected,
        })
      );
    });
  }

  ngOnDestroy() {
    this.alive = false;
  }

In the template we just do the usual reactive form, with looping trough the formarray and showing the formcontrols, nothing special there:

<label>
  <input type="checkbox" [formControl]="masterSwitch" />
  Uncheck / Check all
</label>
<hr>
<form [formGroup]="myForm" (ngSubmit)="onSubmit()">
  <div formArrayName="checklist">
    <div *ngFor="let item of formChecklist.controls; let i = index">
      <div [formGroupName]="i">
        <label>
          <input type="checkbox" formControlName="isSelected" />
          {{ item.get('value').value }}
        </label>
        <textarea formControlName="comment"></textarea>
        <small *ngIf="item.get('isSelected').hasError('required')"
          >Checkbox Required!</small
        >
      </div>
    </div>
  </div>
  <button>Submit</button>
</form>

And when submitting the form you can just filter out the objects in the formarray that are not selected, I have not included that here as it is basic js filter :)

Finally... a STACKBLITZ for your reference.

CodePudding user response:

The two-way binding is working correctly and the comments changes are getting stored correctly in the object that you have in the property checklist

You don't see the changes printed in the app, because in the filtered property checkedList, you are stringifying the filtered array (I guess for printing it).

If you remove in the method getCheckedItemList() the line this.checkedList = JSON.stringify(this.checkedList);

And print the property in the template using the json pipe <code>{{ checkedList | json }}</code>, you'll see that the changes are getting stored correctly.

cheers

  • Related