Super Simple Select Implementation Using Angular CDK Selection Model

Full stack web developer with main focus in Angular and NodeJs. Loves creating blogs, a side projects when I can find time.
Search for a command to run...

Full stack web developer with main focus in Angular and NodeJs. Loves creating blogs, a side projects when I can find time.
No comments yet. Be the first to comment.
People who are familiar with Figma would have noticed that the input fields support dragging to increase or decrease values. Instead of having to click on the input field first and then type the number in, the dragging feature is really handy as you ...

Show customizable markers on the calendar easily.

In the previous article, I shared how to set up Coolify and deploy a Node.js application on a VPS using Nixpacks. It is the easiest way to deploy applications using Coolify. We don't need any knowledge of Docker. Read Here: Deploy Node.js application...

Beginners guide on how to easily deploy a node application on a VPS.

Let's look at one way to architect Angular's highly customizable and dynamic card list component. The goal is to make it easier to render different kinds of card lists using one single component. Dynamic Card List Component The component should be dy...

Building out Selection feature for tables/list can be challenging at times, because of a lot of corner case that needs to be handled. Here's how we can build one without writing much logic using the Angular CDK's SelectionModel utility.

Angular CDK comes with a lot of utilities and components that we can use to build our application. One such very useful utility is the SelectionModel utility that is part of the Collections utilities in Angular Material CDK.
SelectionModel is a utility for powering the selection of one or more options from a list. This model is used in components such as the selection list, table selections, and chip lists.
The Selection Model class comes with a lot of handy methods that make it super easy to maintain the state of selections. Here's how we can use it:
const selectionModel = new SelectionModel<string>();
The class constructor takes in these arguments:
multiple - whether we can select multiple items at a time (multi-select)initiallySelectedValues - initial selectionemitChanges - Whether to emit changes (selectionModel.changed property)compareWith - A method that can hold the logic for identifying if an item is selected.Usage with all the arguments provided:
export interface Movie {
id: number;
name: string;
year: string;
}
const selectionModel = new SelectionModel<Movie>(
true, // <- multi-select
[], // <- Initial selections
true, // <- emit an event on selection change
(otherValue, value) => otherValue.id === value.id // <- compare method which checks the id of the movie
);
We can provide a custom compareWith method to get if a checkbox is selected (selectionModel.isSelected() method). Here's how it's used internally:
isSelected(value: T): boolean {
if (this.compareWith) {
for (const otherValue of this._selection) { // this._selection is a Set()
if (this.compareWith(otherValue, value)) {
return true;
}
}
return false;
}
return this._selection.has(value);
}
Now let's use the selection model to build a table with ability to select a row and also have a global checkbox to Select All/Deselect All.
<section>
<table>
<thead>
<tr>
<th>
<input type="checkbox"
[checked]="selectionModel.selected.length > 0"
[indeterminate]="selectionModel.selected.length > 0 &&
selectionModel.selected.length < tableData.length"
(change)="onCheckAllChange($event)" />
</th>
<th>Name</th>
<th>Year</th>
<th>Rating</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let row of tableData">
<td>
<input type="checkbox"
[checked]="selectionModel.isSelected(row.id)"
(change)="onCheckRowChange(row)" />
</td>
<td>{{ row.name }}</td>
<td>{{ row.year | date : 'short' }}</td>
<td>{{ row.rating | number }}</td>
</tr>
</tbody>
</table>
<footer>
<button type="button" (click)="toggleSelectAll()">
{{ isAllSelected ? 'Deselect All' : 'Select All' }}
</button>
</footer>
</section>
@Component({
selector: 'sreyaj-exp-cdk-selection',
template: `
<!-- see code above >
`,
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [NgForOf, DatePipe, DecimalPipe],
})
export class CdkSelectionComponent {
readonly tableData: MockData[];
readonly selectionModel: SelectionModel<number>;
constructor() {
this.tableData = MOCK_TABLE_DATA;
this.selectionModel = new SelectionModel<number>(true, []);
}
get isAllSelected(): boolean {
return this.selectionModel.selected.length === this.tableData.length;
}
public onCheckAllChange(event: Event): void {
const isChecked = (event.target as HTMLInputElement).checked;
if (isChecked) {
this.selectAll();
} else {
this.deselectAll();
}
}
public onCheckRowChange(row: MockData): void {
this.selectionModel.toggle(row.id);
}
public toggleSelectAll(): void {
if (this.isAllSelected) {
this.deselectAll();
} else {
this.selectAll();
}
}
private selectAll(): void {
this.selectionModel.select(...this.tableData.map((row) => row.id));
}
private deselectAll(): void {
this.selectionModel.clear();
}
}
Here I've used the id property to maintain the selection state, but we can use the full data also for the selection.
The way we check for all selection/intermediate state is:
<input type="checkbox"
[checked]="this.selectionModel.selected.length > 0"
[indeterminate]="this.selectionModel.selected.length > 0 &&
this.selectionModel.selected.length < this.tableData.length"
(change)="this.onCheckAllChange($event)" />
We mark checked by checking if at least one item is selected (by looking at the length of the selected items):
[checked]="this.selectionModel.selected.length > 0"
Next, we set the indeterminate state by checking if at least one is selected but not all.
[indeterminate]="this.selectionModel.selected.length > 0 &&
this.selectionModel.selected.length < this.tableData.length"
We can configure if the selection model should emit an event on change or not. This is specified using the emitChange argument in the constructor.
Here's what the event looks like:
/**
* export interface SelectionChange<T> {
* source: SelectionModel<T>;
* added: T[];
* removed: T[];
* }
* */
selectionModel.changed.subscribe((change: SelectionChange) => {
console.log(change);
})
We'll be able to get the list of items added and removed and the model itself in case we want to do more when the selection changes.
The good thing about the selection model is that it's not coupled to any component (it's completely headless). It can be used with lists and other similar components. A small but really good utility that allows us to concentrate on the actual business logic instead of things like this.
| Title | Link |
| Source Code | https://github.com/adisreyaj/sreyaj/blob/main/libs/examples/cdk-selection/src/lib/cdk-selection.component.ts |
| Demo | https://sreyaj.vercel.app/examples/cdk-selection |
| Angular CDK SelectionModel Docs | https://material.angular.io/cdk/collections/overview#selectionmodel |
| Angular CDK SelectionModel Code | https://github.com/angular/components/blob/main/src/cdk/collections/selection-model.ts |
Have any thoughts or questions? shoot'em below in the comments. Stay Safe ❤️