I'm trying to wrap all the configuration needed to setup a multiselect(using checkboxes for each option) combo using ng-select and the controlValueAccessor
interface.
So far it kinda works, except that if I choose to select or unselect all of the items via the checkbox placed at the top of the list, only the values in the form are updated, not the selection in the control.
The control only works as expected when I select or unselect the items individually, then both the control and the form value are updated accordingly:
Here's the code for the reausable component:
import { Component, Input, OnInit, Self, ViewChild } from '@angular/core';
import { ControlValueAccessor, NgControl } from '@angular/forms';
@Component({
selector: 'app-multi-select',
templateUrl: './multi-select.component.html',
styleUrls: ['./multi-select.component.css']
})
export class MultiSelectComponent implements OnInit, ControlValueAccessor {
disabled: boolean;
selectedValues: any;
@Input() optionItems: any[];
@ViewChild('combo', { static: true }) combo;
constructor(@Self() public controlDir: NgControl) {
this.controlDir.valueAccessor = this;
}
ngOnInit(): void {
}
toggleCheckAll(values: any) {
if (values.currentTarget.checked) {
this.selectAllItems();
} else {
this.unselectAllItems();
}
}
onChange(event) {
debugger;
}
onTouched() {}
onSelectionChange(selectedItems) {
debugger;
if (Array.isArray(selectedItems)) {
const newList = selectedItems.map((x) => x.id);
this.selectedValues = [...newList]
this.onChange([...newList]);
}
this.onTouched();
}
writeValue(obj: any): void {
this.combo.select([...obj]);
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState?(isDisabled: boolean): void {
this.disabled = isDisabled;
}
private selectAllItems() {
const newList = this.optionItems.map((x) => x.id);
this.selectedValues = [...newList];
this.onChange([...newList]);
}
private unselectAllItems() {
this.selectedValues = [];
this.onChange([]);
}
}
<ng-select
#combo
[multiple]="true"
[items]="optionItems"
[closeOnSelect]="false"
(change)="onSelectionChange($event)"
(blur)="onTouched()"
placeholder="Select people"
bindLabel="name"
bindValue="id">
<ng-template ng-header-tmp let-items="items">
<input type="checkbox" (change)="toggleCheckAll($event)"/>
</ng-template>
<ng-template ng-multi-label-tmp let-items="items" let-clear="clear">
<div class="ng-value" *ngFor="let item of items | slice:0:2">
<span class="ng-value-label">{{item.name}}{{item.login}}</span>
<span class="ng-value-icon right" (click)="clear(item)" aria-hidden="true">×</span>
</div>
<div class="ng-value" *ngIf="items.length > 2">
<span class="ng-value-label">{{items.length - 2}} more...</span>
</div>
</ng-template>
<ng-template ng-option-tmp let-item="item" let-item$="item$" let-index="index">
<input id="item-{{index}}" type="checkbox" [checked]="item$.selected" /> {{item.name}}
</ng-template>
</ng-select>
BTW: I had to put a conditional in the onSelectionChange
to check if the argument passed in was an Array because, much to my surprise, the change
event is called even when I use the checkbox placed at the top of the list, not only when I select or unselect the individual options.
You can use ngModel directive to bind selected value to ng-select.
Try this:
<ng-select #combo [multiple]="true" [items]="optionItems" [closeOnSelect]="false" (change)="onSelectionChange($event)"
(blur)="onTouched()" placeholder="Select people" bindLabel="name" [ngModel]="selectedValues" bindValue="id">
<ng-template ng-header-tmp let-items="items">
<input type="checkbox" [ngModel]="checkAll" (change)="toggleCheckAll($event)"/>
</ng-template>
<ng-template ng-multi-label-tmp let-items="items" let-clear="clear">
<div class="ng-value" *ngFor="let item of items | slice:0:2">
<span class="ng-value-label">{{item.name}}{{item.login}}</span>
<span class="ng-value-icon right" (click)="clear(item)" aria-hidden="true">×</span>
</div>
<div class="ng-value" *ngIf="items.length > 2">
<span class="ng-value-label">{{items.length - 2}} more...</span>
</div>
</ng-template>
<ng-template ng-option-tmp let-item="item" let-item$="item$" let-index="index">
<input id="item-{{index}}" type="checkbox" [checked]="item$.selected" /> {{item.name}}
</ng-template>
</ng-select>
Then in your class set value to selectedValue like this:
component.ts
writeValue(obj: any): void {
this.selectedValues =[...obj];
}