Angular ReactiveForms: создание массива значений флажков?
Учитывая список флажков, связанных с тем же formControlName
, как я могу создать массив значений флажка, привязанных к formControl
, а не просто true
/false
?
Пример:
<form [formGroup]="checkboxGroup">
<input type="checkbox" id="checkbox-1" value="value-1" formControlName="myValues" />
<input type="checkbox" id="checkbox-2" value="value-2" formControlName="myValues" />
<input type="checkbox" id="checkbox-3" value="value-2" formControlName="myValues" />
</form>
checkboxGroup.controls['myValues'].value
в настоящее время производит:
true or false
Что я хочу:
['value-1', 'value-2', ...]
Ответы
Ответ 1
С помощью ответа silentsod я написал решение для получения значений вместо состояний в моем formBuilder.
Я использую метод для добавления или удаления значений в formArray. Это может быть плохой подход, но он работает!
component.html
<div *ngFor="let choice of checks; let i=index" class="col-md-2">
<label>
<input type="checkbox" [value]="choice.value" (change)="onCheckChange($event)">
{{choice.description}}
</label>
</div>
component.ts
// For example, an array of choices
public checks: Array<ChoiceClass> = [
{description: 'descr1', value: 'value1'},
{description: "descr2", value: 'value2'},
{description: "descr3", value: 'value3'}
];
initModelForm(): FormGroup{
return this._fb.group({
otherControls: [''],
// The formArray, empty
myChoices: new FormArray([]),
}
}
onCheckChange(event) {
const formArray: FormArray = this.myForm.get('myChoices') as FormArray;
/* Selected */
if(event.target.checked){
// Add a new control in the arrayForm
formArray.push(new FormControl(event.target.value));
}
/* unselected */
else{
// find the unselected element
let i: number = 0;
formArray.controls.forEach((ctrl: FormControl) => {
if(ctrl.value == event.target.value) {
// Remove the unselected element from the arrayForm
formArray.removeAt(i);
return;
}
i++;
});
}
}
Когда я отправляю свою форму, например, моя модель выглядит следующим образом:
otherControls : "foo",
myChoices : ['value1', 'value2']
Только одна вещь отсутствует, функция для заполнения formArray, если ваша модель уже проверила значения.
Ответ 2
Здесь хорошее место для использования FormArray
https://angular.io/docs/ts/latest/api/forms/index/FormArray-class.html
Чтобы начать, мы создадим наш массив элементов управления либо с помощью FormBuilder
, либо создадим a FormArray
FormBuilder
this.checkboxGroup = _fb.group({
myValues: _fb.array([true, false, true])
});
новый FormArray
let checkboxArray = new FormArray([
new FormControl(true),
new FormControl(false),
new FormControl(true)]);
this.checkboxGroup = _fb.group({
myValues: checkboxArray
});
Достаточно легко сделать, но тогда мы собираемся изменить наш шаблон и позволить механизму шаблонов обрабатывать, как мы привязываемся к нашим элементам управления:
template.html
<form [formGroup]="checkboxGroup">
<input *ngFor="let control of checkboxGroup.controls['myValues'].controls"
type="checkbox" id="checkbox-1" value="value-1" [formControl]="control" />
</form>
Здесь мы повторяем наш набор FormControls
в нашем myValues
FormArray
, и для каждого элемента управления мы привязываем [formControl]
к этому элементу управления, а не к элементу управления FormArray
и <div>{{checkboxGroup.controls['myValues'].value}}</div>
производит true,false,true
, в то же время упрощая синтаксис вашего шаблона.
Вы можете использовать этот пример: http://plnkr.co/edit/a9OdMAq2YIwQFo7gixbj?p=preview, чтобы ткнуть вокруг
Ответ 3
Это значительно проще сделать в Angular 6, чем это было в предыдущих версиях, даже когда информация о флажках заполняется асинхронно из API.
Первое, что нужно понять, это то, что благодаря keyvalue
Angular 6 нам больше не нужно использовать FormArray
, и вместо этого мы можем FormGroup
.
Сначала передайте FormBuilder в конструктор
constructor(
private _formBuilder: FormBuilder,
) { }
Затем инициализируйте нашу форму.
ngOnInit() {
this.form = this._formBuilder.group({
'checkboxes': this._formBuilder.group({}),
});
}
Когда наши данные опций FormGroup
доступны, выполните итерацию, и мы сможем FormGroup
их непосредственно во вложенную FormGroup
в виде именованного FormControl
, не полагаясь на FormControl
индексные массивы поиска.
options.forEach((option: any) => {
const checkboxes = <FormGroup>this.form.get('checkboxes');
checkboxes.addControl(option.title, new FormControl(true));
});
Наконец, в шаблоне нам просто нужно keyvalue
значение keyvalue
для keyvalue
: без дополнительных let index = i
, и флажки будут автоматически в алфавитном порядке: намного чище.
<form [formGroup]="form">
<h3>Options</h3>
<div formGroupName="checkboxes">
<ul>
<li *ngFor="let item of form.get('checkboxes').value | keyvalue">
<label>
<input type="checkbox" [formControlName]="item.key" [value]="item.value" /> {{ item.key }}
</label>
</li>
</ul>
</div>
</form>
Ответ 4
Если вы ищете значения флажков в формате JSON
{ "name": "", "countries": [ { "US": true }, { "Germany": true }, { "France": true } ] }
Полный пример здесь.
Я приношу свои извинения за использование названий стран в качестве значений флажков, а не в вопросе. Дальнейшее объяснение -
Создать FormGroup для формы
createForm() {
//Form Group for a Hero Form
this.heroForm = this.fb.group({
name: '',
countries: this.fb.array([])
});
let countries=['US','Germany','France'];
this.setCountries(countries);}
}
Пусть каждый флажок будет FormGroup, созданной из объекта, единственным свойством которого является значение флажка.
setCountries(countries:string[]) {
//One Form Group for one country
const countriesFGs = countries.map(country =>{
let obj={};obj[country]=true;
return this.fb.group(obj)
});
const countryFormArray = this.fb.array(countriesFGs);
this.heroForm.setControl('countries', countryFormArray);
}
Массив FormGroups для флажков используется для установки элемента управления для "стран" в родительской форме.
get countries(): FormArray {
return this.heroForm.get('countries') as FormArray;
};
В шаблоне используйте канал, чтобы получить имя для элемента управления checkbox.
<div formArrayName="countries" class="well well-lg">
<div *ngFor="let country of countries.controls; let i=index" [formGroupName]="i" >
<div *ngFor="let key of country.controls | mapToKeys" >
<input type="checkbox" formControlName="{{key.key}}">{{key.key}}
</div>
</div>
</div>
Ответ 5
Сделайте событие, когда оно щелкнуло, а затем вручную измените значение true на имя того, что представляет собой флажок, тогда имя или истина будут оцениваться одинаково, и вы можете получить все значения вместо списка true/ложный. Пример:
component.html
<form [formGroup]="customForm" (ngSubmit)="onSubmit()">
<div class="form-group" *ngFor="let parameter of parameters"> <!--I iterate here to list all my checkboxes -->
<label class="control-label" for="{{parameter.Title}}"> {{parameter.Title}} </label>
<div class="checkbox">
<input
type="checkbox"
id="{{parameter.Title}}"
formControlName="{{parameter.Title}}"
(change)="onCheckboxChange($event)"
> <!-- ^^THIS^^ is the important part -->
</div>
</div>
</form>
component.ts
onCheckboxChange(event) {
//We want to get back what the name of the checkbox represents, so I'm intercepting the event and
//manually changing the value from true to the name of what is being checked.
//check if the value is true first, if it is then change it to the name of the value
//this way when it set to false it will skip over this and make it false, thus unchecking
//the box
if(this.customForm.get(event.target.id).value) {
this.customForm.patchValue({[event.target.id] : event.target.id}); //make sure to have the square brackets
}
}
Это происходит после того, как оно было изменено на true или false на Angular Forms, если оно истинно. Я меняю имя на имя того, что представляет флажок, который при необходимости также будет оцениваться как true, если он проверяется для true/false.
Ответ 6
TL; DR
- Я предпочитаю использовать FormGroup для заполнения списка флажком
- Написать свой валидатор для проверки, чтобы был выбран хотя бы один флажок
- Рабочий пример https://stackblitz.com/edit/angular-validate-at-least-one-checkbox-was-selected
Это также иногда поражало меня, поэтому я попробовал подходы FormArray и FormGroup.
Большую часть времени список флажков был заполнен на сервере, и я получил его через API. Но иногда у вас будет статический набор флажков с вашим предопределенным значением. В каждом случае использования будет использоваться соответствующий FormArray или FormGroup.
В основном FormArray
является вариантом FormGroup
. Ключевое отличие состоит в том, что его данные сериализуются как массив (в отличие от сериализации в случае FormGroup). Это может быть особенно полезно, когда вы не знаете, сколько элементов управления будет присутствовать в группе, например, динамические формы.
Для простоты представьте, что у вас есть простая форма для создания продукта с
- Одно обязательное текстовое поле с названием продукта.
- Список категорий для выбора, требуется хотя бы одна проверяемая. Предположим, что список будет получен с сервера.
Во-первых, я настроил форму только с именем продукта formControl. Это обязательное поле.
this.form = this.formBuilder.group({
name: ["", Validators.required]
});
Поскольку категория отображается динамически, мне нужно будет добавить эти данные в форму позже, когда они будут готовы.
this.getCategories().subscribe(categories => {
this.form.addControl("categoriesFormArr", this.buildCategoryFormArr(categories));
this.form.addControl("categoriesFormGroup", this.buildCategoryFormGroup(categories));
})
Существует два подхода к созданию списка категорий.
1. Форма массива
buildCategoryFormArr(categories: ProductCategory[], selectedCategoryIds: string[] = []): FormArray {
const controlArr = categories.map(category => {
let isSelected = selectedCategoryIds.some(id => id === category.id);
return this.formBuilder.control(isSelected);
})
return this.formBuilder.array(controlArr, atLeastOneCheckboxCheckedValidator())
}
<div *ngFor="let control of categoriesFormArr?.controls; let i = index" class="checkbox">
<label><input type="checkbox" [formControl]="control" />
{{ categories[i]?.title }}
</label>
</div>
Этот buildCategoryFormGroup
вернет мне FormArray. Он также принимает список выбранных значений в качестве аргумента, поэтому если вы хотите повторно использовать форму для редактирования данных, это может быть полезно. В целях создания новой формы продукта она пока не применима.
Отметил это при попытке доступа к значениям formArray. Это будет похоже на [false, true, true]
. Чтобы получить список выбранных идентификаторов, потребовалось немного больше работы для проверки из списка, но на основе индекса массива. Не звучит хорошо для меня, но это работает.
get categoriesFormArraySelectedIds(): string[] {
return this.categories
.filter((cat, catIdx) => this.categoriesFormArr.controls.some((control, controlIdx) => catIdx === controlIdx && control.value))
.map(cat => cat.id);
}
Вот почему я решил использовать FormGroup
для этого вопроса
2. Форма группы
Отличительной особенностью formGroup является то, что он будет хранить данные формы в виде объекта, для которого требуется ключ и элемент управления формы. Поэтому рекомендуется установить ключ в качестве categoryId, а затем мы сможем получить его позже.
buildCategoryFormGroup(categories: ProductCategory[], selectedCategoryIds: string[] = []): FormGroup {
let group = this.formBuilder.group({}, {
validators: atLeastOneCheckboxCheckedValidator()
});
categories.forEach(category => {
let isSelected = selectedCategoryIds.some(id => id === category.id);
group.addControl(category.id, this.formBuilder.control(isSelected));
})
return group;
}
<div *ngFor="let item of categories; let i = index" class="checkbox">
<label><input type="checkbox" [formControl]="categoriesFormGroup?.controls[item.id]" /> {{ categories[i]?.title }}
</label>
</div>
Значение группы форм будет выглядеть следующим образом:
{
"category1": false,
"category2": true,
"category3": true,
}
Но чаще всего мы хотим получить только список идентификаторов категорий как ["category2", "category3"]
. Я также должен написать, чтобы взять эти данные. Мне нравится этот подход лучше, чем formArray, потому что я мог бы взять значение из самой формы.
get categoriesFormGroupSelectedIds(): string[] {
let ids: string[] = [];
for (var key in this.categoriesFormGroup.controls) {
if (this.categoriesFormGroup.controls[key].value) {
ids.push(key);
}
else {
ids = ids.filter(id => id !== key);
}
}
return ids;
}
3. Был выбран пользовательский валидатор для проверки хотя бы одного флажка
Я сделал валидатор, чтобы убедиться, что установлен хотя бы флажок X, по умолчанию он будет проверять только один флажок.
export function atLeastOneCheckboxCheckedValidator(minRequired = 1): ValidatorFn {
return function validate(formGroup: FormGroup) {
let checked = 0;
Object.keys(formGroup.controls).forEach(key => {
const control = formGroup.controls[key];
if (control.value === true) {
checked++;
}
});
if (checked < minRequired) {
return {
requireCheckboxToBeChecked: true,
};
}
return null;
};
}
Ответ 7
Мое решение - это решение для Angular 5 с Material View
Связь через
formArrayName = "уведомление"
(change) = "updateChkbxArray (n.id, $ event.checked, 'messages')"
Таким образом, он может работать для нескольких массивов флажков в одной форме. Просто установите имя массива элементов управления для подключения каждый раз.
constructor(
private fb: FormBuilder,
private http: Http,
private codeTableService: CodeTablesService) {
this.codeTableService.getnotifications().subscribe(response => {
this.notifications = response;
})
...
}
createForm() {
this.form = this.fb.group({
notification: this.fb.array([])...
});
}
ngOnInit() {
this.createForm();
}
updateChkbxArray(id, isChecked, key) {
const chkArray = < FormArray > this.form.get(key);
if (isChecked) {
chkArray.push(new FormControl(id));
} else {
let idx = chkArray.controls.findIndex(x => x.value == id);
chkArray.removeAt(idx);
}
}
<div class="col-md-12">
<section class="checkbox-section text-center" *ngIf="notifications && notifications.length > 0">
<label class="example-margin">Notifications to send:</label>
<p *ngFor="let n of notifications; let i = index" formArrayName="notification">
<mat-checkbox class="checkbox-margin" (change)="updateChkbxArray(n.id, $event.checked, 'notification')" value="n.id">{{n.description}}</mat-checkbox>
</p>
</section>
</div>
Ответ 8
Если вы хотите использовать угловую реактивную форму (https://angular.io/guide/reactive-forms).
Вы можете использовать один элемент управления формы для управления выводимым значением группы флажков.
составная часть
import { Component } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';
import { flow } from 'lodash';
import { flatMap, filter } from 'lodash/fp';
@Component({
selector: 'multi-checkbox',
templateUrl: './multi-checkbox.layout.html',
})
export class MultiChecboxComponent {
checklistState = [
{
label: 'Frodo Baggins',
value: 'frodo_baggins',
checked: false
},
{
label: 'Samwise Gamgee',
value: 'samwise_gamgee',
checked: true,
},
{
label: 'Merry Brandybuck',
value: 'merry_brandybuck',
checked: false
}
];
form = new FormGroup({
checklist : new FormControl(this.flattenValues(this.checklistState)),
});
checklist = this.form.get('checklist');
onChecklistChange(checked, checkbox) {
checkbox.checked = checked;
this.checklist.setValue(this.flattenValues(this.checklistState));
}
flattenValues(checkboxes) {
const flattenedValues = flow([
filter(checkbox => checkbox.checked),
flatMap(checkbox => checkbox.value )
])(checkboxes)
return flattenedValues.join(',');
}
}
HTML
<form [formGroup]="form">
<label *ngFor="let checkbox of checklistState" class="checkbox-control">
<input type="checkbox" (change)="onChecklistChange($event.target.checked, checkbox)" [checked]="checkbox.checked" [value]="checkbox.value" /> {{ checkbox.label }}
</label>
</form>
checklistState
Управляет моделью/состоянием входов контрольного списка. Эта модель позволяет отображать текущее состояние в любой формат значения, который вам нужен.
Модель:
{
label: 'Value 1',
value: 'value_1',
checked: false
},
{
label: 'Samwise Gamgee',
value: 'samwise_gamgee',
checked: true,
},
{
label: 'Merry Brandybuck',
value: 'merry_brandybuck',
checked: false
}
checklist
формы контроля
Этот элемент управления хранит значение, которое хотелось бы сохранить, например,
вывод значения: "value_1,value_2"
См. Демонстрацию на https://stackblitz.com/edit/angular-multi-checklist
Ответ 9
Я не вижу здесь решения, которое бы полностью отвечало на вопрос, используя реактивные формы в полной мере, поэтому здесь мое решение для того же.
Резюме
Здесь суть подробного объяснения вместе с примером StackBlitz.
- Используйте
FormArray
для флажков и инициализируйте форму.
- Наблюдаемая
valueChanges
идеально подходит для случаев, когда вы хотите, чтобы форма отображала что-то, но сохраняла что-то еще в компоненте. Сопоставьте значения true
/false
с требуемыми значениями здесь.
- Отфильтруйте значения
false
во время отправки.
- Отписаться от
valueChanges
можно заметить.
Подробное объяснение
Используйте FormArray для определения формы
Как уже упоминалось в ответе помечено как правильное. FormArray
- это способ пойти в тех случаях, когда вы предпочитаете получать данные в массиве. Поэтому первое, что вам нужно сделать, это создать форму.
checkboxGroup: FormGroup;
checkboxes = [{
name: 'Value 1',
value: 'value-1'
}, {
name: 'Value 2',
value: 'value-2'
}];
this.checkboxGroup = this.fb.group({
checkboxes: this.fb.array(this.checkboxes.map(x => false))
});
Это просто установит начальное значение всех флажков на false
.
Затем нам нужно зарегистрировать эти переменные формы в шаблоне и выполнить итерацию по массиву checkboxes
(НЕ FormArray
, а по данным флажка), чтобы отобразить их в шаблоне.
<form [formGroup]="checkboxGroup">
<ng-container *ngFor="let checkbox of checkboxes; let i = index" formArrayName="checkboxes">
<input type="checkbox" [formControlName]="i" />{{checkbox.name}}
</ng-container>
</form>
Используйте наблюдаемые значения valueChanges
Здесь часть, которую я не вижу, упоминается в любом ответе, приведенном здесь. В таких ситуациях, как эта, где мы хотели бы отобразить указанные данные, но сохранить их как-то еще, наблюдаемая valueChanges
очень полезна. Используя valueChanges
, мы можем наблюдать изменения в значениях checkboxes
, а затем map
значений true
/false
, полученных от FormArray
до нужных данных. Обратите внимание, что это не изменит выбор флажков, поскольку любое истинное значение, переданное флажку, помечает его как отмеченное и наоборот.
subscription: Subscription;
const checkboxControl = (this.checkboxGroup.controls.checkboxes as FormArray);
this.subscription = checkboxControl.valueChanges.subscribe(checkbox => {
checkboxControl.setValue(
checkboxControl.value.map((value, i) => value ? this.checkboxes[i].value : false),
{ emitEvent: false }
);
});
Это в основном отображает значения FormArray
в исходный массив checkboxes
и возвращает value
в случае, если флажок помечен как true
, в противном случае он возвращает false
. Здесь emitEvent: false
важен, так как установка значения FormArray
без него приведет к тому, что valueChanges
будет генерировать событие, создающее бесконечный цикл. Устанавливая emitEvent
в false
, мы убеждаемся, что наблюдаемая valueChanges
не излучает, когда мы устанавливаем здесь значение.
Отфильтровать ложные значения
Мы не можем напрямую фильтровать значения false
в FormArray
, потому что это испортит шаблон, так как они связаны с флажками. Поэтому наилучшее из возможных решений - отфильтровать значения false
во время отправки. Используйте для этого оператор распространения.
submit() {
const checkboxControl = (this.checkboxGroup.controls.checkboxes as FormArray);
const formValue = {
...this.checkboxGroup.value,
checkboxes: checkboxControl.value.filter(value => !!value)
}
// Submit formValue here instead of this.checkboxGroup.value as it contains the filtered data
}
Это в основном отфильтровывает ложные значения из checkboxes
.
Отписаться от valueChanges
Наконец, не забудьте отписаться от рассылки valueChanges
ngOnDestroy() {
this.subscription.unsubscribe();
}
Примечание: Существует особый случай, когда значение нельзя установить на FormArray
в valueChanges
, т.е. если значение флажка установлено на число 0
. Это будет выглядеть так, как будто флажок не может быть установлен, так как при установке флажка FormControl
будет установлен номер 0
(ложное значение), и, следовательно, он останется не отмеченным. Было бы предпочтительнее не использовать число 0
в качестве значения, но если это требуется, вы должны условно установить 0
на какое-то истинное значение, скажем, строку '0'
или просто простое true
, а затем при отправке, преобразовать его обратно в число 0
.
StackBlitz также содержит код, когда вы хотите передать значения по умолчанию в флажки, чтобы они отмечались как отмеченные в пользовательском интерфейсе.
Ответ 10
ЧАСТЬ ШАБЛОНА: -
<div class="form-group">
<label for="options">Options:</label>
<div *ngFor="let option of options">
<label>
<input type="checkbox"
name="options"
value="{{option.value}}"
[(ngModel)]="option.checked"
/>
{{option.name}}
</label>
</div>
<br/>
<button (click)="getselectedOptions()" >Get Selected Items</button>
</div>
ЧАСТЬ КОНТРОЛЛЕРА: -
export class Angular2NgFor {
constructor() {
this.options = [
{name:'OptionA', value:'first_opt', checked:true},
{name:'OptionB', value:'second_opt', checked:false},
{name:'OptionC', value:'third_opt', checked:true}
];
this.getselectedOptions = function() {
alert(this.options
.filter(opt => opt.checked)
.map(opt => opt.value));
}
}
}
Ответ 11
Добавьте мои 5 центов)
Моя модель вопроса
{
name: "what_is_it",
options:[
{
label: 'Option name',
value: '1'
},
{
label: 'Option name 2',
value: '2'
}
]
}
template.html
<div class="question" formGroupName="{{ question.name }}">
<div *ngFor="let opt of question.options; index as i" class="question__answer" >
<input
type="checkbox" id="{{question.name}}_{{i}}"
[name]="question.name" class="hidden question__input"
[value]="opt.value"
[formControlName]="opt.label"
>
<label for="{{question.name}}_{{i}}" class="question__label question__label_checkbox">
{{opt.label}}
</label>
</div>
component.ts
onSubmit() {
let formModel = {};
for (let key in this.form.value) {
if (typeof this.form.value[key] !== 'object') {
formModel[key] = this.form.value[key]
} else { //if formgroup item
formModel[key] = '';
for (let k in this.form.value[key]) {
if (this.form.value[key][k])
formModel[key] = formModel[key] + k + ';'; //create string with ';' separators like 'a;b;c'
}
}
}
console.log(formModel)
}