Dynamic form name attribute <input type="text" name="{{ variable-name }}" /> in Angularjs

How would someone use formName.inputName.$valid when the "inputName" was dynamically created?

  <form name="formName">
    <input ng-repeat="(variable) in variables"
           type="text" name="variable.name"
           ng-model="variable.name" required />
 </form>

The output of the HTML input attribute 'name' would be the string "variablename", which would applied to ALL repeated inputs.

If we tried this

<form name="formName">
  <input ng-repeat="(variable) in variables"
         type="text" name="{{ variable.name }}"
         ng-model="variable.name" required />
</form>

The output of the HTML input attribute 'name' would be the string"{{ variable.name }}", which would be applied to ALL repeated inputs.

In either of these two conditions, a name attribute for each of the repeated input elements would not be created dynamically; ALL inputs would share the same input name. Not much good if you wanted to call a specific input based on a specific name.

  • need to use dynamic name values
  • need to be able to call $scope.formName.dynamicName.$valid
  • need to be able to call $scope.formName.$valid
  • need dynamic name input fields to be added to nested form, or master form

Solution 1:

Looks like Angular 1.3 fixed this (https://stackoverflow.com/a/32907176/3854385)

This is now possible with Angular 1.3+:

<form name="vm.myForm" novalidate>
  <div ng-repeat="p in vm.persons">
    <input type="text" name="person_{{$index}}" ng-model="p" required>
    <span ng-show="vm.myForm['person_' + $index].$invalid">Enter a name</span>
  </div>
</form>

Demo

In some cases an inner form is a good solution if you can just pass the information: (https://stackoverflow.com/posts/12044600/) To solve the 'dynamic name' problem you need to create an inner form (see ng-form):

<div ng-repeat="social in formData.socials">
      <ng-form name="urlForm">
            <input type="url" name="socialUrl" ng-model="social.url">
            <span class="alert error" ng-show="urlForm.socialUrl.$error.url">URL error</span>
            <button ng-click="doSomething(urlForm.socialUrl.$valid)">Test</button>
      </ng-form>
  </div>

The other alternative would be to write a custom directive for this.

Here is the jsFiddle showing the usage of the ngForm: http://jsfiddle.net/pkozlowski_opensource/XK2ZT/2/

Solution 2:

I could not find the answer that satisfied some or all of these needs. This is what I came up with.

There may be a better way, so please share your thoughts.
I am using Angularjs 1.3.0-beta.8

I have a form with multi-nested directives that all contain input(s), select(s), etc... These elements are all enclosed in ng-repeats, and dynamic string values.

This is how to use the directive:

<form name="myFormName">
  <nested directives of many levels>
    ex: <input ng-repeat=(index, variable) in variables" type="text"
               my-name="{{ variable.name + '/' + 'myFormName' }}"
               ng-model="variable.name" required />
    ex: <select ng-model="variable.name" ng-options="label in label in {{ variable.options }}"
                my-name="{{ variable.name + '/' + 'myFormName' }}"
        </select>
</form>

Note: you can add and index to the string concatenation if you need to serialize perhaps a table of inputs; which is what I did. However, dynamic name inputs means you may not know the name of the form input, so how would you call $scope.formName.??????. You could iterate of the $scope.formName object to get keys that match a certain value. That means string concatenation like this:

my-name="{{ dynamicString + hello + '/' + 'myFormName' }}"

Then in $scope.myFormName you would find any form input name by just iterating over the object and gathering any keys that included 'hello'.

app.directive('myName', function(){

  var myNameError = "myName directive error: "

  return {
    restrict:'A', // Declares an Attributes Directive.
    require: 'ngModel', // ngModelController.

    link: function( scope, elem, attrs, ngModel ){
      if( !ngModel ){ return } // no ngModel exists for this element

      // check myName input for proper formatting ex. something/something
      checkInputFormat(attrs);

      var inputName = attrs.myName.match('^\\w+').pop(); // match upto '/'
      assignInputNameToInputModel(inputName, ngModel);

      var formName = attrs.myName.match('\\w+$').pop(); // match after '/'
      findForm(formName, ngModel, scope);
    } // end link
  } // end return

  function checkInputFormat(attrs){
    if( !/\w\/\w/.test(attrs.rsName )){
      throw myNameError + "Formatting should be \"inputName/formName\" but is " + attrs.rsName
    }
  }

  function assignInputNameToInputModel(inputName, ngModel){
    ngModel.$name = inputName
  }

  function addInputNameToForm(formName, ngModel, scope){
    scope[formName][ngModel.$name] = ngModel; return
  }

  function findForm(formName, ngModel, scope){
    if( !scope ){ // ran out of scope before finding scope[formName]
      throw myNameError + "<Form> element named " + formName + " could not be found."
    }

    if( formName in scope){ // found scope[formName]
      addInputNameToForm(formName, ngModel, scope)
      return
    }
    findForm(formName, ngModel, scope.$parent) // recursively search through $parent scopes
  }
});

This should handle many situations where you just don't know where the form will be. Or perhaps you have nested forms, but for some reason you want to attach this input name to two forms up? Well, just pass in the form name you want to attach the input name to.

What I wanted, was a way to assign dynamic values to inputs that I will never know, and then just call $scope.myFormName.$valid.

This may be an overkill, and a better solution exists in 1.3+. I couldn't find it in the time I had. This works for me now.

Good luck! Hope this helps someone!!!!