Daniel Lizik
Daniel Lizik

Reputation: 3144

can't set ng model on checkbox inside ng-repeat

I have a row of checkboxes, only one can be checked at a time, and if you click on the checkbox that is checked, its state should flip. So if it is checked and you click it, it should uncheck, and vice versa.

model

this.models = [
  {name: 'blah', defaulted: false},
  {name: 'fdsf', defaulted: false},
  ...
]

template

<input 
  type="checkbox"
  ng-repeat="model in vm.models"
  ng-checked="model.defaulted === true" 
  ng-change="vm.setDefault($index)"
  ng-model="model.defaulted" />

a. this one doesn't work, model isn't updated

http://embed.plnkr.co/O275a9O7y60NuimFYdYi?show=preview

this.setDefault = (j) => this.models = this.models.map((m, i) => {
  return j === i 
    ? Object.assign({}, m, {defaulted: !m.defaulted}) 
    : Object.assign({}, m, {defaulted: false}) 
}) 

b. model updates correctly in this one but you are unable to untoggle a checkbox by clicking on it again (hence the !m.defaulted in example a)

http://embed.plnkr.co/MOBuN06R0hmcnpGu01Z7?show=preview

// this works, but now you can't uncheck a box that is checked
this.setDefault = (j) => this.models = this.models.map((m, i) => {
  return j === i 
    ? Object.assign({}, m, {defaulted: true}) 
    : Object.assign({}, m, {defaulted: false}) 
}) 

Need a solution without using radios

Upvotes: 3

Views: 249

Answers (4)

Alon Eitan
Alon Eitan

Reputation: 12025

All you have to do is to change:

this.setDefault = (j) => this.models = this.models.map((m, i) => {
  return j === i 
    ? Object.assign({}, m, {defaulted: true}) 
    : Object.assign({}, m, {defaulted: false}) 
}) 

To

this.setDefault = (j) => this.models = this.models.map((m, i) => {
  return j === i 
    ? Object.assign({}, m, {defaulted: m.defaulted}) 
    : Object.assign({}, m, {defaulted: false}) 
}) 

By changing it to Object.assign({}, m, {defaulted: m.defaulted}) you allow to toggle the state of the current selected/unselected checkbox.

Edit: Please note that @PankajParkar's answer is better, because unlike my answer, that simply solve the main issue, that answer is also better for performances as it doesn't overwrite the this.models array using Array.prototype.map() - Which makes angular re-render the view in the ngRepeat loop.

You're welcome to accept their answer instead

Upvotes: 2

Ruslanas Balčiūnas
Ruslanas Balčiūnas

Reputation: 7428

HTML

<input name="foo" ng-change="vm.setDefault(opt)" ng-model="opt.defaulted" type="checkbox" ng-repeat="opt in vm.models" />

JavaScript

this.setDefault = function(opt) {
    this.models.forEach((val) => {
        if(opt != val) {
            val.defaulted = false;
        }
    });
};

Collected some downvotes, but it actually works pretty well.

https://plnkr.co/edit/BhE7XF?p=preview

Upvotes: 0

Pankaj Parkar
Pankaj Parkar

Reputation: 136194

I don't like the way you played with index of an array element. It will not work if you apply any filtering over models collection. Rather I'd suggest you to pass current selected model to setDefault function like below and make current model flag true, other will be set to false. Also there is no need to use Object.assign to everytime create a new copy of an object. Rather looping and modifying the existing array element makes more sense.

<input type="checkbox" 
  ng-change="vm.setDefault(model)"
  ng-model="model.defaulted" />

Code

this.setDefault = (j) => {
    this.models.forEach(i => i.defaulted = i.defaulted && j.name === i.name);
} 

Demo Plunker

Upvotes: 2

Daniel Lizik
Daniel Lizik

Reputation: 3144

I said screw it and used icons instead of checkboxes

<i
  ng-click="vm.setDefault($index)"
  ng-class="{'fa-circle-thin': model.defaulted === false, 'fa-circle': model.defaulted === true}"
  class="fa">
</i>

https://plnkr.co/edit/5dj865ew1dqUMM9cchrY?p=preview

Upvotes: 0

Related Questions