2

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 class="container">
  <div class="text-center mt-5">
    <div class="row">
      <div class="col-md-6">
        <ul class="list-group">
          <li class="list-group-item">
            <input
              type="checkbox"
              [(ngModel)]="masterSelected"
              name="list_name"
              value="m1"
              (change)="checkUncheckAll()"
            />
            <strong>Select/ Unselect All</strong>
          </li>
        </ul>
        <ul class="list-group">
          <li class="list-group-item" *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 class="col-md-6">
        <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.

7
  • Can you please create a minimal reproducible example to check the issue using stackblitz? Commented Dec 10, 2021 at 16:22
  • @VimalPatel I have updated question. pls check Commented Dec 10, 2021 at 17:30
  • Hint, you should never edit away your code from the question, because you need to provide a minimal reproducible example as code blocks, sharing a Stackblitz is great, but that is just an added bonus. What if the link dies (and it will at some point...) This question becomes useless when it does ;) Also that is not the editable link. Commented Dec 10, 2021 at 17:54
  • @AT82 Sorry, this is my first time using stackblitz. Hope edited question is having information needed. please let me know if something I am missing. Commented Dec 10, 2021 at 18:01
  • No worries, great for showing code again :) A form would probably be a good approach, as you seem to want to have textbox field as required if checkbox is checked, so it would be a perfect scenario for a form. You can choose template driven or reactive. That would be my suggestion if you indeed have some conditions on required. Commented Dec 10, 2021 at 18:04

3 Answers 3

1

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.

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

2 Comments

could you please let me know, what is ... at the start of merge? Its in your demo as well.
It's the spread operator: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… So we pull the objects (FormGroups) from the formarray :)
0

Just add (input)="getCheckedItemList()" to your textarea.

demo

Comments

0

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

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.