64

I'm starting to play around with AngularJS forms in jsfiddle and I've come across a problem already where a very simple form example is not working as expected. All I have is a named form and it's not showing up in scope for some reason (I'm expecting a FormController instance).

I have a fiddle set up, and below is the basic code:

HTML

<div id="mainContainer" ng-app="angularTest" ng-controller="MainCtrl">
    <h1>The Form</h1>
    <form name="theForm">
        <input name="myName" type="text" ng-model="model.name" />
        <input name="submit" type="submit" />
    </form>
</div>

JS

var app = angular.module('angularTest', []);

app.controller('MainCtrl', ['$scope', function($scope) {
    $scope.model = { name: 'Model Name' };
    console.log($scope.theForm); //displays 'undefined'
}]);

I can't find a lot of straightforward examples of this on jsfiddle, so I wasn't sure if this could be some strange interaction with sites like it (most examples I find aren't using formal controllers). I've tried on Plunker to check as well, but I encounter the same problem.

I'm sure I'm missing something super obvious, but I can't see many other things to change or tweak here. Any help is greatly appreciated!

1
  • 44
    I ran into this problem when my form was nested inside an element with ng-if. Using ng-show instead fixed the problem. Commented Jan 8, 2015 at 17:18

7 Answers 7

90

A good way to perform this without using watch (which is a bit overkill) is to define an object in the scope into which you will register the form.

HTML

<div id="mainContainer" ng-app="angularTest" ng-controller="MainCtrl">
    <h1>The Form</h1>
    <form name="form.theForm">
        <input name="myName" type="text" ng-model="model.name" />
        <input type="button" value="Here the scope" ng-click="display()"/>
        <input name="submit" type="submit" />
    </form>
</div>

JS

var app = angular.module('angularTest', []);

app.controller('MainCtrl', ['$scope', function($scope) {
    $scope.model = { name: 'Model Name' };
    $scope.form = {};
    $scope.display = function () {
        console.log($scope.form.theForm);
    }
}]);
Sign up to request clarification or add additional context in comments.

2 Comments

This might not be the best solution to this problem, but it is generally a good way to bring form validation into the controller. Thanks for sharing.
Note trying to bind directly to form won't work it must be form.someNewName.
41

The form only registers itself with the $scope of the controller after the controller has initially run. Therefore the console.log($scope.theForm) will return undefined even if everything is setup correctly.

In your example to react to the presence of theForm, you can setup a watcher on theForm to set debug text depending its presence:

$scope.$watch('theForm', function(theForm) {
    if(theForm) { 
        $scope.formDebugText = 'Form in Scope';
    }
    else {
        $scope.formDebugText = 'Form is Undefined';
    }        
});

which can be seen in action at http://jsfiddle.net/9k2Jk/1/

4 Comments

Ah, I should have known better. I've already dealt with these types of asynchronous loading behaviors! Thanks! It's all going to sink in eventually ;)
If you need to check it only once it's worth removing watch after form reports it's presence var formWatchUnbind = $scope.$watch and then if(theForm){formWatchUnbind(); //do w/e you want here}
this does not work for me. $scope.theForm is always undefined
@michal : Thanks for answer. Any documentation which says 'form' register to scope of controller only when controller had gone through initial run. Would be helpful. thanks
32

What fixed it for me was to use a parent object on the $scope.

In the controller:

$scope.forms = {};
$scope.registerUser = function registerUser() {
    if ($scope.forms.userForm.$valid) {
        alert('submit');
    }
};

In the template:

<form name="forms.userForm" ng-submit="registerUser()">

The reason:

If you use <form name="userForm"... instead of <form name="forms.userForm"... it attaches the form to a child scope, but because $scopes use prototypical inheritance, as soon as I declared an empty object literal on the original $scope the form was attached to it instead.

1 Comment

So I added ng-controller="MainCtrl" to the form and that seemed to get it to bind directly to that scope. Is there anything wrong about this approach?
23

This is the recommended way to access form variable: https://docs.angularjs.org/guide/forms - Binding to form and control state

In HTML:

<form name="form">  
<input type="button" ng-click="reset(form)" value="Reset" />
</form>

Youl will pass the name of the form as a variable to the function.

In JS :

$scope.reset = function(form) { 
  alert(form); 
};

Variable 'form' should NOT be undefined now.

1 Comment

How would you test this?
4

In my case I used ng-include to generate a sub scope, so within current scope the property is undefined, to be safe and prevent sub-scope issue, we should use reversed variable to name the form just like form.theForm.

But make sure that you've declared this form name in your controller in advance.

<form name="form.theForm">
    <input name="myName" type="text" ng-model="model.name" />
    <input name="submit" type="submit" />
</form>

app.controller('MainCtrl', ['$scope', function($scope) {
    $scope.model = { name: 'Model Name' };
    //You have to declare here, else it will trigger undefined error still.
    $scope.form = {
      theForm: {} 
    };

    $scope.reset = function(){
       $scope.form.theForm.$setPristine();
    }
}]);

Comments

1

You can re-initialize the form from your controller before you try to access $scope.form with this line of code. $scope.form will then be available.

angular.element(jQuery('.form-control')).triggerHandler('input')

Comments

0

I wrote a directive to deal with this issue. Its an attribute that you can put on your form and pass a function to which will execute when the form is ready.

Javascript

angular.module('app').directive('formInit', function(){
  return {
    restrict: 'A',
    scope: {
      init: '&formInit'
    },
    controller: function($scope, $element){
      var name = null;
      if ($element[0] && $element[0].name){
        name = $element[0].name;
      }

      var listener = $scope.$watch('init', function(){
        if ($scope[name] && $scope.init){
          $scope.init();
          listener();
        }
      });
    }
  };
});

Example HTML

<form name="test" form-init="testing()">

Where testing is a function on your controllers scope.

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.