Если мы хотим, мы можем создать пользовательский элемент управления Form.
В этом случае нам нужны в качестве входных данных источник и столбцы источника - первым будет ключ, а вторым - текст, который появляется.
Я делаю стек-блиц
.html будет
<check-box-group name="props" [(ngModel)]="person.props"
[source]="dynamicData" cols="value,name" >
</check-box-group>
компонент это типичный пользовательский элемент управления
@Component({
selector: 'check-box-group',
template: `
<div class="form-check" *ngFor="let item of source;let i=index">
<input class="form-check-input" id="{{_name+''+i}}"
type="checkBox" [ngModel]="_selectedItems[i]"
(ngModelChange)="setValue($event,i)">
<label class="form-check-label" for="{{_name+''+i}}">
{{item[_col]}}
</label>
</div>
`,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CheckBoxGroupComponent),
multi: true
}
]
})
export class CheckBoxGroupComponent implements ControlValueAccessor {
@Input() source;
@Input()
set cols(value:string){ //cols is a string separated by commas
//e.g. "value,text", the "key" will be "value" and show the text
let _cols=value.split(',')
this._key = _cols[0];
this._col = _cols[1]
}
_selectedItems: any[] = [];
_key: string;
_col: string;
_name:string="";
onChange;
onTouched;
constructor(el:ElementRef) {
let name=el.nativeElement.getAttribute('name')
this._name=name?name:"ck";
}
writeValue(value: any[]): void {
this._selectedItems = this.propsToBoolean(value);
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean): void {
}
setValue(value: boolean, index: number) {
this._selectedItems[index] = value;
this.onChange(this.booleanToProps(this._selectedItems));
}
propsToBoolean(props): any[] {
console.log(props);
return props ? this.source.map((x: any) => props.indexOf(x[this._key]) >= 0)
: this.source.map(x => false);
}
booleanToProps(propsBoolean: boolean[]) {
let props: any[] = [];
if (propsBoolean) {
propsBoolean.forEach((item, index) => {
if (item)
props.push(this.source[index][this._key])
})
}
return props;
}
}
Обновление: добавить проверки
когда у нас есть пользовательский компонент формы и мы хотим сделать «проверку», у нас есть два варианта: выполнить проверку вне компонента или выполнить проверку внутри компонента. Для второго варианта мы должны добавить в качестве провайдера: NG_VALIDATORS ,,
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => CheckBoxGroupComponent),
multi: true,
}
И добавить функцию проверки
validate(control: AbstractControl): ValidationErrors | null{
...your logic here.., e.g.
if (!this._selectedItems.find(x=>x))
return {error:"you must select one option at last"}
return null
}
Ну, есть еще кое-что, что мы должны сделать, это решить, когда коснуться нашего пользовательского элемента управления. Помните, что элемент управления трогается при потере фокуса после его получения. мы можем сделать это (размытие) нашего флажка (или заключить элемент управления в div с tabindex = 0)
<input type="checkbox" .... (blur)="onTouched()">
Последний шаг - сделать выдавать ошибку или нет, мы добавляем атрибут к элементу управления. Мне нравится, что если мы добавим атрибут isRequired, проверьте ошибку, иначе нет. Таким образом, мы добавляем новое свойство _isRequired и, в проверке конструктора, имеет ли атрибут
constructor(el:ElementRef) {
let name=el.nativeElement.getAttribute('name');
this._isRequired=el.nativeElement.getAttribute('isRequired')!=null?true:false;
this._name=name?name:"ck"; //<--this is necesary for give value to
//for="..." in label
}
И наша проверка учитывает это
validate(control: AbstractControl): ValidationErrors | null{
if (!this._isRequired)
return null;
....
}
ПРИМЕЧАНИЕ. Я обновил пользовательский элемент управления (и мы добавили свойство [customClass])