desmondlee
desmondlee

Reputation: 1703

Angular bind as object

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

Upvotes: 0

Views: 248

Answers (4)

Dr. Cool
Dr. Cool

Reputation: 3721

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>

Upvotes: 1

noppa
noppa

Reputation: 4076

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.

Upvotes: 1

Dr. Cool
Dr. Cool

Reputation: 3721

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;
}

Upvotes: 0

alphapilgrim
alphapilgrim

Reputation: 3975

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>

Upvotes: 0

Related Questions