Search code examples
angularionic-frameworkangular-reactive-formsangular-formsion-checkbox

ion-checkbox doesn't check properly


I am new to angular/ionic

I have parent checkbox, on click of it, I want to enable tick of all its child and vice versa

Here is my code i have tired but its not working:

home.html

<form [formGroup]="form">
<ion-item>
    <ion-checkbox slot="start" formControlName="AllBulkhead" (ionChange)="checkBoxAllLongiClick($event)">
    </ion-checkbox>
    <ion-label>All longitudinal bulkheads</ion-label>
  </ion-item>
  <div class="subCheckbox">
    <ion-item>
      <ion-checkbox slot="start" formControlName="longiBulkhead" (ionChange)="checkBoxLongiClick($event)">
      </ion-checkbox>
      <ion-label>Longitudinal bulkheads</ion-label>
    </ion-item>
    <ion-item>
      <ion-checkbox slot="start" formControlName="outerBulkhead" (ionChange)="checkBoxOuterLongiClick($event)">
      </ion-checkbox>
      <ion-label>Outer longitudinal bulkheads</ion-label>
    </ion-item>
    <ion-item>
      <ion-checkbox slot="start" formControlName="innerBulkhead" (ionChange)="checkBoxInnerLongiClick($event)">
      </ion-checkbox>
      <ion-label>Inner longitudinal bulkheads</ion-label>
    </ion-item>
  </div>
</form>

home.ts

constructor(private _fb: FormBuilder){
this.form = this._fb.group({
      AllBulkhead: false,
      longiBulkhead: false,
      outerBulkhead: false,
      innerBulkhead: false,
});
}

checkBoxAllLongiClick(e) {
    if (e.currentTarget.checked) {
      this.form.controls['longiBulkhead'].patchValue(true);
      this.form.controls['outerBulkhead'].patchValue(true);
      this.form.controls['innerBulkhead'].patchValue(true);
    }
    else {
      this.form.controls['longiBulkhead'].patchValue(false);
      this.form.controls['outerBulkhead'].patchValue(false);
      this.form.controls['innerBulkhead'].patchValue(false);
    }
  }

  checkBoxLongiClick(e){
    this.checkBoxSubLongiClick();
  }

  checkBoxOuterLongiClick(e){
    this.checkBoxSubLongiClick();
  }

  checkBoxInnerLongiClick(e){
    this.checkBoxSubLongiClick();
  }

  checkBoxSubLongiClick() {
    if (this.form.get('longiBulkhead').value &&
      this.form.get('outerBulkhead').value &&
      this.form.get('innerBulkhead').value) {
      this.form.controls['AllBulkhead'].patchValue(true);
    } else {
      this.form.controls['AllBulkhead'].patchValue(false);
    }
  }

What i want to do is when i click on AllBulkhead checkbox, I want to check/uncheck all 3 of its child check-boxes i.e longiBulkhead,outerBulkhead,innerBulkhead

below is my code is giving unexpected behavior when i untick any of 3 child checkboxes so it is not un-cheking the parent checkbox i.e AllBulkhead checkbox

Where i am making mistake can any one help me ? Thank you in advance!


Solution

  • I think the problem is that changing the "all" option also triggers a change in the other options, which at the same time tries to change the "all" option again. It's not very easy to notice this, but if you add a few console.log('...'); you'll find that the method that updates the state of the options is called a few more times than what you'd expect.

    A better way to handle this would be to leave the "all" option outside of the form. The reason behind this is that the "all" option is not really an option, but a side effect based on the state of the other options. And by leaving it outside of the form, we can control when and how it should be updated without having any issues related to the change on an option triggering the change on another option behind the scenes.

    Please also notice that as of Ionic 4.1.0 the ion-checkbox component has an indeterminate state that may be useful for scenarios like this one. It's a common UI/UX practice that if you select only some of several options (but not all of them) instead of showing the "all" option as unchecked, it's shown in an indeterminate state.

    Anyway, please take a look at this working Stackblitz demo:

    Stackblitz demo

    Like you can see in the demo, I'm keeping the state of the "all" option outside of the form:

      public form: FormGroup;
    
      public allOptionStateChecked = false;
      public allOptionStateIndeterminate = false;
    
      constructor(private formBuilder: FormBuilder) {}
    
      ngOnInit() {
        this.form = this.formBuilder.group({
          option1: false,
          option2: false,
          option3: false
        });
      }
    

    And I'm setting its state manually using those properties (by also using the click handler instead of ionChange):

    <ion-item (click)="onAllChange()" lines="none">
      <ion-checkbox 
        slot="start" 
        [checked]="allOptionStateChecked"
        [indeterminate]="allOptionStateIndeterminate">
      </ion-checkbox>
      <ion-label>All</ion-label>
    </ion-item>
    

    I also have some helper methods that tell me if all or some of the options are checked:

      private allChecked(): boolean {
        const option1Value = this.form.get("option1").value;
        const option2Value = this.form.get("option2").value;
        const option3Value = this.form.get("option3").value;
    
        return option1Value && option2Value && option3Value;
      }
    
      private someChecked(): boolean {
        const option1Value = this.form.get("option1").value;
        const option2Value = this.form.get("option2").value;
        const option3Value = this.form.get("option3").value;
    
        return !this.allChecked() && (option1Value || option2Value || option3Value);
      }
    

    And with all that in place, there are only two things to be done:

    1. Update the "all" option state when any of the other options are changed
    2. Update the rest of the options when clicking the "all" option.
      public onAllChange(): void {
        const nextValue = this.allChecked() ? false : true;
    
        this.form.get("option1").patchValue(nextValue);
        this.form.get("option2").patchValue(nextValue);
        this.form.get("option3").patchValue(nextValue);
      }
    
      public onOptionChange(): void {
        this.allOptionStateChecked = this.allChecked();
        this.allOptionStateIndeterminate = this.someChecked();
      }
    

    Please notice that I don't update allOptionStateChecked and allOptionStateIndeterminate in the onAllChange() method. Since the state of the "all" option is a side effect based on the state of the other options, its state is updated in the onOptionChange() instead.

    EDIT

    Based on your comment I've updated the Stackblitz demo to also include the "all" option as part of the form but still, the difference with your code is that I manually set the state of the "all" option instead of binding it to the view using something like formControlName="all":

      ...
    
      ngOnInit() {
        this.form = this.formBuilder.group({
          all: false, // <-- added this to the form
          option1: false,
          option2: false,
          option3: false
        });
      }
    
      ...
    
      public onOptionChange(): void {
        this.allOptionStateChecked = this.allChecked();
        this.allOptionStateIndeterminate = this.someChecked();
    
        // Added this line at the end of the method!
        this.form.get("all").patchValue(this.allOptionStateChecked);
      }