I have an array containing variable names, example:
var names = ['address.street','address.city'];
I want to create input fields out of these, and I'm using AngularJS. No big deal:
<div ng-repeat="n in names">
<input type="text" ng-model="data[n]" />
</div>
The resulting $scope.data
object is:
{
"address.street" : ...,
"address.city" : ...
}
Which, by the way, is not exactly what I'm trying to achieve. Is there a syntax that could lead me to an object as the following one as result?
{
"address" : {
"street" : ...,
"city" : ...
}
}
Please consider that I can have even more than one level of nesting, this is just an example.
I do not think models should be accessed this way.
However, this was curious question and the solution is a bit fun.
The problem is that ng-model
requires a reference and thought Javascript sends modifiable copies of objects, it does not have pass-by-reference semantics and we cannot just pass a string to ng-model
.
However, arrays and objects do have this property. Hence, the solution is to return an array whose 0
th element will be the reference for ng-model
. This is also the hacky part since all your objects are now arrays with '1' element.
The other solution would be to return an object for each case instead of 1 element array.
Here is the solution using an embedded object: http://plnkr.co/edit/MuC4LE2YG31RdU6J6FaD?p=preview which in my opinion looks nicer.
Hence, in your controller:
$scope.getModel = function(path) {
var segs = path.split('.');
var root = $scope.data;
while (segs.length > 0) {
var pathStep = segs.shift();
if (typeof root[pathStep] === 'undefined') {
root[pathStep] = segs.length === 0 ? { value: '' } : {};
}
root = root[pathStep];
}
return root;
}
And in your template:
<p>Hello {{data.person.name.value}}!</p>
<p>Address: {{data.address.value}}</p>
<input ng-model="getModel('person.name').value" />
<input ng-model="getModel('address').value" />
Here is the shortest (albeit hacky) solution I could come up with: http://plnkr.co/edit/W92cHU6SQobot8xuElcG?p=preview
Hence, in your controller:
$scope.getModel = function(path) {
var segs = path.split('.');
var root = $scope.data;
while (segs.length > 0) {
var pathStep = segs.shift();
if (typeof root[pathStep] === 'undefined') {
root[pathStep] = segs.length === 0 ? [ '' ] : {};
}
root = root[pathStep];
}
return root;
}
And in your template:
<p>Hello {{data.person.name[0]}}!</p>
<p>Address: {{data.address[0]}}</p>
<input ng-model="getModel('person.name')[0]" />
<input ng-model="getModel('address')[0]" />
The answer provided by @musically_ut is good but has one significant flaw: It will work great if you're creating a new model but if you have an pre-defined existing model that you can't refactor into the '.value' structure or the array structure, then you're stuck...
Clearly that was the case for me... (and I assume that was the case for @LorenzoMarcon too, as he's implying that he'll have to "post-process" the result and transform it to a different format)
I ended up elaborating on @musically_ut's solution:
$scope.getModelParent = function(path) {
var segs = path.split('.');
var root = $scope.data;
while (segs.length > 1) {
var pathStep = segs.shift();
if (typeof root[pathStep] === 'undefined') {
root[pathStep] = {};
}
root = root[pathStep];
}
return root;
};
$scope.getModelLeaf = function(path) {
var segs = path.split('.');
return segs[segs.length-1];
};
(note the change in the while loop index)
Later on you access the dynamic field like this:
<input ng-model="getModelParent(fieldPath)[ getModelLeaf(fieldPath) ]"/>
The idea is (as explained in @musically_ut's answer) that JS can't pass a string by reference, so the hack around it I pass the parent node (hence the while loop inside 'getModelParent' stops before the last index) and access the leaf node (from 'getModelLeaf') using an array like notation.
Hope this makes sense and helps.
If you can restructure your models, you can simply do like this:
$scope.names = {
"address":[
"street",
"city"
]
};
$scope.data = {
address:{
street:"",
city:""
}
};
<div ng-repeat="(key, values) in names">
<div ng-repeat="value in values">
<input type="text" ng-model="data[key][value]" />
</div>
</div>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With