Checkbox Group management and verification in Angular2

I do not understand how checkbox groups should be handled in a model-driven form in angular2.

The model has a languages property, which I create as follows:

 this.model = { languages: [] }; 

Using FormBuilder to create a form:

 this.modelForm = this.formBuilder.group({ 'languages': [model.languages, Validators.required], }); 

And the template:

 <div *ngFor="let language of translateService.get('languages') | async | key_value"> <input type="checkbox" name="languages[]" formControlName="languages" value="{{ language.key }}" id="language_{{ language.key }}"> <label attr.for="language_{{ language.key }}">{{ language.value }}</label> </div> 

div necessary for styling purposes (custom checkbox), key_value obviously makes the keys and language values โ€‹โ€‹available in the loop.

The first problem is verification. If someone checks and unchecks the box (and no other box is checked), the input is still valid - somehow strange.

The second problem is related to a value that is true if two other languages โ€‹โ€‹are checked and false otherwise.

Then the third problem arises with the initial value of languages , which is just an empty array, but first checks all the checkboxes (does not happen if the initial value is set to string ), although I can not define any checked attribute in the DOM.

I work with the latest ion beta (2.0.0-beta.10), which uses angular / core version 2.0.0-rc.4 and angular / forms version 0.2.0.

So, is there a guide for working with checkbox groups? Any tips or ideas?

+6
source share
2 answers

The first problem is verification. If someone checks and unchecks the box (and no other box is checked), the input is still valid - somehow strange.

I noticed that if the languages value is an empty array, it passes the Validations.required check.

The second problem is related to the value, which is true if two other languages โ€‹โ€‹and false are checked otherwise.

Then the third problem arises with the initial value of languages, which is just an empty array, but first checks all the checkboxes (it does not happen if the initial value is set to a string), although I can not specify any verified attribute in the DOM.

I think the problem is how you bind multiple controls to one FormControl , I believe that you need to use FormArray , potentially with another FormControl stores the result of your checkbox.

So, is there a guide for working with checkbox groups? Any tips or ideas?

Of course, I took a hit on its implementation, I will first publish it, and then a few notes. You can view it in action at https://plnkr.co/edit/hFU904?p=preview

 @Component({ template: ` <template [ngIf]="loading"> Loading languages... </template> <template [ngIf]="!loading"> <form [formGroup]="modelForm"> <div [formArrayName]="'languages'" [class.invalid]="!modelForm.controls.selectedLanguages.valid"> <div *ngFor="let language of modelForm.controls.languages.controls; let i = index;" [formGroup]="language"> <input type="checkbox" formControlName="checked" id="language_{{ language.controls.key.value }}"> <label attr.for="language_{{ language.controls.key.value }}">{{ language.controls.value.value }}</label> </div> </div> <hr> <pre>{{modelForm.controls.selectedLanguages.value | json}}</pre> </form> </template> ` }) export class AppComponent { loading:boolean = true; modelForm:FormGroup; languages:LanguageKeyValues[]; constructor(public formBuilder:FormBuilder){ } ngOnInit() { this.translateService.get('languages').subscribe((languages:LanguageKeyValues[]) => { let languagesControlArray = new FormArray(languages.map((l) => { return new FormGroup({ key: new FormControl(l.key), value: new FormControl(l.value), checked: new FormControl(false), }); })); this.modelForm = new FormGroup({ languages: languagesControlArray, selectedLanguages: new FormControl(this.mapLanguages(languagesControlArray.value), Validators.required) }); languagesControlArray.valueChanges.subscribe((v) => { this.modelForm.controls.selectedLanguages.setValue(this.mapLanguages(v)); }); this.loading = false; }); } mapLanguages(languages) { let selectedLanguages = languages.filter((l) => l.checked).map((l) => l.key); return selectedLanguages.length ? selectedLanguages : null; } } 

The main difference here is that I combined your model.languages into your modelForm and now repeat in modelForm.languages FormArray in the template.

modelForm.languages became modelForm.selectedLanguages and is now a calculated value based on the checked values โ€‹โ€‹in modelForm.languages . If nothing is selected, modelForm.selectedLanguages set to null to check for failure.

modelForm not created until languages โ€‹โ€‹are available, these are mainly personal preferences, I'm sure that you could asynchronously attach languages and selectedLanguages to your modelForm , but this simplified things for building it synchronously.

I took out translateService.get('languages') | async translateService.get('languages') | async , I noticed some strange behavior when calling this function in the template, and I prefer to deploy my observables in the component in order to capture the loading / error states.

It's not as elegant as some kind of checkbox form control can be, but it's clean and very flexible. Check out the plunker and let me know if you have any questions!

+6
source

If you check the box and then clear the box, FormControl will still show it as valid, even if it is not set. It may happen that it takes only true and false meanings. You can try this sample code as it worked for me -

  mustBeChecked(control: FormControl): {[key: string]: string} { if (!control.value) { return {mustBeCheckedError: 'Must be checked'}; } else { return null; } } 

You can also refer to this plunker

There is currently an open problem where click triggers appear before the value changes. Check out the github link here . Hope this can help you.

There are various workarounds for multiple checkboxes, such as using

 event.target.checked 

instead of the value from the model.

You can use it as in this example -

  <input type="checkbox" (change)="expression && expression.Option1=$event.target.checked ? true : undefiend" [ngModel]="expression?.Option1"> <input type="checkbox" (change)="expression && expression.Option2=$event.target.checked ? true : undefiend" [ngModel]="expression?.Option2"> 
+1
source