Angular 2 - Checkbox not kept in sync

2019-01-15 17:02发布

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?

1条回答
等我变得足够好
2楼-- · 2019-01-15 17:35

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

查看更多
登录 后发表回答