0

Rephrasing question for clarification purpose.

Plunkr
View:

<input type="text" ng-model="form['data']['sampleData']">
    <input type="text" ng-model="form[bindingPrefix][bindingSuffix]">
    <input type="text" ng-model="form[bindingValue]">

Controller:

    $scope.form = {
    data: {
      sampleData: '123'
    }
  };

  $scope.bindingValue = 'data.sampleData';
  $scope.bindingPrefix = 'data';
  $scope.bindingSuffix = 'sampleData';

Desired effect: I would expect form[bindingValue] to yield the effect as form[bindingPrefix][bindingSuffix] without purposely separating bindingValue to bindingPrefix and bindingSuffix as bindingValue could be a dynamic value such as data.sampleData.childData, data.sampleData.childData.childChildData in an array for ng-repeat the model.

P/S: bindingValue is something that pass from Server side and i have no control over it.

========================================================================== Might work from this plunkr over here. Ideally, the view should not be modified.Click here

desmondlee
  • 1,643
  • 4
  • 21
  • 33
  • Might you be using controller as syntax? Please include a bigger snippet of controller. – alphapilgrim May 03 '16 at 21:56
  • @alphapilgrim Hi what do you mean by using controller as syntax? – desmondlee May 03 '16 at 21:59
  • Still dont understand('to yield the effect as '), you want the [bindingPrefix][bindingSuffix] to have same model value as form[bindingValue]? – alphapilgrim May 03 '16 at 22:27
  • JavaScript just doesn't work that way. You can't use the dot notation and the bracket notation simultaneously like that. You could write a function that would do it for you, put it in the controller, and call it with your dot-notation string... – Heretic Monkey May 03 '16 at 22:29
  • For the function, see e.g. http://stackoverflow.com/q/6393943/215552 – Heretic Monkey May 03 '16 at 22:31
  • @MikeMcCaughan Could you show how it could be achieve? I have updated the plunkr [here](http://plnkr.co/edit/C0QWMMUxUvEgGiU0GWPx?p=preview) using the function but now everything breaks. – desmondlee May 03 '16 at 22:48

4 Answers4

1

I created a directive called my-dynamic-model which is referenced by your <input> elements. This contains a reference to the scope variable which is $parsed to refer to the correct $scope.bindingValue array.

See the attached working plunkr.

You can now specify the hierarchy in $scope.bindingValue to be as deep as you want and it will properly update that $scope variable. Just make sure that it's a complete $scope object hierarchy path.

CODE:

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

app.controller('MyController', function($scope) {
  $scope.form = {
    data: { 
      sampleData: '1234',
      sampleData1: {
        sampleData2: '2345'
      }
    }
  };

  $scope.bindingValue = ['form.data.sampleData', 'form.data.sampleData1.sampleData2'];
});


app.directive('myDynamicModel', function( $parse, $log ) {
    return function( scope, el, attrs ) {
        var model = $parse( attrs.myDynamicModel );
        var finalModel = $parse(model(scope));

        finalModel.assign(scope, finalModel(scope));
        scope.$apply();

        el.bind('keyup', function() {
            finalModel.assign(scope, el.val());
            if (!scope.$$phase) scope.$apply();
        })
    }
});

HTML:

<div ng-controller="MyController">
    <input type="text" ng-model="form.data.sampleData" my-dynamic-model="bindingValue[0]" placeholder="Update me">
    <input type="text" ng-model="form.data.sampleData1.sampleData2" my-dynamic-model="bindingValue[1]" placeholder="Update me too">

  <div>{{ form.data.sampleData }}</div>
  <div>{{ form.data.sampleData1.sampleData2 }}</div>
</div>
Dr. Cool
  • 3,713
  • 3
  • 22
  • 26
  • Hi, it seems that it still doesnt change the fact of assumption that there is only 2 hierarchy object eg: `bindingPrefix.bindingSuffix` but it could be in the form of `bindingPrefix.bindingSuffix1.bindingSuffix2` etc. Furthermore, it involves two input field. It should be done in a single input field `` where it should be able to retrieve value of `form.data.sampleData` as well as performing update on the same model. – desmondlee May 03 '16 at 23:15
  • I have updated new plunkr here to better illustrate [Click here](http://plnkr.co/edit/j2PaIGlK5o5OdJmqbtGj?p=preview) – desmondlee May 03 '16 at 23:24
  • It only uses one input field which is the second one. – Dr. Cool May 03 '16 at 23:25
  • If you really don't want to use a scope function and you prefer to use the format ``form['data']['sampleData']`` or ``form[value]``, you should create a directive. Then you could create something like: ```` – Dr. Cool May 03 '16 at 23:29
  • I see what you mean here. But how could it be possible to instantiate the value at first place? The initial value should according to the value of the object but currently is null – desmondlee May 04 '16 at 00:17
  • Which value do you want to instantiate? – Dr. Cool May 04 '16 at 00:24
  • From your plunker you created, the input with placeholder `updateme` should have the value of `$scope.form.data.sampleData` by default while the input with placeholder `updateme2` should have the value of `$scope.form.data.sampleData1.sampleData2` by default. – desmondlee May 04 '16 at 00:27
  • Check the updated answer and the updated plunkr. Now it initializes the ```` elements properly. – Dr. Cool May 04 '16 at 00:35
  • I just realise that you explictly declare `ng-model="form.data.sampleData"` in the latest update. It defeat the whole purpose of making it dynamic by declaring that way. – desmondlee May 04 '16 at 00:50
  • I hope that with all of the code there, you should be able to do the last few changes to get it working just like you need it. – Dr. Cool May 04 '16 at 01:50
1

Even though the path could be variable length, we can reduce the problem to only use path of one variable. This should work as long as you don't break the structure of the data object (or if you do, remember to run this preparation code again).

So we have data

$scope.form = {
    data: {
      sampleData: '123'//This could be even deeper in the object, can't know for sure
    }
};

but the only variable name that we will need to keep the linkage between the sampleData and the containing object is the last one. "sampleData". All the other property names can be thrown away if we just get a reference to the data obejct and "sampleData" property name.

In controller:

//Get the path from the server, split it to create an array of property names
var path = 'data.sampleData'.split('.');
//We'll start changing these soon
var prevValue = $scope.form, nextValue;

for(var i = 0; i < path.length - 1; i++){//Note that we are not looping all the way through (-1)!
    //Get all the properties one by one
    nextValue = prevValue[path[i]];
    if(nextValue == undefined){
        //This is an error, the data didn't conain the property that it was supposed to.
        //It's up to you how to handle this. Doing the following will add the missing properties and keep things working.
        nextValue = prevValue[path[i]] = {};
    }
    //The prevValue will first be $scope.form, then form.data
    prevValue = nextValue;
 }
 //$scope.bindingContainer is a reference to $scope.form.data object
 $scope.bindingContainer = prevValue;
 //$scope.bindingValue is the last property name, "sampleData"
 $scope.bindingValue = path[path.length-1];

In template:

<input type="text" ng-model="bindingContainer[bindingValue]">

And everything should just work (again, as long as you don't change $scope.form.data = somethingElse).

We are cheating a bit, of course, because now the template does not reference to the original $scope.form object at all. It shouldn't matter though, because it has a reference to the data object and its property "sampleData", so as long as $scope.form is referencing to the same data object we've got all we need.

noppa
  • 3,947
  • 21
  • 22
  • Note that this does not work on arrays if the path string references them with typical `'data.someArray[0]'` way. It does work though if you reference it with `'data.someArray.0'` and expanding this to also support the '[index]' notation would only require more sophisticated splitting than `.split('.');` – noppa May 04 '16 at 00:08
  • How could you initialise the value that is declare in `$scope.form.data.sampleData` in the input then? – desmondlee May 04 '16 at 00:21
  • What do you mean by initializing? The template example is there in the answer. If you want to give it default value, you could do `$scope.bindingContainer[$scope.bindingValue] = 'something' ` in the controller. – noppa May 04 '16 at 00:31
  • I thought the whole point of this was to use the data from the form object as values for the input. That's how the above code works already. – noppa May 04 '16 at 00:36
0

Or this might be along the lines your currently writing with angular, also here is an excellent article on controller as syntax.

function ExampleCtrl($scope) {

    $scope.bindingValue = data.sampleData;
    $scope.bindingPrefix = 'data';
    $scope.bindingSuffix = 'sampleData';
  }
  // Controller or Controller as syntax is a reference the controller just a short-hand name.
<body ng-app="ExampleApp">
  <div class="example" ng-controller="ExampleCtrl">
    <input type="text" ng-model="bindingValue">
  </div>
</body>

Try something like this, maybe some syntax differences:

function ExampleCtrl() {
  var ctrl = this;

  ctrl.bindingValue = data.sampleData;
  ctrl.bindingPrefix = 'data';
  ctrl.bindingSuffix = 'sampleData';
}
<body ng-app="ExampleApp">
  <div class="example" ng-controller="ExampleCtrl as ctrl">
    <input type="text" ng-model="ctrl.bindingValue">
  </div>
</body>
alphapilgrim
  • 3,761
  • 8
  • 29
  • 58
0

In your controller, create a scope object like this:

$scope.data = {
    sampleData: {
        childSampleData: null
    },
    anotherItem: null,
    moreData: {
        Child1: null,
        Child2: null
    }
}

Your HTML should reference the scope object like this:

<input type="text" ng-model="data.sampleData.childSampleData">
<input type="text" ng-model="data.anotherItem">
<input type="text" ng-model="data.moreData.Child1">
<input type="text" ng-model="data.moreData.Child1">

Unfortunately, you can't reference an ngModel in the way that your code shows. So it's incorrect to say ng-model="form[bindingPrefix][bindingSuffix]" because you can't access the form object here. But you can access child objects using dot notation as I did in the HTML.

If you're not sure which ngModel needs to be updated, you should instead use a function like this:

<input type="text" ng-model="item1" ng-change="updateModel()">

$scope.updateModel = function() {
    $scope.data[bindingPrefix][bindingSuffix] = $scope.item1;
}
Dr. Cool
  • 3,713
  • 3
  • 22
  • 26
  • Hi, thanks for your comment. Maybe my question clarification is not enough. I have updated the question – desmondlee May 03 '16 at 22:23
  • I added a new answer below instead of updating this one, since the new answer is more specific to your clarification. – Dr. Cool May 03 '16 at 23:04