web-dev-qa-db-fra.com

Angular ReactiveForms: Produire un tableau de valeurs de cases à cocher?

É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', ...]
77

Voici un bon endroit pour utiliser la FormArrayhttps://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 myValuesFormArray 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

40
silentsod

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.

31
Guymage

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>
10
Danny Pritchard

Si vous recherchez des valeurs de case à cocher au format JSON

{ "name": "", "countries": [ { "US": true }, { "Germany": true }, { "France": true } ] }

Exemple complet ici .

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>
8
aCiD

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.

4
canada11

TL; DR

  1. Je préfère utiliser FormGroup pour remplir la liste des cases à cocher
  2. Écrire un validateur personnalisé pour cocher au moins une case cochée
  3. Exemple de travail https://stackblitz.com/edit/angular-validate-at-least-one-checkbox-was-selected

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 de FormGroup. 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

  • Une zone de texte requise pour le nom du produit.
  • Une liste de catégories à sélectionner, dont au moins une doit être cochée. Supposons que la liste sera extraite du serveur.

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.

1. Tableau de forme

  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

2. Groupe de formulaire

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;
  }

3. Validateur personnalisé pour cocher au moins une case cochée

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;
  };
}
3
trungk18

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 formulaire

Ce 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

2
Robert Prib

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. The UI View

The relevat part of the json of the form

Sera heureux d'avoir des remarques d'amélioration.

1

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));
                }
             }

        }
0

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)
   }
0
Chemaxa