Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to select all filtered checkboxes inside ngFor in Angular 2?

Tags:

angular

ngfor

I have an Angular 2 component which loads and shows a list of checkboxes to the user within *ngFor, the list can also be filtered based on a key. I need to select all the filtered items and add them to an array. I can check all the checkboxes, but the problem is when I change the checked problematically the change event does not fire, any idea how to fix this?

template:

<div class="form-group">
    <input type="text" class="form-control" id="stringKeyFilter" placeholder="Key Filter"
           [(ngModel)]="keyFilter">
</div>

<table class="table table-striped table-hover">
    <thead>
    <tr>
        <th>Id</th>
        <th style="width: 150px;">Action</th>
    </tr>
    </thead>
    <tbody>
    <tr *ngFor="let device of devices| stringFilter: keyFilter">
        <td>
            {{device.$key}}
        </td>
        <td>
            <div class="checkbox">
                <label> <input type="checkbox"
                               [checked]="selectAll || selectedDevices.indexOf(device.$key) > -1"
                               (change)="updateSelectedDevices(device.$key, $event)" > View</label>
            </div>
        </td>
    </tr>
    </tbody>
</table>

<div class="btn-group pull-right">
    <button type="button" class="btn btn-primary" (click)="selectAllDevices()">Select All</button>
    <button type="button" class="btn btn-default" (click)="deselectAllDevices()">Deselect All
    </button>
</div>

Component:

@Component({
    moduleId: module.id,
    selector: 'device-list',
    template: template
})
export class DeviceListComponent implements OnInit {
    devicesObservable: FirebaseListObservable<Device[]>;
    devices: Device[] = [];
    isLoading: boolean = true;
    selectedDevices: string[] = [];
    selectAll: boolean = false;
    allCtrl: any;

    keyFilter: string = '';

    constructor(private af: AngularFire) {

    }

    ngOnInit(): void {


        this.devicesObservable = this.af.database.list('/online', {
            query: {
                orderByKey: true,
            }
        });
        this.devicesObservable.subscribe((devicesData)=> {
            this.devices = devicesData;
            this.isLoading = false
        });
    }

    updateSelectedDevices(deviceId: string, event): void {
        if (event.target.checked) {
            this.selectedDevices.push(deviceId);
        }
        else {
            _.pull(this.selectedDevices, deviceId);
        }
    }


    loadingDeviceDetail(loading: boolean): void {
        this.isLoading = loading;
    }

    selectAllDevices(): void {
        this.selectAll = true;
    }

    deselectAllDevices(): void {
        this.selectAll = false;
        this.selectedDevices = [];
    }

}

enter image description here

enter image description here

like image 437
ePezhman Avatar asked Sep 26 '16 12:09

ePezhman


3 Answers

Update

Updated sample code and plunker to demo select/deselect all checkbox for filtered list.


According to my understanding, the filter behavior changed from Angular1 to Angular2.

In Angular1, when data or filter parameter is modified, the DOM (webpage) will be recalculated. (I maybe wrong on this, too long ago :P)

In Angular2, in short, modifying the data(array) will not trigger filter calculation, so no new result, no update to DOM.

The long and official explanation is here: Angular2 Pipes - ts. If you insist on using pipe(filter) within template, you should explore the impure pipe section in the document.

Another solution is to not use filter with *ngFor. Create a filtered list instead.

Plunker

http://plnkr.co/edit/pEpaj0?p=preview

Sample Code

app.component.ts

import {Component} from '@angular/core';
import {bootstrap} from '@angular/platform-browser-dynamic';

@Component({
  selector: 'material-app',
  templateUrl: 'app.component.html'
})
export class AppComponent {

  title='How to select all filtered checkboxes inside ngFor in Angular 2?';
  url='http://stackoverflow.com/questions/39703103/';

  device = [
    {'name':'abc','checked':false},
    {'name':'abcd','checked':false},
    {'name':'abcde','checked':false},
    {'name':'abc123','checked':false},
    {'name':'abc1234','checked':false}];

  deviceFilter = '';

  deviceFiltered = this.device.slice(0);

  selectFiltered(){
    this.deviceFiltered.forEach(i=>i.checked=true);
  }

  deSelectFiltered(){
    this.deviceFiltered.forEach(i=>i.checked=false);
  }

  onFilterChange(){
    if (this.deviceFilter.length > 0) {
      this.deviceFiltered = this.device.filter(i => i.name.indexOf(this.deviceFilter) > -1);
      console.log(this.deviceFiltered);
    } else {
      this.deviceFiltered = this.device.slice(0);
    }
  }
}

app.component.html

<h2><a [href]="titleUrl">{{title}}</a></h2>

<div>
  Filter
  <input [(ngModel)]="deviceFilter" (ngModelChange)="onFilterChange()">
</div>

<div>
  List:
  <ul>
    <li *ngFor="let i of device">
      <input type="checkbox" [(ngModel)]="i.checked">{{i.name}}
    </li>
  </ul>
</div>

<div>
  Filtered List:
  <ul>
    <li *ngFor="let i of deviceFiltered">
      <input type="checkbox" [(ngModel)]="i.checked">{{i.name}}
    </li>
  </ul>
  <button (click)="selectFiltered()">Select Filtered</button>
  <button (click)="deSelectFiltered()">Deselect Filtered</button>
</div>
like image 188
John Siu Avatar answered Oct 17 '22 07:10

John Siu


When you try to select all the devices, your main list, is not updated, nothing has changed and angular doesn't detect changes on it. (There is an updated flag, but the list is the same). You have two options:

  1. Put a flag on every checked element on the list.
  2. Trigger a manual render with ApplicationRef.tick() https://angular.io/docs/ts/latest/api/core/index/ApplicationRef-class.html#!#tick-anchor
like image 31
jordic Avatar answered Oct 17 '22 08:10

jordic


Note: Your code may vary as i made some changes to get a working app only. Please get the related part from the code snippets and apply.

Here is the working code for your question.

Below is the changes you need to make into your code.

<input type="checkbox"  [checked]="selectedDevices.indexOf(device.$key) > -1"
                               (change)="updateSelectedDevices(device.$key, $event)" >

Change your select all function as the following.

selectAllDevices(): void {
    this.selectedDevices = this.strFilter(this.devices, this.keyFilter).map(dev=>dev.$key); 
  }

Note: this.strFilter is the filter function in component class/function. This depends on your Filter class. I have create filter/pipe like this.

//stringfilter.pipe.ts

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({name: 'stringFilter'})
export class StringFilter implements PipeTransform {
  transform(value: Array<any>, filterKey: string): Array<any> {
    return (
      filterKey ? value.
        filter(el => (el.name).toLowerCase().includes(filterKey))
        : value
    );
  }
}

In the component class i did like //component class

import {StringFilter} from './filter-file'

And

ngOnInit(): void {

    this.strFilter = new StringFilter().transform;
    //this.devices = [];
    this.isLoading = false;

  }

I have tested following code. Hope this solves your problem.

like image 1
lokeshjain2008 Avatar answered Oct 17 '22 07:10

lokeshjain2008