Давайте создадим Angular компонент для создания множества checkbox из определенной логической группы. Компонент будет написан с идей повторного использования. Что это значит? Приведем пример ниже:
Представьте что перед вами задача сделать редактирование пользователей. При редактвровании обычно открывается форма со всеми полями. Пользователь может иметь одну или множество ролей из списка «Adimin», «Director», «Professor», «Student».
Для реализации множественного выбора ролей было решено нарисовать на форме по одному checkbox для каждой роли. Ставя галочки или снимая роли пользователя будут меняться.
Для начало давайте создадим родительский компонент (то есть страница для формы) в котором уже будет содержаться наш checkbox-group компонент.
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 {
}
}
Как вы видите здесь я сделал хардкод всех возможных ролей, но в реальном приложении роли скорее всего будут запрошены из базы данных. Для того чтобы симулировать наш HTTP на сервер, я присвоил те же значения в другую переменную с небольшой задержкой.
//app.component.ts
userRolesOptions = new Array<any>();
ngOnInit(): void {
setTimeout(() => {
this.userRolesOptions = this.userRoles;
},
1000); //здесь мы добавляем задержку в 1 секунду
}
Теперь приступим к созданию обобщенного checkbox-group компонента, который можно будет без проблем повторно использовать во всех случаях когда нужна будет группа из checkbox.
Для начала нам понадобиться такой класс 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;
}
}
Он будет использоваться CheckboxComponent для рендера всех возможных вариантов выбора (в нашем случае это роли) и сохранения из состояния (выбран вариант или нет). Заметьте что свойство «checked» это необязательный параметр в конструкторе и по умолчанию будет иметь значение false, то есть все значения сперва будут не выбраны. Это подходит когда мы создаем нового пользователя без единой роли.
Далее изменим немного нашу функую симулрующее запрос на сервер, так чтобы сделать мапинг между JSON ответом и Array
userRolesOptions = new Array<CheckboxItem>();
ngOnInit(): void {
setTimeout(() => {
this.userRolesOptions = this.userRoles.map(x => new CheckboxItem(x.id, x.name));
}, 1000);
}
Не имеет значения какую JSON структуру вернет нам сервер. Каждый HTML checkbox всегда имеет значение (value) и описание (label). В нашем случае мы делает маппинг «id» с «value» и «name» с «label». Value будет использоваться как ключ или id для опции, а label просто строка с описанием которую читает пользователь.
Следующий шаг это создание 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() {}
}
Это не туториал Angular так что я не буду объяснять специфику фреймворка. Кому нужно может прочесть в официальной документации.
Свойство @Input с названием options будет содержать список всех возможных значений, которые по умолчанию не выбраны. Наш HTML шаблон компонента будет рендерить столько checkbox сколько содержится в этом списке.
Так выглядит html для CheckboxGroupComponent:
<div *ngFor=”let item of options”>
<input type=”checkbox” [(ngModel)]=”item.checked”>{{item.label}}
</div>
Заметьте что я использовал ngModel для связывания (binding) каждого «checked» свойства item из списка options.
Последний шаг это добавить наш новый созданный компонент в шаблон родительского AppComponent.
// somewhere in AppComponent html template
<checkbox-group [options]=”userRolesOptions”></checkbox-group>
Результат должен быть такой:
Чтобы получить все текущие выбранные опции мы создадим Output event который при любом клике на один из checkboxes вернет наш список выбранных. Примерно так: [1,2,4]
В CheckboxGroupComponent шаблоне свзяжем change event с новой функцией.
<div *ngFor=”let item of options”>
<input type=”checkbox” [(ngModel)]=”item.checked” (change)=”onToggle()”>{{item.label}}
</div>
Настало время реализовать эту самую функцию:
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));
}
}
Подпишемся на это событие (Output свойство с названием toggle) в шаблоне AppComponent.
<checkbox-group [options]=”userRolesOptions” (toggle)=”onRolesChange($event)”></checkbox-group>
И присвоем возвращаемый результат (выбранные роли) в userModel.
export class CheckboxGroupComponent implements OnInit {
//..остальной код
onRolesChange(value) {
this.userModel.roles = value;
console.log('Model role:' , this.userModel.roles);
}
}
Теперь на каждом клике на checkbox вы увидите в консле список выбранных ролей. Точнее их id. Например если я выбрал роль Admin и Professor, я получу “Model roles: (2) [1, 3]”.
Компонент почти завершен и готов для повторного использования. Последнее что осталось это сделать поддержку инициализации группы checkbox. Это понадобиться в случае когда мы будем делать редактирование пользователя. Перед этим нам понадобиться сделать запрос на сервер чтобы получить текущий список ролей пользователя и инициализировать CheckboxGroupComponent.
У нас есть два способа сделать это. Первый это использовать конструктор класса CheckboxItem и использовать опциональный параметр «checked». В том месте где мы делали маппинг.
//AppComponent.ts
setTimeout(() => {
this.userRolesOptions = this.userRoles.map(x => new CheckboxItem(x.id, x.name, true));
}, 1000);
// в таком случае все роли будут сразу же выбраны
Второй способ это добавить еще один список selectedValues для инициализации нашего компонента.
<checkbox-group [options]=”userRolesOptions” [selectedValues]=”userModel.roles” (toggle)=”onRolesChange($event)”></checkbox-group>
Давайте представим что мы уже сделали запрос для текущего юзера и из базы данных пришла модель с тремя ролями.
//AppComponent.ts
public userModel = {
id: 1,
name: ‘Vlad’,
roles: [1,2,3]
};
constructor() { }
//rest of the code
В CheckboxGroupComponent мы инициализируем все свойства «checked» каждого checkbox в значение true, если id роли содержится в списке selectedValues .
//CheckboxGroupComponent.ts
ngOnInit() {
this.selectedValues.forEach(value => {
const element = this.options.find(x => x.value === value);
if (element) {
element.checked = true;
}
});
}
В итоге должны получить следующий результат:
Тут я использовал стили от Angular Material
При запуске будет задержка в одну секунду перед тем как Angular нарисует все checkboxes на странице. Это имитирует время затраченное на загрузку ролей с базы данных.
Важно отметить что вы же можете получить все выбранные роли с помощью подписки на событие (toggle) или просто использовать свойство «checked» в каждом объекте item из списка userRolesOptions , который находиться в родительском компоненте. Это происходит потому что ссылка на список передается через @Input (binding) и все изменения внутри объекта будут синхронизированы.
const checkedOptions = this.userRolesOptions.filter(x => x.checked);
Такой компонент можно стилизовать как угодно и использовать для любой задачи где нужен multi-select.
Спасибо за то, что прочитали эту статью! Надеюсь вам понравилось делать компонент Angular для с идей повторного использования.
P.S.: Если статья будет пользоваться популярностью, я опубликую вторую небольшую часть, где этот же пример написан с использованием Angular Reactive Forms.
Автор: guleaevvlad