@ngdoc directive
@name ngForm
@restrict EAC

@description
Helper directive that makes it possible to create control groups inside a
{@link ng.directive:form `form`} directive.
These "child forms" can be used, for example, to determine the validity of a sub-group of
controls.

<div class="alert alert-danger">
**Note**: `ngForm` cannot be used as a replacement for `<form>`, because it lacks its
[built-in HTML functionality](https://html.spec.whatwg.org/#the-form-element).
Specifically, you cannot submit `ngForm` like a `<form>` tag. That means,
you cannot send data to the server with `ngForm`, or integrate it with
{@link ng.directive:ngSubmit `ngSubmit`}.
</div>

@param {string=} ngForm|name Name of the form. If specified, the form controller will
be published into the related scope, under this name.

 
@ngdoc directive
@name form
@restrict E

@description
Directive that instantiates
{@link form.FormController FormController}.

If the `name` attribute is specified, the form controller is published onto the current scope under
this name.

## Alias: {@link ng.directive:ngForm `ngForm`}

In AngularJS, forms can be nested. This means that the outer form is valid when all of the child
forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so
AngularJS provides the {@link ng.directive:ngForm `ngForm`} directive, which behaves identically to
`form` but can be nested. Nested forms can be useful, for example, if the validity of a sub-group
of controls needs to be determined.

## CSS classes
- `ng-valid` is set if the form is valid.
- `ng-invalid` is set if the form is invalid.
- `ng-pending` is set if the form is pending.
- `ng-pristine` is set if the form is pristine.
- `ng-dirty` is set if the form is dirty.
- `ng-submitted` is set if the form was submitted.

Keep in mind that ngAnimate can detect each of these classes when added and removed.


## Submitting a form and preventing the default action

Since the role of forms in client-side AngularJS applications is different than in classical
roundtrip apps, it is desirable for the browser not to translate the form submission into a full
page reload that sends the data to the server. Instead some javascript logic should be triggered
to handle the form submission in an application-specific way.

For this reason, AngularJS prevents the default action (form submission to the server) unless the
`<form>` element has an `action` attribute specified.

You can use one of the following two ways to specify what javascript method should be called when
a form is submitted:

- {@link ng.directive:ngSubmit ngSubmit} directive on the form element
- {@link ng.directive:ngClick ngClick} directive on the first
button or input field of type submit (input[type=submit])

To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit}
or {@link ng.directive:ngClick ngClick} directives.
This is because of the following form submission rules in the HTML specification:

- If a form has only one input field then hitting enter in this field triggers form submit
(`ngSubmit`)
- if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter
doesn't trigger submit
- if a form has one or more input fields and one or more buttons or input[type=submit] then
hitting enter in any of the input fields will trigger the click handler on the *first* button or
input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`)

Any pending `ngModelOptions` changes will take place immediately when an enclosing form is
submitted. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit`
to have access to the updated model.

@animations
Animations in ngForm are triggered when any of the associated CSS classes are added and removed.
These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any
other validations that are performed within the form. Animations in ngForm are similar to how
they work in ngClass and animations can be hooked into using CSS transitions, keyframes as well
as JS animations.

The following example shows a simple way to utilize CSS transitions to style a form element
that has been rendered as invalid after it has been validated:

<pre>
//be sure to include ngAnimate as a module to hook into more
//advanced animations
.my-form {
transition:0.5s linear all;
background: white;
}
.my-form.ng-invalid {
background: red;
color:white;
}
</pre>

@example
<example name="ng-form" deps="angular-animate.js" animations="true" fixBase="true" module="formExample">
<file name="index.html">
<script>
angular.module('formExample', [])
.controller('FormController', ['$scope', function($scope) {
$scope.userType = 'guest';
}]);
</script>
<style>
.my-form {
transition:all linear 0.5s;
background: transparent;
}
.my-form.ng-invalid {
background: red;
}
</style>
<form name="myForm" ng-controller="FormController" class="my-form">
userType: <input name="input" ng-model="userType" required>
<span class="error" ng-show="myForm.input.$error.required">Required!</span><br>
<code>userType = {{userType}}</code><br>
<code>myForm.input.$valid = {{myForm.input.$valid}}</code><br>
<code>myForm.input.$error = {{myForm.input.$error}}</code><br>
<code>myForm.$valid = {{myForm.$valid}}</code><br>
<code>myForm.$error.required = {{!!myForm.$error.required}}</code><br>
</form>
</file>
<file name="protractor.js" type="protractor">
it('should initialize to model', function() {
var userType = element(by.binding('userType'));
var valid = element(by.binding('myForm.input.$valid'));

expect(userType.getText()).toContain('guest');
expect(valid.getText()).toContain('true');
});

it('should be invalid if empty', function() {
var userType = element(by.binding('userType'));
var valid = element(by.binding('myForm.input.$valid'));
var userInput = element(by.model('userType'));

userInput.clear();
userInput.sendKeys('');

expect(userType.getText()).toEqual('userType =');
expect(valid.getText()).toContain('false');
});
</file>
</example>

@param {string=} name Name of the form. If specified, the form controller will be published into
related scope, under this name.
function(isNgForm) {
  return ['$timeout', '$parse', function($timeout, $parse) {
    var formDirective = {
      name: 'form',
      restrict: isNgForm ? 'EAC' : 'E',
      require: ['form', '^^?form'], //first is the form's own ctrl, second is an optional parent form
      controller: FormController,
      compile: function ngFormCompile(formElement, attr) {
        // Setup initial state of the control
        formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS);

        var nameAttr = attr.name ? 'name' : (isNgForm && attr.ngForm ? 'ngForm' : false);

        return {
          pre: function ngFormPreLink(scope, formElement, attr, ctrls) {
            var controller = ctrls[0];

            // if `action` attr is not present on the form, prevent the default action (submission)
            if (!('action' in attr)) {
              // we can't use jq events because if a form is destroyed during submission the default
              // action is not prevented. see #1238
              //
              // IE 9 is not affected because it doesn't fire a submit event and try to do a full
              // page reload if the form was destroyed by submission of the form via a click handler
              // on a button in the form. Looks like an IE9 specific bug.
              var handleFormSubmission = function(event) {
                scope.$apply(function() {
                  controller.$commitViewValue();
                  controller.$setSubmitted();
                });

                event.preventDefault();
              };

              formElement[0].addEventListener('submit', handleFormSubmission);

              // unregister the preventDefault listener so that we don't not leak memory but in a
              // way that will achieve the prevention of the default action.
              formElement.on('$destroy', function() {
                $timeout(function() {
                  formElement[0].removeEventListener('submit', handleFormSubmission);
                }, 0, false);
              });
            }

            var parentFormCtrl = ctrls[1] || controller.$$parentForm;
            parentFormCtrl.$addControl(controller);

            var setter = nameAttr ? getSetter(controller.$name) : noop;

            if (nameAttr) {
              setter(scope, controller);
              attr.$observe(nameAttr, function(newValue) {
                if (controller.$name === newValue) return;
                setter(scope, undefined);
                controller.$$parentForm.$$renameControl(controller, newValue);
                setter = getSetter(controller.$name);
                setter(scope, controller);
              });
            }
            formElement.on('$destroy', function() {
              controller.$$parentForm.$removeControl(controller);
              setter(scope, undefined);
              extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
            });
          }
        };
      }
    };

    return formDirective;

    function getSetter(expression) {
      if (expression === '') {
        //create an assignable expression, so forms with an empty name can be renamed later
        return $parse('this[""]').assign;
      }
      return $parse(expression).assign || noop;
    }
  }];
}