82

I'm trying to generate a set of check-boxes from an object array. I'm aiming to have the check-boxes dynamically map their ng-model to a property of the new object that will be submitted into the array.

What I had in mind is something like

<li ng-repeat="item in items">
    <label>{{item.name}}</label>
    <input type="checkbox" ng-model="newObject.{{item.name}}">
</li>

This doesn't work as can be seen on this JSFiddle:

http://jsfiddle.net/GreenGeorge/NKjXB/2/

Can anybody help?

SpiderWasp42
  • 2,526
  • 1
  • 12
  • 17
George Ananda Eman
  • 3,292
  • 8
  • 28
  • 29

5 Answers5

146

This should give you desired results:

<input type="checkbox" ng-model="newObject[item.name]">

Here is a working plunk: http://plnkr.co/edit/ALHQtkjiUDzZVtTfLIOR?p=preview

pkozlowski.opensource
  • 117,202
  • 60
  • 326
  • 286
  • 1
    hmm actually this literally gave me ' ', is it something im missing? – George Ananda Eman Jan 06 '13 at 15:55
  • Hmm, strange, just added a live example (plunker since for some reason jsFiddle is not working on my side today). – pkozlowski.opensource Jan 06 '13 at 15:58
  • ah yes, i was used to thinking in php and expected the actual markup to change to the name, it worked. thx! – George Ananda Eman Jan 06 '13 at 16:12
  • Great answer! Make sure newObject is an object {} as show in the Plunker. – Keith Holliday Jan 18 '16 at 21:16
  • 1
    It also works great in Angular 2. But is there also a solution for multidimensional objects? In your example, if `item.name` should sometimes point to `newObject['x']` and sometimes to `newObject['x']['y']`. – Martin Schneider Dec 13 '16 at 10:47
  • I am also looking for an answer in Angular 2/Angular 4 that works with multilevel objects. For example newObject[item.name] would work if item.name is single level such as "someProperty", but what if I need a path "someProperty.someSubproperty" because my object looks like `"newObject": {"id": 1234 "someProperty": { "someSubproperty: "Value to bind to with ngModel" } }` – Devon Holcombe Oct 31 '17 at 00:23
  • Dude, I love a short answer thanks! in my case I assigned to my existing Object node $scope.variable.subattr so variable.subattr[item.name] – Arnaud Bouchot Feb 21 '18 at 10:17
23

EDIT As correctly noted in the comments using this with ng-change requires a "dummy" ng-model to be present beforehand. It should however be noted that apparently with 1.3 the required options have been provided by the framework. Please check out https://stackoverflow.com/a/28365515/3497830 below! /EDIT

Just in case you are like me stumbling over a simple case while having a more complex task, this is the solution I came up with for dynamically binding arbitrary expressions to ng-model: http://plnkr.co/edit/ccdJTm0zBnqjntEQfAfx?p=preview

Method: I created a directive dynamicModel that takes a standard angular expression, evaluates it and links the result to the scope via ng-model and $compile.

var app = angular.module('plunker', []);

app.controller('MainCtrl', function($scope) {
  $scope.data = {};
  $scope.testvalue = 'data.foo';
  $scope.eval = $scope.$eval;
});

var app = angular.module('plunker', []);

app.controller('MainCtrl', function($scope) {
  $scope.data = {};
  $scope.testvalue = 'data.foo';
  $scope.eval = $scope.$eval;
});

app.directive('dynamicModel', ['$compile', function ($compile) {
    return {
        'link': function(scope, element, attrs) {
            scope.$watch(attrs.dynamicModel, function(dynamicModel) {
                if (attrs.ngModel == dynamicModel || !dynamicModel) return;

                element.attr('ng-model', dynamicModel);
                if (dynamicModel == '') {
                    element.removeAttr('ng-model');
                }

                // Unbind all previous event handlers, this is 
                // necessary to remove previously linked models.
                element.unbind();
                $compile(element)(scope);
            });
        }
    };
}]);

Usage is simply dynamic-model="angularExpression" where angularExpression results in a string that is used as the expression for ng-model.

I hope this saves someone the headache of having to come up with this solution.

Regards, Justus

Community
  • 1
  • 1
Justus Wingert
  • 472
  • 4
  • 9
  • 3
    You're a lifesaver. I almost despaired before finding this post. – Nelo Mitranim Jul 03 '14 at 09:23
  • Can you be more specific Brian? What did you try and what happened? – Justus Wingert Jul 28 '14 at 10:02
  • This is a compete gem of a solution. You've gotten me out of a very sticky issue - thanks! – Mikebert4 Feb 18 '15 at 18:48
  • 1
    ng-change does not work with this. If you look in the angular source, the ngChange directive has ngModel as a required directive. A quick search shows only ngChange and ngList have this issue. All other directives seem to have ngModel as an optional controller. I worked around this problem by adding an ng-model="dummyValue" to any element using the dynamic-model directive. Since a change to dynamic-model calls $compile, ngChange and any other directives using the ng-model value are updated correctly. – EverPresent Mar 11 '15 at 01:07
  • 1
    This is a more robust solution, when you don't need to watch the dynamic-model value changing - http://stackoverflow.com/a/32096328/887092 – Kind Contributor Oct 15 '16 at 04:29
6

With Angular 1.3, you can use ng-model-options directive to dynamically assign the model, or bind to an expression.

Here is a plunkr: http://plnkr.co/edit/65EBiySUc1iWCWG6Ov98?p=preview

<input type="text" ng-model="name"><br>
<input type="text" ng-model="user.name" 
ng-model-options="{ getterSetter: true }">

More info on ngModelOptions here: https://docs.angularjs.org/api/ng/directive/ngModelOptions

Mosh Feu
  • 28,354
  • 16
  • 88
  • 135
Rob R
  • 181
  • 1
  • 3
  • 4
  • Forgive me if I'm missing something, but nothing about your plunk appears to include dynamic model assignment. And nothing about ngModelOptions would obviously support that. Could you perhaps clarify? Because it would be super-useful if it did, in fact, work that way... – XML Mar 03 '15 at 06:55
  • @XMLilley "getterSetter: boolean value which determines whether or not to treat functions bound to ngModel as getters/setters." – Chris Bolton Mar 17 '15 at 22:32
  • Thanks Rob for bringing this to my attention, I have updated my answer and linked to yours. – Justus Wingert Mar 27 '15 at 10:16
1

This is my approach to support deeper expression, e.g. 'model.level1.level2.value'

<input class="form-control" ng-model="Utility.safePath(model, item.modelPath).value">

where item.modelPath = 'level1.level2' and Utility(model, 'level1.level2') is the utility function that returns model.level1.level2

0

<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<body>

    <div ng-app="myApp" ng-controller="myCtrl">
        <form name="priceForm" ng-submit="submitPriceForm()">
            <div ng-repeat="x in [].constructor(9) track by $index">
                <label>
                    Person {{$index+1}} <span class="warning-text">*</span>
                </label>
                <input type="number" class="form-control" name="person{{$index+1}}" ng-model="price['person'+($index+1)]" />

            </div>
            <button>Save</button>
        </form>
    </div>

    <script>
        var app = angular.module('myApp', []);
        app.controller('myCtrl', function ($scope) {
            $scope.price = [];
            $scope.submitPriceForm = function () {
                //objects be like $scope.price=[{person1:value},{person2:value}....]
                console.log($scope.price);
            }
        });
    </script>
</body>
</html>
Arun Saini
  • 6,714
  • 1
  • 19
  • 22