Andreas Andreas - 1 month ago 17
AngularJS Question

Bind dynamically set ng-model attribute for text field

After searching around for hours I am still unable to find an answer to my problem. I am populating a dynamic form with text fields based on values from a database, but am unable to successfully bind the fields to my model. Here's the scenario:

I've got a "project" model in my controller containing lots of project related information (name, start date, participants, category etc), but let's just focus on the "project.name" property for now. In the database I configure "forms" with a number of associated fields, where each field has a property that points to which property it corresponds to in my view model (e.g. "project.name"). At runtime I add these fields to an HTML form dynamically and attempt to set the ng-model attribute to the "modelBinding" value, in this case "project.name".

<div ng-repeat="formField in form.formFields">
<input ng-model="formField.modelBinding" /></div>


This will result in a text box being added to my form, with ng-model="formField.modelBinding" and the textbox value = 'project.data'.

What I am trying to achieve is to set ng-model = 'project.data', in other words replace formField.modelBinding with the value of formField.modelBinding.

One approach that seemed logical was

<input ng-model = "{{formField.modelBinding}}" />


but this is obviously not going to work. I've tried to insert the HTML tags with ng-bind-html but this seems to only work with ng-bind, not ng-model.

Any suggestions?

Answer

Assuming that you are trying to bind a value to a model from a name that you have within the formField you can create a directive (aka ngModelName) to bind your model by name from this value.

Observation: My first thought was using a simple accessor like model[formField.modelBinding] which would simple bind the formField.modelBinding into a model member on scope. However, I didn't use the property accessor because it would create a property named by formField.modelBinding value and not the correct object hierarchy expected. For example, the case described on this question, project.data would create an object { 'project.data': 'my data' } but not { 'project': { data: 'my data'}} as it should.

angular.module('myApp', [])
  .directive('ngModelName', ['$compile', function ($compile) {
    return {
        restrict: 'A',
        priority: 1000,
        link: function (scope, element, attrs) {
          
          scope.$watch(attrs.ngModelName, function(ngModelName) {
                // no need to bind a model
                if (attrs.ngModel == ngModelName || !ngModelName) return;

                element.attr('ng-model', ngModelName);
                
                // remove ngModel if it's empty
                if (ngModelName == '') {
                    element.removeAttr('ng-model');
                }

                // clean the previous event handlers,
                // to rebinded on the next compile
                element.unbind();

                //recompile to apply ngModel, and rebind events
                $compile(element)(scope);
            });
          }
    };
  }])
  .controller('myController', function ($scope) {
    $scope.form = {
      formFields: [
        {
          modelBinding: 'model.project.data'
        }
      ]
    };
  
    $scope.model = {};
  });

angular.element(document).ready(function () {
  angular.bootstrap(document, ['myApp']);  
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-controller="myController">
  <div ng-repeat="formField in form.formFields">
      <input ng-model-name="formField.modelBinding" placeholder="{{ formField.modelBinding }}" />
  </div>
  <div>
    <pre>{{ model | json }}</pre>
  </div>
</div>