Angular Material 9/8 Check/ Uncheck Multi Checkbox List with Indeterminate State

In this Angular 9 tutorial, we’ll create a Multi checkbox list using the Material UI library’s mat-checkbox component. This checkbox list can be checked and unchecked by clicking a master checkbox which will also display indeterminate state.

Checking/ Unchecking a list of items with checkboxes is provided with a master checkbox to provide ease to users to control all list items. Angular Material Checkbox module MatCheckbox proves very handy in creating such lists with a number of properties.

In Material Checkbox module there are three states with Checked and Unchecked which are common, but there is also a third state called Indeterminate which is a middle of these two. Indeterminate Checkbox state indicates that some of the list items are checked but not all. It is indicated with a hyphen (-) in the checkbox.

Here we will create a list of items with a master checkbox using which a user can check to uncheck all item lists. Also if a user checks some of the items but not all, the master checkbox will show Indeterminate state which is very easy to implement.


Let’s get started!

# Setup Angular CLI

We’ll create Angula project in the latest version. Make sure you have updated the Angular CLI tool by running below npm command in the terminal

$ npm install -g @angular/cli

 

# Create an Angular Project

Execute below ng command to create an Angular project in latest version 9.1.3. But this tutorial is compatible with previous version 7,6,5 and 4

$ ng new angular-material-checkboxlist
$ cd angular-material-checkboxlist

# Install Angular Material in project

After version 8, Angular Material package can be installed by executing the following ng command. For configuration, it will ask a few questions related to the theme, browser animations etc

$ ng add @angular/material

Answer questions

? Choose a prebuilt theme name, or "custom" for a custom theme: Indigo/Pink
? Set up global Angular Material typography styles? Yes
? Set up browser animations for Angular Material? Yes

We are done with Angular material installation and configurations. Now our Angular project is ready to use Material components.

 

# Import Material Modules

The Material UI library provides a wide variety of components, so we need to import the API module of the component we are going to use in the App module.

Add Checkbox and Other Modules

To use a Material component we need to import it into the app’s module. Here for our Checkbox list, we will import some required modules.

<strong>MatCheckboxModule</strong> : Checkbox component module <strong><mat-checkbox></strong>
<strong>MatListModule</strong> : List item component module <strong><mat-list></strong>
<strong>MatCardModule</strong> : Card module, will wrap Checklist in a card <strong><mat-card></strong>
<strong>FormsModule</strong> : For Form controls like checkbox we use [(ngModel)] which needs FormsModule
Now open the app.module.ts file, then import above modules then add in the imports array as shown below:
// app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatListModule } from '@angular/material/list';
import { MatCardModule } from '@angular/material/card';
import { FormsModule } from '@angular/forms';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,

    MatCheckboxModule,
    MatListModule,
    MatCardModule,
    FormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
</pre>
<h4></h4>
<h3># Add Checkbox List in App Template</h3>
</div>
<div>In the <strong>app.component.html</strong> file add following template HTML</div>
<div></div>
<div>Here we have wrapped a list of checkboxes with a master checkbox component. This list is wrapped in a card component</div>
<div></div>
<div>
<pre><code class="language-markup"><div style="padding: 50px; width:400px;">

  <mat-card>

    <mat-card-title>Angular Material 8 Checkboxes</mat-card-title>

    <mat-card-content>

      <mat-list>

        <mat-list-item>
          <mat-checkbox
          [(ngModel)]="master_checked"
          [(indeterminate)]="master_indeterminate"
          (change)="master_change()"
          ><b>Check/ Uncheck All</b></mat-checkbox>
        </mat-list-item>

        <mat-list-item *ngFor="let item of checkbox_list">
          <mat-checkbox
          [(ngModel)]="item.checked"
          [disabled]="item.disabled"
          [labelPosition]="item.labelPosition"
          (change)="list_change()"
          >{{ item.name }}</mat-checkbox>
        </mat-list-item>

      </mat-list>

    </mat-card-content>
  </mat-card>
</div>

The <mat -list-item> component with the <mat-checkbox> checkbox control is iterating over items in <strong>checkbox_list</strong> object using <strong>*ngFor</strong> Angular directive

Let’s discuss some of the important properties of <mat-checkbox>
<strong>[disabled]</strong> : Boolean value to set state enable/disabled.
[checked]: Boolean value to change checked state of a checkbox.
[indeterminate]: Boolean value, when set to true it will show Indeterminate state with hyphen like checked sign.
<strong>[labelPosition]</strong> : Label text around checkbox can be set to ‘after‘ or ‘before
We have used Indeterminate state for a master check as its main significance is for main parent checkboxes for a list.

# Define Checklist Object and Methods in Component Class

For checkbox list we have defines an object with a set of keys of each checkbox with these properties name, checked, disabled, labelPosition values.
    this.checkbox_list = [
      {
        name: "HTML/CSS",
        disabled: false,
        checked: false,
        labelPosition: "after"
      }, {
        name: "JavaScript/jQuery",
        disabled: false,
        checked: false,
        labelPosition: "after"
      }, {
        name: "CSS Preprocessing",
        disabled: false,
        checked: false,
        labelPosition: "after"
      }, {
        name: "Version Control/Git",
        disabled: false,
        checked: false,
        labelPosition: "after"
      }, {
        name: "Responsive Design",
        disabled: false,
        checked: false,
        labelPosition: "after"
      }, {
        name: "Testing/Debugging",
        disabled: false,
        checked: false,
        labelPosition: "after"
      }, {
        name: "Browser Developer Tools",
        disabled: false,
        checked: false,
        labelPosition: "after"
      },
    ]
The (change) event listener on Master and checkbox list checkboxes are having logic to change the state.
The master_change() method on master checkbox is traversing over list items to check/ uncheck all items by checking its model state
  master_change() {
    for (let value of Object.values(this.checkbox_list)) {
      value.checked = this.master_checked;
    }
  }
The list_change() method on each checkbox change event is checking the following three conditions:
If some checkboxes are checked but not all; then set Indeterminate state of the master to true.
If checked count is equal to total items; then check the master checkbox and also set Indeterminate state to false.
If none of the checkboxes in the list is checked then uncheck master also set Indeterminate to false.
  list_change(){
    let checked_count = 0;
    //Get total checked items
    for (let value of Object.values(this.checkbox_list)) {
      if(value.checked)
      checked_count++;
    }

    if(checked_count>0 && checked_count<this.checkbox_list.length){
      // If some checkboxes are checked but not all; then set Indeterminate state of the master to true.
      this.master_indeterminate = true;
    }else if(checked_count == this.checkbox_list.length){
      //If checked count is equal to total items; then check the master checkbox and also set Indeterminate state to false.
      this.master_indeterminate = false;
      this.master_checked = true;
    }else{
      //If none of the checkboxes in the list is checked then uncheck master also set Indeterminate to false.
      this.master_indeterminate = false;
      this.master_checked = false;
    }
  }

 

The complete component class (app.component.ts) will look like this

//app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {

  master_checked: boolean = false;
  master_indeterminate: boolean = false;
  checkbox_list = [];

  constructor() {
    this.checkbox_list = [
      {
        name: "HTML/CSS",
        disabled: false,
        checked: false,
        labelPosition: "after"
      }, {
        name: "JavaScript/jQuery",
        disabled: false,
        checked: false,
        labelPosition: "after"
      }, {
        name: "CSS Preprocessing",
        disabled: false,
        checked: false,
        labelPosition: "after"
      }, {
        name: "Version Control/Git",
        disabled: false,
        checked: false,
        labelPosition: "after"
      }, {
        name: "Responsive Design",
        disabled: false,
        checked: false,
        labelPosition: "after"
      }, {
        name: "Testing/Debugging",
        disabled: false,
        checked: false,
        labelPosition: "after"
      }, {
        name: "Browser Developer Tools",
        disabled: false,
        checked: false,
        labelPosition: "after"
      },
    ]
  }

  master_change() {
    for (let value of Object.values(this.checkbox_list)) {
      value.checked = this.master_checked;
    }
  }

  list_change(){
    let checked_count = 0;
    //Get total checked items
    for (let value of Object.values(this.checkbox_list)) {
      if(value.checked)
      checked_count++;
    }

    if(checked_count>0 && checked_count<this.checkbox_list.length){
      // If some checkboxes are checked but not all; then set Indeterminate state of the master to true.
      this.master_indeterminate = true;
    }else if(checked_count == this.checkbox_list.length){
      //If checked count is equal to total items; then check the master checkbox and also set Indeterminate state to false.
      this.master_indeterminate = false;
      this.master_checked = true;
    }else{
      //If none of the checkboxes in the list is checked then uncheck master also set Indeterminate to false.
      this.master_indeterminate = false;
      this.master_checked = false;
    }
  }
}

 

Conclusion: Indeterminate state adds a good user experience to checkbox list which indicates the overall state of checked items. Angular material components are very easy to implement and use with a number of optional parameters available.

2 thoughts on “Angular Material 9/8 Check/ Uncheck Multi Checkbox List with Indeterminate State”

  1. Thank you for sharing knowledge related to this check box selection and how to handle its states. This article helps me to find a solution for my issue.
    But there is an issue with the logic you have mentioned here. That is when I select multiple rows without selecting all rows and try to be unchecked all rows by clicking header check box (master checkbox); at that time header check box (master checkbox) displayed as checked (display as selected).
    This is because when we select rows the value of master_checked is false (That is unchecked state) and when we click header check box (master checkbox), the value of master_checked is set to true. Because of that at that time master checkbox shows as checked.
    I could fix this issue by setting master_checked = true when only multiple rows but not all are selected.
    if(checked_count>0 && checked_count<this.checkbox_list.length){
         // If some checkboxes are checked but not all; then set Indeterminate state of the master to true.
         this.master_indeterminate = true;
         this.<strong>master_checked </strong>=<strong> </strong>true
    }

Leave a Comment

Your email address will not be published. Required fields are marked *