Reusable Angular: Create multiple checkboxes component

Vladislav Guleaev
5 min readJul 12, 2018

--

Let’s build a reusable Angular component to handle multiple checkboxes from related logical group.

Imagine that we need to solve a problem. We are building an edit User form in Angular. User can have one or many following roles:

“Admin”, “Director”, “Professor”, “Student”.

To implement it we need to create a multi select possibility. Let’s assume we want to implement it by rendering a checkbox for every role. So in edit mode you can easy assign or remove a role by clicking on a checkbox.

First of all let’s create some parent component (page for a user form) that will contain your checkbox-group component.

export class AppComponent implements OnInit { private userRoles = [
{ id: 1, name: ‘Admin’ },
{ id: 2, name: ‘Director’ },
{ id: 3, name: ‘Professor’ },
{ id: 4, name: ‘Student’ }
];
public userModel = {
id: 0,
name: "",
roles: []
};
constructor() { } ngOnInit(): void { }}

As we can see I hardcoded the roles but in real scenario roles will be probably fetched from the database. So let’s add a simulation of a HTTP request by adding a delay.

userRolesOptions = new Array<any>();ngOnInit(): void {setTimeout(() => {
this.userRolesOptions = this.userRoles;
}, 1000); //here we add a 1 second delay
}

Now we start building our reusable checkbox group component.

We will need a following class CheckboxItem.ts

export class CheckboxItem {
value: string;
label: string;
checked: boolean;
constructor(value: any, label: any, checked?: boolean) {
this.value = value;
this.label = label;
this.checked = checked ? checked : false;
}
}

This class will be used by CheckboxGroupComponent to render all the options available and its checked state. Note that checked is an optional param so by default all the options are unchecked. Like we create a new user.

Now modify a little bit our backend fetch function to map the structure of response JSON data to Array<CheckboxItem>.

userRolesOptions = new Array<CheckboxItem>();ngOnInit(): void {setTimeout(() => {
this.userRolesOptions = this.userRoles.map(x => new CheckboxItem(x.id, x.name));
}, 1000);
}

No matter what structure your backend sends you. Any checkbox option will have an value and label . So in our case we map role “id” to “value” and “name” to “label”. Value is treated as a key or id, but label is simply user-friendly description string.

Next step is to create a CheckboxGroupComponent

@Component({
selector: 'checkbox-group',
templateUrl: './checkbox-group.component.html',
styleUrls: ['./checkbox-group.component.css']
})
export class CheckboxGroupComponent implements OnInit {
@Input() options = Array<CheckboxItem>();
constructor() { }ngOnInit() {}}

This was our typescript file. It has an @Input property array of CheckBoxItem that will be used to render checkbox by ngFor.

And its html template.

<div *ngFor=”let item of options”><input type=”checkbox” [(ngModel)]=”item.checked”>{{item.label}}</div>

Note that we use ngModel for binding every option checked property. But we remember that by default its false.

Last step is to add new created component to AppComponent template

// somewhere in AppComponent html template<checkbox-group [options]=”userRolesOptions”></checkbox-group>

The result should be something like this.

To get the selected roles we need to create and Output event that will send you an array of selected Ids e.g. [1,2,4].

In CheckboxGroupComponent template add change event binding to some function.

<div *ngFor=”let item of options”><input type=”checkbox” [(ngModel)]=”item.checked” (change)=”onToggle()”>{{item.label}}</div>

Time to implement the onToggle function in typescript file:

export class CheckboxGroupComponent implements OnInit {
@Input() options = Array<CheckboxItem>();
@Input() selectedValues: string[];
@Output() toggle = new EventEmitter<any[]>();
constructor() {} ngOnInit(){} onToggle() {
const checkedOptions = this.options.filter(x => x.checked);
this.selectedValues = checkedOptions.map(x => x.value);
this.toggle.emit(checkedOptions.map(x => x.value));
}
}

Subscribe to this function in AppComponent template:

<checkbox-group [options]=”userRolesOptions” (toggle)=”onRolesChange($event)”></checkbox-group>

And use the result in ts file:

export class CheckboxGroupComponent implements OnInit {
//...
onRolesChange(value) {
this.userModel.roles = value;
console.log('Model role:' , this.userModel.roles);
}
}

Now on every click you will get in console selected roles ids as array.

When I selected Admin and Professor, I got console log:
“Model roles: (2) [1, 3]”

Reusable component is almost finished. The last part is to support a case when you do edit user. So we need to fetch user current roles and initialize the checkboxes with roles already assigned.

We have 2 ways of doing this. One is to set the “checked” property in map function in AppComponent.

//AppComponent.tssetTimeout(() => {this.userRolesOptions = this.userRoles.map(x => new CheckboxItem(x.id, x.name, true)); // here}, 1000);

And the second is to provide an selectedValues array to CheckboxGroupComponent for initialization.

<checkbox-group [options]=”userRolesOptions” [selectedValues]=”userModel.roles” (toggle)=”onRolesChange($event)”></checkbox-group>

Lets imagine we fetched our user from the database with 3 roles:

//AppComponent.tspublic userModel = {
id: 1,
name: ‘Vlad’,
roles: [1,2,3]
};
constructor() { } //rest of the code

In CheckboxGroupComponent we set to all checkboxes “checked”property to true if value contains in selectedValues array.

export class CheckboxGroupComponent implements OnInit, OnChanges { ngOnChanges() {
this.selectedValues.forEach(value => {
const element = this.options.find(x => x.value === value);
if (element) {
element.checked = true;
}
});
}
}

This time we use ngOnChanges() not ngOnInit(). This is because at init time we still doesn't have a values retrieved from the backend. Remember 1 second delay? If we use ngOnInit() it just won’t work. Don’t forget to implement the OnChanges interface in CheckboxGroupComponent.

You should get following result:

I used Angular Material styles

It will be a second delay for checkbox to appear because of our imaginary backend call.

Note that you can get the selected result by subscribing to the event in AppComponent or simply to use “checked” property in items of userRolesOptions array. They are sync because of data binding.

const checkedOptions = this.userRolesOptions.filter(x => x.checked);

You can read the Part 2 article where I built the same behavior using Angular Forms.

Thank you for reading! I hope you enjoy building reusable components in Angular :)

--

--

Vladislav Guleaev

Fullstack Javascript Developer. Lives in Munich. Born in Moldova Republic.