AngularJS: Binding boolean value to radio button such that it updates model to false on uncheck event
Asked Answered
B

3

9

In my AngularJS application, I am displaying contacts data in a grid. My typical contacts JSON looks like as below ...

[
    { type: "IM", value: "mavaze123", default: true },
    { type: "IM", value: "mvaze2014", default: false },
    { type: "IM", value: "mavaze923", default: false },
    { type: "IM", value: "mvaze8927", default: false },
    { type: "Email", value: "[email protected]", default: true },
    { type: "Email", value: "[email protected]", default: false } 
]

The last property 'default' is actually a radio button, selection of which should alter the original default value of the corresponding contact type in above JSON. There can be one default from each type of contact i.e. we can group radio buttons based on the contact type.

<div ng-repeat="contact in contacts">
    <div>{{contact.type}}</div>
    <div>{{contact.value}}</div>
    <div><input type="radio" name="{{contact.type}}" ng-model="contact.default" ng-value="true"/></div>
</div>

Note: The above code is not the exact one, but approximately same, as it will appear inside a custom grid component.

Now when I load my view/edit form page with above JSON, it correctly shows the radio state of all contacts. The problem comes, after page load, when user selects another contact as default. This actually changes the model value of default to true for newly selected contact however the model value of original default contact still remains true, even though its radio state changes to uncheck/blur (because they are having same 'name' value).

I thought to write a directive, but I am unable get it triggered on radio on-blur/uncheck event.

There are various posts on binding boolean values to radio buttons, but I am unable to get it work in my scenario, as I want to update model values for individual radio button in a radio group. See there is no single model representing a radio group.

Original State of radio buttonsModel changes on selection of radio buttonPrevious model remains unchanged on selection of other radio button

Beauvoir answered 4/1, 2014 at 5:33 Comment(0)
B
1

I added an attribute directive in my input statement ...

<div ng-repeat="contact in contacts">
    <div>{{contact.type}}</div>
    <div>{{contact.value}}</div>
    <div><input type="radio" name="{{contact.type}}" ng-model="contact.default" ng-value="true" boolean-grid-model /></div>
</div>

And my custom directive ...

myModule.directive('booleanGridModel') {
    return {
        restrict: 'A',
        require: 'ngModel',
        link: function (scope, elem, attrs, controller) {
           var radioSelected = scope.$eval(attrs.ngModel);
           if(radioSelected) {
               var selectedContact = scope.contact;
               _.each(scope.contacts, function(contact) {
                  if(contact.type === selectedContact.type) {
                      _.isEqual(contact, selectedContact) ? contact.default = true : contact.default = false;
                  }
               });
           }
       }
    };
}
Beauvoir answered 5/1, 2014 at 6:42 Comment(0)
R
2

I think you should change your design to separate the contacts from contactTypes and store the key to the default contact in contact type.

In your current design, there are duplicated values for default and that's not the desired way to work with radio.

$scope.contacts = [
        { type: "IM", value: "mavaze123" },
        { type: "IM", value: "mvaze2014" },
        { type: "IM", value: "mavaze923" },
        { type: "IM", value: "mvaze8927" },
        { type: "Email", value: "[email protected]" },
        { type: "Email", value: "[email protected]" } 
   ];

    $scope.contactTypes = {
        "IM": { default:"mavaze123"}, //the default is contact with value = mavaze123
        "Email": { default:"[email protected]"}
   };

You Html:

<div ng-repeat="contact in contacts">
            <div>{{contact.type}}</div>
            <div>{{contact.value}}</div>
            <div><input type="radio" name="{{contact.type}}" ng-model="contactTypes[contact.type].default" ng-value="contact.value"/></div>
        </div>

DEMO

I assume that the key of contact is value, you could use an Id for your contact.

Riva answered 4/1, 2014 at 6:24 Comment(9)
Hi @khanh-to, I tend to accept this as I had already got this working. However I was thinking to operate on the same JSON I am getting from REST response and the same I need to post to REST API after modification. Your solution though works, I need to write backend logic to convert into original JSON format while sending it to REST. I was almost there, as false to true works perfect, though reverse fails on selection of other radio from the same contact type group.Beauvoir
@mavaze: what do you mean by reverse fails?Riva
@mavaze: there is no true or false in this design, you assign the key of the default contact to contact type.Riva
Hi @khanh-to, I am talking about the solution I achieved so far with the JSON format I have. First, I am able to select only one from each group. Second, if default=false and if I select that radio, the model changes to true, however, third, if I select another radio from the same group, the previously selected radio though changes its state (uncheck/blur), doesn't change its model value to false.Beauvoir
@mavaze: I understand your problem as it was stated in the question. I mean, in the new design, there is no default (true or false) for each contact. We move the default to the contact type instead. Each contact type stores an Id of the default contact. We no longer care about true or false, just care about the Id of the default in contact type.Riva
@mavaze: take a look at this: jsfiddle.net/cJce6/1 . We are still able to display true, false state if you want.Riva
@Beauvoir You can still use your model. Create $scope.contactTypes after you get the data and update the default values before you send them back.Inconformity
Thanks @khanh-to. As I said in first comment, I accept this as solution. I don't intend to show true and false value (I uploaded some screenshots in my original question), but need to combine your solution with what @ zeroflagL has to say, to achieve the end goal. Though this achieves, I am little unconvinced, but may feel this is the only way. I wish to dig into more.Beauvoir
Please note I am using a custom grid component, so anyway need to send 3 params to grid to match with number of columns.Beauvoir
B
1

I added an attribute directive in my input statement ...

<div ng-repeat="contact in contacts">
    <div>{{contact.type}}</div>
    <div>{{contact.value}}</div>
    <div><input type="radio" name="{{contact.type}}" ng-model="contact.default" ng-value="true" boolean-grid-model /></div>
</div>

And my custom directive ...

myModule.directive('booleanGridModel') {
    return {
        restrict: 'A',
        require: 'ngModel',
        link: function (scope, elem, attrs, controller) {
           var radioSelected = scope.$eval(attrs.ngModel);
           if(radioSelected) {
               var selectedContact = scope.contact;
               _.each(scope.contacts, function(contact) {
                  if(contact.type === selectedContact.type) {
                      _.isEqual(contact, selectedContact) ? contact.default = true : contact.default = false;
                  }
               });
           }
       }
    };
}
Beauvoir answered 5/1, 2014 at 6:42 Comment(0)
G
-1

WHy you declare ng-value="true" please remove that

<div><input type="radio" name="{{contact.type}}" ng-model="contact.default" ng-value="{{contact.default}}"/></div>

Please use $scope.$apply() in your value changing code

Like something below

$scope.$apply(function ChangeType()
{
/Code
});

And you need to change name="{{contact.type}}" to name="contact.type{{$index}}" Because some types are same name.

Greer answered 4/1, 2014 at 5:51 Comment(1)
name="contact.type{{$index}}" doesn't allow selecting one default from each group like one default IM, one default Email, but it may allow to select one default contact across all types. This is not what I want. Also, ng-value="{{contact.default}}" doesn't show the selection of radio button when default is true. With this solution last radio button will be selected always even if default=false. When I set ng-value="true" atleast I ensure the radio is selected only when the default's model value is true. I'm almost there, as false to true work fine, reverse not working though.Beauvoir

© 2022 - 2024 — McMap. All rights reserved.