📜 ⬆️ ⬇️

Angular: Create a multiple checkbox component that is suitable for reuse.

Let's create an Angular component to create a checkbox set from a specific logical group. The component will be written with reuse ideas. What does it mean? Let's give an example below:

Imagine that you have a task to make editing users. When editing, a form with all fields is usually opened. The user can have one or many roles from the list of "Adimin", "Director", "Professor", "Student".

To implement the multiple choice of roles, it was decided to draw one checkbox for each role on the form. Putting checkmarks or removing user roles will change.

To begin with, let's create a parent component (that is, a page for the form) which will already contain our 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 you can see here, I made a hardcode for all possible roles, but in a real application, roles will most likely be requested from the database. In order to simulate our HTTP to the server, I assigned the same values ​​to another variable with a slight delay.

 //app.component.ts userRolesOptions = new Array<any>(); ngOnInit(): void { setTimeout(() => { this.userRolesOptions = this.userRoles; }, 1000); //     1  } 

Now let's start creating a generic checkbox-group component that can be reused without any problems in all cases when a group from the checkbox is needed.

First we need such a 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; } } 

It will be used by the CheckboxComponent to render all possible choices (in our case, these are roles) and save from the state (selected or not). Note that the “checked” property is an optional parameter in the constructor and will default to false, that is, all values ​​will not be selected first. This is suitable when we create a new user without a single role.

Next, we will change our function simulative request to the server a bit, so as to make a mapping between the JSON response and Array
 userRolesOptions = new Array<CheckboxItem>(); ngOnInit(): void { setTimeout(() => { this.userRolesOptions = this.userRoles.map(x => new CheckboxItem(x.id, x.name)); }, 1000); } 

It does not matter which JSON structure the server will return to us. Each HTML checkbox always has a value and a description ( label ). In our case, we do the mapping "id" with "value" and "name" with "label". Value will be used as a key or id for an option, and label is just a string describing which the user is reading.

The next step is to create a CheckboxGroupComponent. He looks like this:

 @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 is not an Angular tutorial so I will not explain the specifics of the framework. Who needs can read in the official documentation.

The @Input property with the name options will contain a list of all possible values ​​that are not selected by default. Our HTML component template will render as many checkboxes as there are in this list.

This is the html for CheckboxGroupComponent:

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

Notice that I used ngModel to bind each “checked” item property from the options list.

The final step is to add our newly created component to the parent AppComponent template.

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

The result should be:

image

To get all the currently selected options, we will create an Output event which, on any click on one of the checkboxes, will return our list of selections. Like this: [1,2,4]

In the CheckboxGroupComponent template, we associate a change event with a new function.

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

It is time to implement this very function:

 export class CheckboxGroupComponent implements OnInit { @Input() options = Array<CheckboxItem>(); @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 event ( Output property called toggle ) in the AppComponent template.

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

And assign the return result (selected roles) to userModel.

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

Now on each click on the checkbox you will see in the console a list of selected roles. More precisely, their id. For example, if I chose the Admin and Professor roles, I would get “Model roles: (2) [1, 3]”.

The component is almost complete and ready for reuse. The last thing left to do is support the initialization of the checkbox group. This is needed in the case when we will be editing the user. Before that we need to make a request to the server to get the current list of user roles and initialize the CheckboxGroupComponent.

We have two ways to do this. The first is to use the CheckboxItem class constructor and use the optional parameter “checked”. In the place where we did the mapping.

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

The second way is to add another selectedValues list to initialize our component.

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

Let's imagine that we have already made a request for the current user and a model with three roles came from the database.

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

In the CheckboxGroupComponent, we initialize all the “checked” properties of each checkbox to true if the role id is in the selectedValues list.

 //CheckboxGroupComponent.ts ngOnInit() { this.selectedValues.forEach(value => { const element = this.options.find(x => x.value === value); if (element) { element.checked = true; } }); } 

As a result, you should get the following result:

image
Here I used styles from Angular Material

At startup, there will be a one second delay before Angular draws all the checkboxes on the page. This simulates the time spent loading roles from the database.

It is important to note that you can get all selected roles using an event subscription (toggle) or simply use the “checked” property in each item object from the userRolesOptions list that is in the parent component. This happens because the link to the list is passed through @Input (binding) and all changes inside the object will be synchronized.

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

Such a component can be styled as you like and used for any task where multi-select is needed.

Thank you for reading this article! I hope you enjoyed making the component Angular for reuse ideas.

PS: If the article will be popular, I will publish the second small part, where the same example is written using Angular Reactive Forms.

Source: https://habr.com/ru/post/417137/


All Articles