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 = [];
}
}
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.
http://plnkr.co/edit/pEpaj0?p=preview
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>
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:
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With