6

Im working on Angular 2.0 (Yes, a bit behind from current 2.4).

I have a checkbox list. Im trying to make that when the LAST CHECKBOX IS UNCHECKED all CHECKBOXES ARE CHECKED.

HTML

<div *ngFor="let filter of filters">
    <label htmlFor="check-box-{{filter.text}}"
           [ngClass]="(filter.selected)? 'active' : '' ">

           <input type="checkbox" 
                 name="check-box-{{filter.text}}"
                 [checked]="filter.selected"
                 (change)="onSelectFilter(filter)"
                 attr.id="check-box-{{filter.text}}">

           {{filter.selected}} -  ({{filter.counter}})

    </label>
</div>

TS

onSelectFilter(filter: Filter){
    filter.toggleSelection();


    let isAnyFilterSelected = this.filters.find(filter => filter.selected);

    // If no filter is selected then ALL filters are selected
    if(!isAnyFilterSelected){
        this.filters.forEach(filter => filter.selected = true );
    }

}

I create a plunker for it.

https://plnkr.co/edit/uzF6Lk5fxRZjXBOaS9ob?p=preview

If unchecked the only checkbox with CHECKED attribute TRUE, Im expecting that ALL checkboxes would have CHECKED attribute. This does not happen.

enter image description here

Any ideas?

kitimenpolku
  • 2,604
  • 4
  • 36
  • 51

1 Answers1

17

You should use ngModel instead of binding to checked, and use a setTimeout.

<div *ngFor="let filter of filters">
    <label htmlFor="check-box-{{filter.text}}"
           [ngClass]="(filter.selected)? 'active' : '' ">

           <input type="checkbox" 
                 name="check-box-{{filter.text}}"
                 [ngModel]="filter.selected"
                 (ngModelChange)="onSelectFilter($event,filter)"
                 attr.id="check-box-{{filter.text}}">

           {{filter.selected}} -  ({{filter.counter}})

    </label>
</div>
onSelectFilter(selected:boolean,filter: Filter){
    filter.selected=selected;
    if(this.filters.every(filter=>!filter.selected)){
      setTimeout(()=>{
        this.filters.forEach(filter=>filter.selected=true);
      });
    }
}

plunkr

"why such a fishy trick is needed ?"

Actually, because from change-detector's point of view, there is no change between the previous state and the new one.

So there is no need to update the @Input() of the child /call the writeValue() method of the ControlValueAccessor (<input type="checkbox" [ngModel]="foo">).

Using setTimeout, you first update the property to false, then delay its change back to initial state, allowing a new change-detection cycle.

Also note that events like (ngModelChange) are not correlated to the change-detection cycle.

Without setTimeout() :

Here, we will get the same result as in your example, while we keep foo being true, the checkbox won't update :

The code (plunkr):

@Component({
  selector: 'my-app',
  template: `
    <input id="foo" type="checkbox" [ngModel]="foo" (ngModelChange)="fooChange($event)"><label for="foo">{{foo}}</label>
  `,
})
export class App {
  filters:[];
  foo=true
  fooChange(newValue:boolean){
    if(newValue===false)
        this.foo=true; // if newValue is false, foo becomes true
      else
        this.foo = newValue; // otherwise, do change
  }
}

What is happening under the hood :

enter image description here

With setTimeout()

This time we will delay resetting the value to a next tick using setTimeout :

The code (plunkr):

@Component({
  selector: 'my-app',
  template: `
    <input id="foo" type="checkbox" [ngModel]="foo" (ngModelChange)="fooChange($event)"><label for="foo">{{foo}}</label>
  `,
})
export class App {
  filters:[];
  foo=true
  fooChange(newValue:boolean){
    this.foo=newValue; // we need to make it change !
    setTimeout(()=>{
      if(newValue===false)
        this.foo=true; // if newValue is false, foo becomes true
      else
        this.foo = newValue; // otherwise, do change  
    })
  }
}

What is happening under the hood :

enter image description here

n00dl3
  • 21,213
  • 7
  • 66
  • 76
  • Made those changes but still having same issue. I added a plunker which might make things easier to understand the situation Im dealing with. Thanks for the try anyhow. :) – kitimenpolku Apr 11 '17 at 07:51
  • Your problem is unclear, do you need to allow other checkbox to be checked/unchecked when last one is unchecked or is it exclusive? in other words is unchecking the last checkbox a shortcut to checking others, without any other constraint or the use shouldn't be allowed to use other ones while last is unchecked ? – n00dl3 Apr 11 '17 at 08:07
  • Sorry if Im not explaining myself properly. I updated explanation. In summary, I want that there is always at least one checkbox selected. If someone starts unchecking checkboxes, when there is only one remaining, unchecking that will make all checkboxes to be CHECKED. Thanks again for your time. – kitimenpolku Apr 11 '17 at 08:26
  • There used to be another comment saying about this timeout trick. Im waiting in case someone else can explain why such a fishy trick is needed... – kitimenpolku Apr 11 '17 at 09:48
  • That's just because it hasn't changed ! – n00dl3 Apr 11 '17 at 10:05