Étant donné une liste de cases à cocher liées à la même formControlName
, comment puis-je produire un tableau de valeurs de case à cocher liées à la formControl
plutôt que simplement true
/false
?
Exemple:
<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
produit actuellement:
true or false
Ce que je veux qu'il produise:
['value-1', 'value-2', ...]
Voici un bon endroit pour utiliser la FormArray
https://angular.io/docs/ts/latest/api/forms/index/FormArray-class.html
Pour commencer, nous allons construire notre tableau de commandes avec un FormBuilder
ou en créant un FormArray
FormBuilder
this.checkboxGroup = _fb.group({
myValues: _fb.array([true, false, true])
});
new FormArray
let checkboxArray = new FormArray([
new FormControl(true),
new FormControl(false),
new FormControl(true)]);
this.checkboxGroup = _fb.group({
myValues: checkboxArray
});
C'est assez facile à faire, mais ensuite nous allons changer notre modèle et laisser le moteur de gabarit gérer la manière dont nous nous lions à nos contrôles:
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>
Ici, nous parcourons notre ensemble de FormControls
dans notre myValues
FormArray
et pour chaque contrôle nous lions [formControl]
à ce contrôle au lieu de FormArray
control et <div>{{checkboxGroup.controls['myValues'].value}}</div>
produit true,false,true
tout en rendant la syntaxe de votre modèle un peu moins manuelle.
Vous pouvez utiliser cet exemple: http://plnkr.co/edit/a9OdMAq2YIwQFo7gixbj?p=preview pour fouiller
Avec l'aide de silentsod answer, j'ai écrit une solution pour obtenir des valeurs plutôt que des états dans mon formBuilder.
J'utilise une méthode pour ajouter ou supprimer des valeurs dans le formArray. C'est peut-être une mauvaise approche, mais ça marche!
composant.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>
composant.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++;
});
}
}
Lorsque je soumets mon formulaire, par exemple, mon modèle ressemble à:
otherControls : "foo",
myChoices : ['value1', 'value2']
Une seule chose manque, une fonction pour remplir le formArray si votre modèle a déjà des valeurs vérifiées.
Cela est beaucoup plus facile à faire dans Angular 6 que dans les versions précédentes, même lorsque les informations de la case à cocher sont renseignées de manière asynchrone à partir d'une API.
La première chose à réaliser est que, grâce au Angular 6 keyvalue
, nous n'avons plus besoin d'utiliser FormArray
, mais peut imbriquer un FormGroup
.
Tout d'abord, passez FormBuilder au constructeur
constructor(
private _formBuilder: FormBuilder,
) { }
Puis initialisez notre formulaire.
ngOnInit() {
this.form = this._formBuilder.group({
'checkboxes': this._formBuilder.group({}),
});
}
Lorsque nos données d'options de case à cocher sont disponibles, itérez-les et vous pouvez les insérer directement dans le FormGroup
imbriqué sous la forme d'un FormControl
nommé, sans avoir à recourir à des matrices de recherche indexées par des nombres.
options.forEach((option: any) => {
const checkboxes = <FormGroup>this.form.get('checkboxes');
checkboxes.addControl(option.title, new FormControl(true));
});
Enfin, dans le modèle, il suffit d’itérer le keyvalue
des cases à cocher: pas de let index = i
supplémentaire, et les cases à cocher seront automatiquement dans l’ordre alphabétique: beaucoup plus propres.
<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>
Si vous recherchez des valeurs de case à cocher au format JSON
{ "name": "", "countries": [ { "US": true }, { "Germany": true }, { "France": true } ] }
Je m'excuse d'utiliser les noms de pays comme valeurs de case à cocher au lieu de ceux de la question. En savoir plus -
Créer un groupe de formulaires pour le formulaire
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);}
}
Laissez chaque case à cocher être un groupe de formulaire construit à partir d'un objet dont la seule propriété est la valeur de la case à cocher.
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);
}
Le tableau de FormGroups pour les cases à cocher est utilisé pour définir le contrôle pour les "pays" dans le formulaire parent.
get countries(): FormArray {
return this.heroForm.get('countries') as FormArray;
};
Dans le modèle, utilisez un tuyau pour obtenir le nom du contrôle de case à cocher.
<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>
Créez un événement lorsque vous cliquez dessus, puis modifiez manuellement la valeur true en indiquant le nom de la case à cocher. Le nom ou la valeur true sera évalué de la même manière et vous pouvez obtenir toutes les valeurs au lieu d'une liste true/false. Ex:
composant.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>
composant.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's 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
}
}
Ceci intercepte l'événement après qu'il ait déjà été changé en vrai ou faux par Angular Forms, s'il est vrai, je change le nom pour le nom de ce que la case à cocher représente, ce qui le cas échéant sera également considéré comme vrai s'il est en cours vérifié pour vrai/faux aussi.
TL; DR
Cela m’a aussi frappé parfois, j’ai donc essayé à la fois les approches FormArray et FormGroup.
La plupart du temps, la liste des cases à cocher était remplie sur le serveur et je la recevais via une API. Mais parfois, vous aurez un ensemble statique de cases à cocher avec votre valeur prédéfinie. Avec chaque cas d'utilisation, le FormArray ou le FormGroup correspondant sera utilisé.
Fondamentalement,
FormArray
est une variante deFormGroup
. La principale différence est que ses données sont sérialisées sous forme de tableau (au lieu d'être sérialisées sous forme d'objet dans le cas de FormGroup). Cela peut être particulièrement utile lorsque vous ne savez pas combien de contrôles seront présents dans le groupe, tels que les formulaires dynamiques.
Par souci de simplicité, imaginez que vous avez une forme de produit simple à créer avec
Tout d'abord, j'ai créé un formulaire avec uniquement le nom du produit formControl. C'est un champ obligatoire.
this.form = this.formBuilder.group({
name: ["", Validators.required]
});
Étant donné que la catégorie affiche de manière dynamique, je devrai ajouter ces données au formulaire ultérieurement, une fois les données prêtes.
this.getCategories().subscribe(categories => {
this.form.addControl("categoriesFormArr", this.buildCategoryFormArr(categories));
this.form.addControl("categoriesFormGroup", this.buildCategoryFormGroup(categories));
})
Il existe deux approches pour construire la liste de catégories.
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>
Cette buildCategoryFormGroup
me retournera un FormArray. Il prend également une liste de valeurs sélectionnées en tant qu'argument. Par conséquent, si vous souhaitez réutiliser le formulaire pour les données de modification, cela peut être utile. Dans le but de créer un nouveau formulaire de produit, celui-ci n'est pas encore applicable.
Notez que lorsque vous essayez d'accéder aux valeurs formArray. Cela ressemblera à [false, true, true]
. Pour obtenir une liste des identifiants sélectionnés, il fallait un peu plus de travail pour vérifier dans la liste, mais en fonction de l'index du tableau. Ça ne me semble pas bien mais ça marche.
get categoriesFormArraySelectedIds(): string[] {
return this.categories
.filter((cat, catIdx) => this.categoriesFormArr.controls.some((control, controlIdx) => catIdx === controlIdx && control.value))
.map(cat => cat.id);
}
Voilà pourquoi je suis venu en utilisant FormGroup
d'ailleurs
Le différent de formGroup est qu'il stockera les données de formulaire en tant qu'objet, ce qui nécessitait une clé et un contrôle de formulaire. Il est donc judicieux de définir la clé comme categoryId pour pouvoir la récupérer ultérieurement.
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>
La valeur du groupe de formulaires ressemblera à ceci:
{
"category1": false,
"category2": true,
"category3": true,
}
Mais le plus souvent, nous souhaitons obtenir uniquement la liste de categoryIds sous la forme ["category2", "category3"]
. Je dois aussi écrire un get pour prendre ces données. J'aime mieux cette approche que de comparer à formArray, car je pourrais prendre la valeur du formulaire lui-même.
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;
}
J'ai demandé au validateur de cocher au moins X la case cochée; par défaut, il ne vérifie qu'une seule case.
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;
};
}
Si vous souhaitez utiliser un formulaire réactif Angular ( https://angular.io/guide/reactive-forms ).
Vous pouvez utiliser un contrôle de formulaire pour gérer la valeur sortie du groupe de cases à cocher.
composant
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
Gère le modèle/état des entrées de la liste de contrôle. Ce modèle vous permet de mapper l'état actuel au format de valeur dont vous avez besoin.
Modèle:
{
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
Contrôle de formulaireCe contrôle stocke la valeur que vous souhaitez enregistrer, par exemple
valeur de sortie: "value_1,value_2"
Voir la démo sur https://stackblitz.com/edit/angular-multi-checklist
Ma solution - résolu pour Angular 5 avec la vue Matériel
La connexion est à travers le
formArrayName = "notification"
(change) = "updateChkbxArray (n.id, $ event.checked, 'notification')"
De cette façon, cela peut fonctionner pour plusieurs tableaux de cases à cocher dans un seul formulaire. Il suffit de définir le nom du tableau de contrôles à connecter à chaque fois.
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>
À la fin, vous enregistrez le formulaire avec un tableau d'identifiants d'enregistrements d'origine à enregistrer/mettre à jour.
Sera heureux d'avoir des remarques d'amélioration.
PARTIE DU MODÈLE: -
<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>
PARTIE DU CONTRÔLEUR: -
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));
}
}
}
Ajouter mes 5 cents) Ma question modèle
{
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>
composant.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)
}