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!