Getting form controls from FormController

28,980

Solution 1

For a direct solution to the question, modify @lombardo's answer like so;

     var dirtyFormControls = [];
     var myForm = $scope.myForm;
     angular.forEach(myForm, function(value, key) {
         if (typeof value === 'object' && value.hasOwnProperty('$modelValue') && value.$dirty)
             dirtyFormControls.push(value)                        
     });

The array 'dirtyFormControls' will then contain the form controls that are dirty.

You can also use this trick to show error messages on form submission for 'Required' validations and all others. In your submit() function you will do something like;

 if (form.$invalid) {
     form.$setDirty();              
     angular.forEach(form, function(value, key) {
         if (typeof value === 'object' && value.hasOwnProperty('$modelValue'))
             value.$setDirty();                        
     });
    //show user error summary at top of form.
     $('html, body').animate({
         scrollTop: $("#myForm").offset().top
     }, 1000);
     return;
 }

And in your form you will show error messages with

    <span ng-messages="myForm['subject-' + $index].$error" ng-show="myForm['subject-' + $index].$dirty" class="has-error">
        <span ng-message="required">Course subject is required.</span>
    </span>

The above solution is useful when you have dynamically generated controls using 'ng-repeat' or something similar.

Solution 2

You can use the following code to iterate the controls:

    var data = {};
    angular.forEach(myForm, function (value, key) {
        if (value.hasOwnProperty('$modelValue'))
            data[key] = value.$modelValue;
    });

Solution 3

try simply with from within your controller:

$scope.checkForm = function(myFormName){
     console.log(myFormName.$invalid);
}

UPDATE:

<div ng-controller="MyController">
                <form name="form" class="css-form" novalidate>
                    <input type="text" ng-model="user.uname" name="uname" required /><br />
                    <input type="text" ng-model="user.usurname" name="usurname" required /><br />
                    <button ng-click="update(form)">SAVE</button>
                </form>
              </div>

app.controller('MyController',function($scope){
                $scope.user = {};
                $scope.update = function (form){
                    var editedFields = [];
                    angular.forEach($scope.user, function(value, key){
                        if(form[key].$dirty){
                           this.push(key + ': ' + value); 
                        }

                    }, editedFields);
                    console.log(editedFields);
                }
        });
Share:
28,980
robert.bo.roth
Author by

robert.bo.roth

Currently working on some upcoming projects with a small team in Austin, TX. Primarily using AngularJS and Symfony2 to develop new mobile and web-based healthcare applications. Always looking for inspiration of something fun to build on my own, but haven't been able to do much recently.

Updated on June 22, 2020

Comments

  • robert.bo.roth
    robert.bo.roth about 4 years

    I need a way to loop through the registered controls of an AngularJS form. Essentially, I'm trying to get all the $dirty controls, but there's no array of the controls (the FormController has a number of different properties/functions in addition to containing the controls themselves - each as its' own object).

    I've been looking at the source code, and I see that there is a controls array in the FormController that is exactly the array I'm looking for. Is there a way to get access to this value, or extend the FormController to include a function that returns this controls array?

    Edit: Plnkr demo

    Also, I realized that technically I could check the first character in the key string for "$", but I'd like to avoid that in case the FormController/directive changes in a future version of Angular.

    Edit 2: Another clarification: My goal in all of this is to be able to determine which specific fields are $dirty, whether by looping through the entire list of controls (not including the $dirty, $invalid, $error, $name, and other properties that live in the Form object as it is) or by extending the FormController and creating a function that returns only the controls which are currently dirty (and not equal to their starting values)

    Edit 3: The solution I'm looking for needs to be applicable to forms/models of different structures. The models on the scope are generated via AJAX, so their structure is already set (I'd like to avoid having to hardcode a new structure for all the data I'm already receiving via AJAX). Also, I'm looking to use this form submission process across multiple forms/models, and each of them have differing JSON structures - as they apply to different entities in our Object Model. This is why I've chosen to ask for a way to get access to the controls object in the FormController (I'll post the code from FormController below), because it's the only place where I can get a flat array of all of my fields.

    function FormController(element, attrs) {
    
    
    var form = this,
          parentForm = element.parent().controller('form') || nullFormCtrl,
          invalidCount = 0, // used to easily determine if we are valid
          errors = form.$error = {},
          controls = [];
    
      // init state
      form.$name = attrs.name || attrs.ngForm;
      form.$dirty = false;
      form.$pristine = true;
      form.$valid = true;
      form.$invalid = false;
    
      parentForm.$addControl(form);
    
      // Setup initial state of the control
      element.addClass(PRISTINE_CLASS);
      toggleValidCss(true);
    
      // convenience method for easy toggling of classes
      function toggleValidCss(isValid, validationErrorKey) {
        validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
        element.
          removeClass((isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey).
          addClass((isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey);
      }
    
      /**
       * @ngdoc function
       * @name ng.directive:form.FormController#$addControl
       * @methodOf ng.directive:form.FormController
       *
       * @description
       * Register a control with the form.
       *
       * Input elements using ngModelController do this automatically when they are linked.
       */
      form.$addControl = function(control) {
        controls.push(control);
    
        if (control.$name && !form.hasOwnProperty(control.$name)) {
          form[control.$name] = control;
        }
      };
    
      /**
       * @ngdoc function
       * @name ng.directive:form.FormController#$removeControl
       * @methodOf ng.directive:form.FormController
       *
       * @description
       * Deregister a control from the form.
       *
       * Input elements using ngModelController do this automatically when they are destroyed.
       */
      form.$removeControl = function(control) {
        if (control.$name && form[control.$name] === control) {
          delete form[control.$name];
        }
        forEach(errors, function(queue, validationToken) {
          form.$setValidity(validationToken, true, control);
        });
    
        arrayRemove(controls, control);
      };
    
      // Removed extra code
    }
    

    As you can see, the form itself has the controls array privately available. I'm wondering if there's a way for me to extend the FormController so I can make that object public? Or create a public function so I can at least view the private array?

  • robert.bo.roth
    robert.bo.roth over 10 years
    I don't think my question was clear, so I added a plnkr demo. I don't have any problems detecting if the form is $dirty or $invalid. My problem is that I don't have a good way of looping through all of the form controls (the fields themselves) to determine which specific fields are $dirty. My main goal is to submit only the fields that have been edited.
  • robert.bo.roth
    robert.bo.roth over 10 years
    This is pretty much what I've got at the moment, but my problem is that I'm having to iterate over an unstructured/multi-dimensional JSON object in my scope, which is why I'd prefer to be able to loop through the flat array of fields that's in the FormController
  • Bryan Larsen
    Bryan Larsen almost 10 years
    I had to add typeof value === 'object' && to your if condition. Otherwise you can get errors like "TypeError: Cannot read property 'hasOwnProperty' of undefined"
  • robert.bo.roth
    robert.bo.roth over 8 years
    I totally forgot I had submitted this question, and ended up doing something very similar to what you posted here. I actually ended up extending the ngSubmit directive so that it runs all of my validation code for ALL of my <form> elements that have ngSubmit functions.
  • faitha
    faitha over 8 years
    I hope that solved your issue without getting in your way! Using directives is probably the best way of doing custom stuff in AngularJS, but there are lots of built in stuffs that can be taken advantage of.
  • user1067920
    user1067920 almost 8 years
    Just take care of ng-form. This method does not handle them.
  • Daniel Z.
    Daniel Z. over 7 years
    @BryanLarsen Better use angular.isObject(value) for this. It also checks for null values, because typeof null is also an object.
  • jrharshath
    jrharshath almost 7 years
    I believe this solution works only for controls that have the "name" attribute set.