34

I m using a checkbox in Angular to select more than one element and I'm trying to get the value of that checkbox for submitting.

Instead I'm getting the value those values as an array of true-s. That's what I've tried:

export class CreateSessionComponent implements OnInit {
  form : FormGroup ;

  constructor(private formBuilder: FormBuilder) {}

  ngOnInit() {
    this.form = this.formBuilder.group({
      userdata :  new FormArray([
        new FormControl('', Validators.required)
      ])
    })
  }
}

userdata is a dynamic array populated from a database.

<div formArrayName="useremail; let k = index">
  <div *ngFor="let data of userdata">
    <div>
      <input type="checkbox" name="useremail" formControlName ="{{k}}" [value]="data.email">{{data.email}}
    </div>
  </div>
</div> 
BinaryButterfly
  • 18,137
  • 13
  • 50
  • 91
Arun Kumaresh
  • 6,211
  • 6
  • 32
  • 50

7 Answers7

67

Since you have several values you want to use, you need to use an FormArray, since FormControl can only capture one value.

Start with declaring an empty formArray:

this.myForm = this.fb.group({
  useremail: this.fb.array([])
});

Iterate the your emails, and watch for the change event and pass the respective email and event to a method onChange where you check if it's checked, then add the respective email to the formarray, if it's unchecked, remove the chosen email from the form array:

<div *ngFor="let data of emails">
  <input type="checkbox" (change)="onChange(data.email, $event.target.checked)"> {{data.email}}<br>
</div>

And onChange:

onChange(email:string, isChecked: boolean) {
  const emailFormArray = <FormArray>this.myForm.controls.useremail;

  if(isChecked) {
    emailFormArray.push(new FormControl(email));
  } else {
    let index = emailFormArray.controls.findIndex(x => x.value == email)
    emailFormArray.removeAt(index);
  }
}

Demo

AT82
  • 71,416
  • 24
  • 140
  • 167
  • if there are multiple values are checked then how can i get the multiple values above which gets the single checked value – Arun Kumaresh Apr 15 '17 at 09:20
  • data.email is a dynamic value which i will get from the database which contains multiple values – Arun Kumaresh Apr 15 '17 at 09:25
  • and what are these values? Where are the checkboxes, are they all email checkboxes? You have only one formcontrol, so you cannot capture more than one. You'd have to perhaps use some formarray/nested formgroup, but it's impossible to help further if you do not show more code. – AT82 Apr 15 '17 at 09:30
  • from your answer in the plunker data = {email:"email value",email: "value2 "} contains two values and if both values are checked then how can i get those values on the form submit – Arun Kumaresh Apr 15 '17 at 09:36
  • Then you have to create a formarray instead of formcontrol for the emails. – AT82 Apr 15 '17 at 09:37
  • No problem, glad I could help! Have a great day and happy coding! :) – AT82 Apr 15 '17 at 11:01
  • [Example with initial emails](http://plnkr.co/edit/9NSlx86ncP8iB7TTMewf?p=preview) – Ian Clark Feb 22 '18 at 09:05
6

The problem with the @AJT_82 answer is that if you want to modify the model using form.reset() or or form.patchValue(), it won't work. To resolve these problems you need to implement ControlValueAccessor interface. Basically you need to create 2 components: group component which holds the model value and implements ControlValueAccessor and checkbox component, the actual checkbox. I have written a blog post with detailed explanation as well as created a plunker which demonstrate some examples.

Final usage:

<checkbox-group [(ngModel)]="selectedItems">
    <checkbox value="item1">Item 1</checkbox>
    <checkbox value="item2">Item 2</checkbox>
    <checkbox value="item3">Item 3</checkbox>
    <checkbox value="item4">Item 4</checkbox>
</checkbox-group>

Implementation of group component:

@Component({
selector: 'checkbox-group',
template: `<ng-content></ng-content>`,
providers: [{
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => CheckboxGroupComponent),
      multi: true
    }]
 })
export class CheckboxGroupComponent implements ControlValueAccessor {
  private _model: any;

  // here goes implementation of ControlValueAccessor
  ...

  addOrRemove(value: any) {
    if (this.contains(value)) {
        this.remove(value);
    } else {
        this.add(value);
    }
  }

  contains(value: any): boolean {
    if (this._model instanceof Array) {
        return this._model.indexOf(value) > -1;
    } else if (!!this._model) {
        return this._model === value;
    }

    return false;
  }

  // implementation for add and remove
  ...
}

Checkbox component:

@Component({
selector: 'checkbox',
template: `
  <div (click)="toggleCheck()">
    <input type="checkbox" [checked]="isChecked()" />
    <ng-content></ng-content>
  </div>`
})
export class CheckboxComponent {
  @Input() value: any;

  constructor(@Host() private checkboxGroup: CheckboxGroupComponent) {
  }

  toggleCheck() {
    this.checkboxGroup.addOrRemove(this.value);
  }

  isChecked() {
    return this.checkboxGroup.contains(this.value);
  }
}

Child checkbox controls have reference to group component (@Host() decorator). When a checkbox is clicked toggleCheck() call addOrRemove() method on group component and if checkbox's value is already in the model, it is removed, otherwise it is added to the model.

Andrei Mihalciuc
  • 2,148
  • 16
  • 14
  • Though it looks more complicated at a glance this is the best answer ive found on the topic, those classes are very reusable and clean to use once included, going in the toolbox, thank you. – owlyfool Nov 13 '17 at 11:25
  • 1
    There is one small issue with it, the labels of the check boxes do not trigger change events, only the actual checkbox portion. See this plunker https://plnkr.co/edit/BAhzLYo9e4H8PdAt9lGR?p=preview try clicking both the checkbox square and the label, both trigger the checkbox but only the checkbox portion fires a change. – owlyfool Nov 13 '17 at 13:38
  • @owlyfool I don't understand what you are referring to. toogleCheck() is called when you click on the div which contains a checkbox and a label. So clicking on checkbox and label fires the same behavior. – Andrei Mihalciuc Nov 13 '17 at 14:21
  • [x] <-- clicking here fires (change) which i can handle in the parent component (gets logged to the screen) [x] checkbox label <-- clicking the label directly does not (but it does update the modal as you say). – owlyfool Nov 13 '17 at 14:28
  • You need to put (change)="..." on checkbox-group component. It is the component that holds the model value. – Andrei Mihalciuc Nov 13 '17 at 14:43
  • but i want to respond to the change of an individual checkbox not the entire group, if i move the change event to the group level, i lose the current selected checkbox value, and the label clicking still doesnt work anyway, the change event only gets fired when i click on the input section of the checkbox and not the label. It is strange as i see toggleCheck() -> addOrRemove() -> add() -> this.onChange and that definately seems to be getting called. – owlyfool Nov 13 '17 at 14:53
  • fixed it thanks to response here, needed to use (click) instead of (change) to handle the event. https://stackoverflow.com/questions/47265900/angular-checkbox-labels-not-firing-change-events/47267031#47267031 thanks for your help – owlyfool Nov 13 '17 at 14:56
  • This is the best answer and the correct answer in my opinion and deserves more than one upvote. – Andre Lombaard Jul 24 '18 at 21:36
2

For Angular 2+ check this example link

enter image description here

Code Spy
  • 9,626
  • 4
  • 66
  • 46
1

You can get array of checked data:

<input .... (change)="onChange(data)" />
onChange(data){
   data.checked = !data.checked;
}

to get all checked value

let checkedValues = userdata.filter(x => x.checked).map(x => x.email);
  • 1
    How does "data" relate to "userdata"? I'm also confused because you only show one input, but data does not seem keyed, so if I had 10 of these they would all just toggle one "data" variable, wouldn't they? Last, I'm not sure I understand the "x.email" reference. I'm trying to do exactly what your example intends, but don't know enough to put together the missing pieces. Any help would be appreciated! – Dan Chase May 02 '18 at 02:13
0

I did this function at input for the load form auto select:

[checked]="this.selected? this.selected.type.includes(type) : false"

Full code:

<label *ngFor="let type of this.entityType" class="checkbox-inline c-checkbox">
<input type="checkbox" [checked]="this.selected? this.selected.type.includes(type) : false" (change)="onChangeEntityType(type, $event.target.checked)"/>
<span class="fa fa-check"></span>{{type | translate}}</label>

Where

this.selected

is my object and

this.selected.type.includes(type)

auto check if checkboxes will be checked.

Pedro Braz
  • 29
  • 3
0

I have solved in two different ways One with simple checkbox array - mat-checkbox and the other with mat-selection-list.

Gist of the code in the post. At the value you can set all kind of combinations. In my case I've used concatination of id and description in order to init the form controls at the end from one source..

https://medium.com/@2bhere4u/angular-5-material-design-checkbox-array-reactive-forms-handle-3885dde366ca

My issue was with cleaning the checkbox array in a more simple example.. No time to waste .. Running along to the next tasks.. :)

Tzvi Gregory Kaidanov
  • 3,080
  • 3
  • 26
  • 33
0

This was my approach, I used this for ionic cordova Contacts plugin to get multiple Contacts from addressbook and add the selected Contact.

HTML

<div *ngFor="let c of addressBook" tappable class="px-2">
  <input type="checkbox"  (change)="onChangeContact($event,c)">
  <p>{{ c.displayName }}</p>
 </div>

TS

onChangeContact($event, c) { 
    if ($event.target.checked) {
      this.tempArr.push(c);
    } else {
      let i: number = 0;
      this.tempArr.forEach((item: any) => {
        console.log(item);
        if (item.displayName == c.displayName) {
          this.tempArr.splice(i, 1);
          return;
        }
        i++;
      });
    }
  }
dilip t
  • 139
  • 1
  • 1
  • 5