javascript - navigationend - Angular ReactiveForms: ¿Produce una matriz de valores de casillas de verificación?
router events angular 6 (10)
Dada una lista de casillas de verificación vinculadas al mismo
formControlName
, ¿cómo puedo producir una matriz de valores de casilla de verificación vinculados al
formControl
, en lugar de simplemente
true
/
false
?
Ejemplo:
<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
produce actualmente:
true or false
Lo que quiero que produzca:
[''value-1'', ''value-2'', ...]
Agregue mis 5 centavos) Mi modelo de pregunta
{
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>
componente.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)
}
Con la ayuda de la respuesta de silentsod, escribí una solución para obtener valores en lugar de estados en mi formBuilder.
Yo uso un método para agregar o eliminar valores en el formArray. Puede ser un mal acercamiento, ¡pero funciona!
componente.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>
componente.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++;
});
}
}
Cuando envío mi formulario, por ejemplo, mi modelo se ve así:
otherControls : "foo",
myChoices : [''value1'', ''value2'']
Solo falta una cosa, una función para completar el formulario Array si su modelo ya tiene valores verificados.
Es significativamente más fácil hacer esto en Angular 6 que en versiones anteriores, incluso cuando la información de la casilla de verificación se llena de forma asíncrona desde una API.
Lo primero que debe darse cuenta es que gracias a la tubería de valor
keyvalue
Angular 6 ya no necesitamos tener que usar
FormArray
, y en su lugar podemos anidar un
FormGroup
.
Primero, pase FormBuilder al constructor
constructor(
private _formBuilder: FormBuilder,
) { }
Entonces inicialice nuestro formulario.
ngOnInit() {
this.form = this._formBuilder.group({
''checkboxes'': this._formBuilder.group({}),
});
}
Cuando nuestros datos de opciones de casilla de verificación estén disponibles, repítalos y podemos insertarlos directamente en el
FormGroup
anidado como un
FormControl
nombrado, sin tener que depender de matrices de búsqueda indexadas por números.
options.forEach((option: any) => {
const checkboxes = <FormGroup>this.form.get(''checkboxes'');
checkboxes.addControl(option.title, new FormControl(true));
});
Finalmente, en la plantilla solo necesitamos iterar el valor
keyvalue
de las casillas de verificación: no
let index = i
adicional, y las casillas de verificación estarán automáticamente en orden alfabético: mucho más limpio.
<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>
Este es un buen lugar para usar
FormArray
https://angular.io/docs/ts/latest/api/forms/index/FormArray-class.html
Para comenzar, crearemos nuestra matriz de controles con un
FormBuilder
o renovando un
FormArray
FormBuilder
this.checkboxGroup = _fb.group({
myValues: _fb.array([true, false, true])
});
nuevo FormArray
let checkboxArray = new FormArray([
new FormControl(true),
new FormControl(false),
new FormControl(true)]);
this.checkboxGroup = _fb.group({
myValues: checkboxArray
});
Es bastante fácil de hacer, pero luego vamos a cambiar nuestra plantilla y dejar que el motor de plantillas maneje cómo nos unimos a nuestros controles:
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>
Aquí estamos iterando sobre nuestro conjunto de
FormControls
en nuestro
myValues
FormArray
y para cada control estamos vinculando
[formControl]
a ese control en lugar de al control
FormArray
y
<div>{{checkboxGroup.controls[''myValues''].value}}</div>
produce
true,false,true
tiempo que hace que la sintaxis de su plantilla sea un poco menos manual.
Puede usar este ejemplo: http://plnkr.co/edit/a9OdMAq2YIwQFo7gixbj?p=preview para hurgar
Haga un evento cuando se haga clic y luego cambie manualmente el valor de verdadero al nombre de lo que representa la casilla de verificación, luego el nombre o verdadero evaluará lo mismo y puede obtener todos los valores en lugar de una lista de verdadero / falso. Ex:
componente.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>
componente.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
}
}
Esto captura el evento después de que Angular Forms ya lo haya cambiado a verdadero o falso, si es cierto, cambio el nombre por el nombre de lo que representa la casilla de verificación, que si es necesario también se evaluará como verdadero si se está verificando como verdadero / falso como bien.
Mi solución: lo resolvió para Angular 5 con Vista material
La conexión es a través de
formArrayName = "notificación"
(change) = "updateChkbxArray (n.id, $ event.checked, ''notificación'')"
De esta manera, puede funcionar para múltiples casillas de verificación en una sola forma. Simplemente configure el nombre de la matriz de controles para conectarse cada vez.
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>
Al final, puede guardar el formulario con una matriz de ID de registros originales para guardar / actualizar.
Estaremos encantados de tener alguna observación para mejorar.
Si busca valores de casillas de verificación en formato JSON
{ "name": "", "countries": [ { "US": true }, { "Germany": true }, { "France": true } ] }
Pido disculpas por usar nombres de países como valores de casillas de verificación en lugar de los de la pregunta. Explicación adicional
Crear un grupo de formularios para el formulario
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);}
}
Deje que cada casilla de verificación sea un FormGroup creado a partir de un objeto cuya única propiedad es el valor de la casilla de verificación.
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);
}
La matriz de FormGroups para las casillas de verificación se usa para establecer el control de los ''países'' en el Formulario principal.
get countries(): FormArray {
return this.heroForm.get(''countries'') as FormArray;
};
En la plantilla, use una tubería para obtener el nombre del control de casilla de verificación
<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>
Si desea utilizar una forma reactiva angular ( https://angular.io/guide/reactive-forms ).
Puede usar un control de formulario para administrar el valor de salida del grupo de casillas de verificación.
componente
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
Administra el modelo / estado de las entradas de la lista de verificación. Este modelo le permite asignar el estado actual al formato de valor que necesite.
Modelo:
{
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
formulario de
checklist
Este control almacena el valor que le gustaría guardar, por ejemplo
salida de valor:
"value_1,value_2"
Ver demostración en https://stackblitz.com/edit/angular-multi-checklist
TL; DR
- Prefiero usar FormGroup para completar la lista de casillas de verificación
- Escriba un validador personalizado para marcar al menos una casilla de verificación seleccionada
- Ejemplo de trabajo https://stackblitz.com/edit/angular-validate-at-least-one-checkbox-was-selected
Esto también me sorprendió a veces, así que probé los enfoques FormArray y FormGroup.
La mayoría de las veces, la lista de casillas de verificación se rellenó en el servidor y la recibí a través de API. Pero a veces tendrá un conjunto estático de casilla de verificación con su valor predefinido. Con cada caso de uso, se utilizará el FormArray o FormGroup correspondiente.
Básicamente,
FormArray
es una variante deFormGroup
. La diferencia clave es que sus datos se serializan como una matriz (en lugar de serializarse como un objeto en el caso de FormGroup). Esto puede ser especialmente útil cuando no sabe cuántos controles estarán presentes dentro del grupo, como los formularios dinámicos.
En aras de la simplicidad, imagine que tiene un formulario de creación de producto simple con
- Se requiere un cuadro de texto para el nombre del producto.
- Una lista de categorías para seleccionar, requiere al menos una para ser verificada. Suponga que la lista se recuperará del servidor.
Primero, configuré un formulario con solo el nombre del producto formControl. Es un campo obligatorio.
this.form = this.formBuilder.group({
name: ["", Validators.required]
});
Dado que la categoría se procesa dinámicamente, tendré que agregar estos datos al formulario más tarde, una vez que los datos estén listos.
this.getCategories().subscribe(categories => {
this.form.addControl("categoriesFormArr", this.buildCategoryFormArr(categories));
this.form.addControl("categoriesFormGroup", this.buildCategoryFormGroup(categories));
})
Hay dos enfoques para construir la lista de categorías.
1. Forma de matriz
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>
Este
buildCategoryFormGroup
me devolverá un FormArray.
También toma una lista del valor seleccionado como argumento, por lo que si desea reutilizar el formulario para editar datos, podría ser útil.
Con el fin de crear un nuevo formulario de producto, aún no es aplicable.
Notó que cuando intenta acceder a los valores de formArray.
Se verá como
[false, true, true]
.
Para obtener una lista de la ID seleccionada, se requirió un poco más de trabajo para verificar de la lista, pero en función del índice de la matriz.
No me suena bien pero funciona.
get categoriesFormArraySelectedIds(): string[] {
return this.categories
.filter((cat, catIdx) => this.categoriesFormArr.controls.some((control, controlIdx) => catIdx === controlIdx && control.value))
.map(cat => cat.id);
}
Es por eso que se me ocurrió usar
FormGroup
para el caso
2. Grupo de formularios
La diferencia de formGroup es que almacenará los datos del formulario como el objeto, lo que requiere una clave y un control de formulario. Por lo tanto, es una buena idea establecer la clave como categoryId y luego podemos recuperarla más adelante.
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>
El valor del grupo de formulario se verá así:
{
"category1": false,
"category2": true,
"category3": true,
}
Pero la mayoría de las veces queremos obtener solo la lista de ID de categoría como
["category2", "category3"]
.
También tengo que escribir un get para tomar estos datos.
Me gusta más este enfoque en comparación con el formArray, porque en realidad podría tomar el valor del formulario en sí.
get categoriesFormArraySelectedIds(): string[] {
return this.categories
.filter((cat, catIdx) => this.categoriesFormArr.controls.some((control, controlIdx) => catIdx === controlIdx && control.value))
.map(cat => cat.id);
}
3. Validador personalizado para marcar al menos una casilla de verificación seleccionada
Hice que el validador para marcar al menos la casilla de verificación X estuviera seleccionada, por defecto verificará solo una casilla de verificación.
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;
};
}
PARTE DE PLANTILLA: -
<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>
PIEZA DEL CONTROLADOR: -
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));
}
}
}