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.

Mahdi Zarei
  • 5,644
  • 7
  • 24
  • 54
ganesh
  • 416
  • 1
  • 11
  • 32
  • Can you please create a minimal reproducible example to check the issue using stackblitz? – Vimal Patel Dec 10 '21 at 16:22
  • @VimalPatel I have updated question. pls check – ganesh Dec 10 '21 at 17:30
  • Hint, you should never edit away your code from the question, because you need to provide a [mcve] 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. – AT82 Dec 10 '21 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. – ganesh Dec 10 '21 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. – AT82 Dec 10 '21 at 18:04
  • textbox is not required. It can have value/empty with checkbox checked. but if textbox value found and checkbox is uncheck then that should not allowed. – ganesh Dec 10 '21 at 18:13
  • Well, anyways, a form would suit as you have this requirement :) – AT82 Dec 10 '21 at 18:43

3 Answers3

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.

AT82
  • 71,416
  • 24
  • 140
  • 167
  • could you please let me know, what is `...` at the start of `merge`? Its in your demo as well. – ganesh Dec 13 '21 at 09:23
  • It's the spread operator: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax So we pull the objects (FormGroups) from the formarray :) – AT82 Dec 13 '21 at 09:35
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

akotech
  • 1,961
  • 2
  • 4
  • 10
0

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

demo

Mahdi Zarei
  • 5,644
  • 7
  • 24
  • 54