10

Giving the code inside of my controller:

$scope.entity = {
  firstName: 'Jack',
  lastName: 'Bauer',
  location: {
    city: 'New York'
  }
};
$scope.path = 'location.city';

How do I dynamically bind ngModel to the property of the entity specified by path?

I've tried something like this, but to no avail:

<input ng-model="'entity.' + path">

4 Answers 4

7

Slava, I'm not too sure if this is a good idea to begin with. But anyhow, You need to make your model getterSetter aware by adding this property to your input ng-model-options="{ getterSetter: true }. Then you need a function in your controller that builds a getterSetter out of a sting.

<input type="text" ng-model="propertify('entity.' + path)" ng-model-options="{ getterSetter: true }">

That's how the resulting template would look.

Luckily angular has an $parse service that makes this a lot easier. so something like this would need to be in your controller, or even better in a injected service.

  $scope.propertify = function (string) {
      var p = $parse(string);
      var s = p.assign;
      return function(newVal) {
          if (newVal) {
              s($scope,newVal);
          }
          return p($scope);
      } ;
  };

That will return a getter-setter function that handles this for you. see it in action in this plunk

Sign up to request clarification or add additional context in comments.

5 Comments

Sander, you are an Angular-ninja for real, thank you very much! = )
I'm still getting the "non-assignable" error with this... but your plunkr is working fine 🤔
I had the wrong angular version 🙈 - everything's working great, thank you!
one thing I'd do different is to use if(angular.isDefined(newVal)) {} so empty values are assigned as well, like false or ""
Great solution! Unfortunately it does not recognize array expressions like data.model.step[0].theText, data.model.step[1].theText. It automatically converts the numbers 0, 1, 2 into object keys instead of making it an array.
7

Update

It's not working as expected, the value is displayed correctly, but can not be changed. The correct solution is provided by Sander here.


Incorrect solution

Wow, solved it accidentally:

<input type="text" ng-model="$eval('entity.' + path)">

And here's the Plunk.

I hope it will help someone.

Comments

2

You could use the bracket notation with a little modification, as you want to bind to a nested property. You have to split the path to the property:

<input ng-model="entity[locationKey][cityKey]"/>

Controller:

$scope.locationKey = 'location';
$scope.cityKey = 'city';

See js fiddle

1 Comment

Thank you, but it won't do, I store the full path in a single variable, that is the core of the problem.
2

After reading and using Sander Elias' answer, I was using this, but ran into another problem.

When combining his result with ng-required="true" in the <input> you could not empty the field, because when the field would be empty, the newVal is passed as undefined.

After some more research, I found an isssue on GitHub that addresses and solves this problem.

Here is what Sander's and the GitHub answer combined look like:

$scope.propertify = function (string) {
    var property = $parse(string);
    var propAssign = property.assign;
    return function (newVal) {
        if (arguments.length) {
            newVal = angular.isDefined(newVal)?  newVal : '';
            propAssign($scope, newVal);
        }
        return property($scope);
    };
};

The argument.length reflects the number of values that are passed to the getter/setter and will be 0 on a get and 1 on a set.

Besided that, I added the angular.isDefined() as Sumit suggested in a comment to also save false and empty ("") values.

Here is an updated Plunker

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.