mirror of
https://github.com/zhigang1992/angular.js.git
synced 2026-01-12 22:45:52 +08:00
508 lines
18 KiB
Plaintext
508 lines
18 KiB
Plaintext
@ngdoc overview
|
|
@name Forms
|
|
@sortOrder 290
|
|
@description
|
|
|
|
Controls (`input`, `select`, `textarea`) are ways for a user to enter data.
|
|
A Form is a collection of controls for the purpose of grouping related controls together.
|
|
|
|
Form and controls provide validation services, so that the user can be notified of invalid input.
|
|
This provides a better user experience, because the user gets instant feedback on how to
|
|
correct the error. Keep in mind that while client-side validation plays an important role
|
|
in providing good user experience, it can easily be circumvented and thus can not be trusted.
|
|
Server-side validation is still necessary for a secure application.
|
|
|
|
|
|
# Simple form
|
|
The key directive in understanding two-way data-binding is {@link ng.directive:ngModel ngModel}.
|
|
The `ngModel` directive provides the two-way data-binding by synchronizing the model to the view,
|
|
as well as view to the model. In addition it provides an {@link ngModel.NgModelController API}
|
|
for other directives to augment its behavior.
|
|
|
|
<example module="formExample">
|
|
<file name="index.html">
|
|
<div ng-controller="ExampleController">
|
|
<form novalidate class="simple-form">
|
|
Name: <input type="text" ng-model="user.name" /><br />
|
|
E-mail: <input type="email" ng-model="user.email" /><br />
|
|
Gender: <input type="radio" ng-model="user.gender" value="male" />male
|
|
<input type="radio" ng-model="user.gender" value="female" />female<br />
|
|
<input type="button" ng-click="reset()" value="Reset" />
|
|
<input type="submit" ng-click="update(user)" value="Save" />
|
|
</form>
|
|
<pre>form = {{user | json}}</pre>
|
|
<pre>master = {{master | json}}</pre>
|
|
</div>
|
|
|
|
<script>
|
|
angular.module('formExample', [])
|
|
.controller('ExampleController', ['$scope', function($scope) {
|
|
$scope.master = {};
|
|
|
|
$scope.update = function(user) {
|
|
$scope.master = angular.copy(user);
|
|
};
|
|
|
|
$scope.reset = function() {
|
|
$scope.user = angular.copy($scope.master);
|
|
};
|
|
|
|
$scope.reset();
|
|
}]);
|
|
</script>
|
|
</file>
|
|
</example>
|
|
|
|
|
|
Note that `novalidate` is used to disable browser's native form validation.
|
|
|
|
The value of `ngModel` won't be set unless it passes validation for the input field.
|
|
For example: inputs of type `email` must have a value in the form of `user@domain`.
|
|
|
|
|
|
|
|
# Using CSS classes
|
|
|
|
To allow styling of form as well as controls, `ngModel` adds these CSS classes:
|
|
|
|
- `ng-valid`: the model is valid
|
|
- `ng-invalid`: the model is invalid
|
|
- `ng-valid-[key]`: for each valid key added by `$setValidity`
|
|
- `ng-invalid-[key]`: for each invalid key added by `$setValidity`
|
|
- `ng-pristine`: the control hasn't been interacted with yet
|
|
- `ng-dirty`: the control has been interacted with
|
|
- `ng-touched`: the control has been blurred
|
|
- `ng-untouched`: the control hasn't been blurred
|
|
- `ng-pending`: any `$asyncValidators` are unfulfilled
|
|
|
|
The following example uses the CSS to display validity of each form control.
|
|
In the example both `user.name` and `user.email` are required, but are rendered
|
|
with red background only after the input is blurred (loses focus).
|
|
This ensures that the user is not distracted with an error until after interacting with the control,
|
|
and failing to satisfy its validity.
|
|
|
|
<example module="formExample">
|
|
<file name="index.html">
|
|
<div ng-controller="ExampleController">
|
|
<form novalidate class="css-form">
|
|
Name: <input type="text" ng-model="user.name" required /><br />
|
|
E-mail: <input type="email" ng-model="user.email" required /><br />
|
|
Gender: <input type="radio" ng-model="user.gender" value="male" />male
|
|
<input type="radio" ng-model="user.gender" value="female" />female<br />
|
|
<input type="button" ng-click="reset()" value="Reset" />
|
|
<input type="submit" ng-click="update(user)" value="Save" />
|
|
</form>
|
|
</div>
|
|
|
|
<style type="text/css">
|
|
.css-form input.ng-invalid.ng-touched {
|
|
background-color: #FA787E;
|
|
}
|
|
|
|
.css-form input.ng-valid.ng-touched {
|
|
background-color: #78FA89;
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
angular.module('formExample', [])
|
|
.controller('ExampleController', ['$scope', function($scope) {
|
|
$scope.master = {};
|
|
|
|
$scope.update = function(user) {
|
|
$scope.master = angular.copy(user);
|
|
};
|
|
|
|
$scope.reset = function() {
|
|
$scope.user = angular.copy($scope.master);
|
|
};
|
|
|
|
$scope.reset();
|
|
}]);
|
|
</script>
|
|
</file>
|
|
</example>
|
|
|
|
|
|
|
|
# Binding to form and control state
|
|
|
|
A form is an instance of {@link form.FormController FormController}.
|
|
The form instance can optionally be published into the scope using the `name` attribute.
|
|
|
|
Similarly, an input control that has the {@link ng.directive:ngModel ngModel} directive holds an
|
|
instance of {@link ngModel.NgModelController NgModelController}.Such a control instance
|
|
can be published as a property of the form instance using the `name` attribute on the input control.
|
|
The name attribute specifies the name of the property on the form instance.
|
|
|
|
This implies that the internal state of both the form and the control is available for binding in
|
|
the view using the standard binding primitives.
|
|
|
|
This allows us to extend the above example with these features:
|
|
|
|
- Custom error message displayed after the user interacted with a control (i.e. when `$touched` is set)
|
|
- Custom error message displayed upon submitting the form (`$submitted` is set), even if the user
|
|
didn't interact with a control
|
|
|
|
|
|
<example module="formExample">
|
|
<file name="index.html">
|
|
<div ng-controller="ExampleController">
|
|
<form name="form" class="css-form" novalidate>
|
|
Name:
|
|
<input type="text" ng-model="user.name" name="uName" required="" />
|
|
<br />
|
|
<div ng-show="form.$submitted || form.uName.$touched">
|
|
<div ng-show="form.uName.$error.required">Tell us your name.</div>
|
|
</div>
|
|
|
|
E-mail:
|
|
<input type="email" ng-model="user.email" name="uEmail" required="" />
|
|
<br />
|
|
<div ng-show="form.$submitted || form.uEmail.$touched">
|
|
<span ng-show="form.uEmail.$error.required">Tell us your email.</span>
|
|
<span ng-show="form.uEmail.$error.email">This is not a valid email.</span>
|
|
</div>
|
|
|
|
Gender:
|
|
<input type="radio" ng-model="user.gender" value="male" />male
|
|
<input type="radio" ng-model="user.gender" value="female" />female
|
|
<br />
|
|
<input type="checkbox" ng-model="user.agree" name="userAgree" required="" />
|
|
|
|
I agree:
|
|
<input ng-show="user.agree" type="text" ng-model="user.agreeSign" required="" />
|
|
<br />
|
|
<div ng-show="form.$submitted || form.userAgree.$touched">
|
|
<div ng-show="!user.agree || !user.agreeSign">Please agree and sign.</div>
|
|
</div>
|
|
|
|
<input type="button" ng-click="reset(form)" value="Reset" />
|
|
<input type="submit" ng-click="update(user)" value="Save" />
|
|
</form>
|
|
</div>
|
|
</file>
|
|
|
|
<file name="script.js">
|
|
angular.module('formExample', [])
|
|
.controller('ExampleController', ['$scope', function($scope) {
|
|
$scope.master = {};
|
|
|
|
$scope.update = function(user) {
|
|
$scope.master = angular.copy(user);
|
|
};
|
|
|
|
$scope.reset = function(form) {
|
|
if (form) {
|
|
form.$setPristine();
|
|
form.$setUntouched();
|
|
}
|
|
$scope.user = angular.copy($scope.master);
|
|
};
|
|
|
|
$scope.reset();
|
|
}]);
|
|
</file>
|
|
</example>
|
|
|
|
|
|
|
|
# Custom model update triggers
|
|
|
|
By default, any change to the content will trigger a model update and form validation. You can
|
|
override this behavior using the {@link ng.directive:ngModelOptions ngModelOptions} directive to
|
|
bind only to specified list of events. I.e. `ng-model-options="{ updateOn: 'blur' }"` will update
|
|
and validate only after the control loses focus. You can set several events using a space delimited
|
|
list. I.e. `ng-model-options="{ updateOn: 'mousedown blur' }"`
|
|
|
|
<img alt="animation showing debounced input" src="img/guide/forms-update-on-blur.gif">
|
|
|
|
If you want to keep the default behavior and just add new events that may trigger the model update
|
|
and validation, add "default" as one of the specified events.
|
|
|
|
I.e. `ng-model-options="{ updateOn: 'default blur' }"`
|
|
|
|
The following example shows how to override immediate updates. Changes on the inputs within the form
|
|
will update the model only when the control loses focus (blur event).
|
|
|
|
<example module="customTriggerExample">
|
|
<file name="index.html">
|
|
<div ng-controller="ExampleController">
|
|
<form>
|
|
Name:
|
|
<input type="text" ng-model="user.name" ng-model-options="{ updateOn: 'blur' }" /><br />
|
|
Other data:
|
|
<input type="text" ng-model="user.data" /><br />
|
|
</form>
|
|
<pre>username = "{{user.name}}"</pre>
|
|
<pre>userdata = "{{user.data}}"</pre>
|
|
</div>
|
|
</file>
|
|
<file name="script.js">
|
|
angular.module('customTriggerExample', [])
|
|
.controller('ExampleController', ['$scope', function($scope) {
|
|
$scope.user = {};
|
|
}]);
|
|
</file>
|
|
</example>
|
|
|
|
|
|
|
|
# Non-immediate (debounced) model updates
|
|
|
|
You can delay the model update/validation by using the `debounce` key with the
|
|
{@link ng.directive:ngModelOptions ngModelOptions} directive. This delay will also apply to
|
|
parsers, validators and model flags like `$dirty` or `$pristine`.
|
|
|
|
<img alt="animation showing debounced input" src="img/guide/forms-debounce.gif">
|
|
|
|
I.e. `ng-model-options="{ debounce: 500 }"` will wait for half a second since
|
|
the last content change before triggering the model update and form validation.
|
|
|
|
If custom triggers are used, custom debouncing timeouts can be set for each event using an object
|
|
in `debounce`. This can be useful to force immediate updates on some specific circumstances
|
|
(like blur events).
|
|
|
|
I.e. `ng-model-options="{ updateOn: 'default blur', debounce: { default: 500, blur: 0 } }"`
|
|
|
|
If those attributes are added to an element, they will be applied to all the child elements and
|
|
controls that inherit from it unless they are overridden.
|
|
|
|
This example shows how to debounce model changes. Model will be updated only 250 milliseconds
|
|
after last change.
|
|
|
|
<example module="debounceExample">
|
|
<file name="index.html">
|
|
<div ng-controller="ExampleController">
|
|
<form>
|
|
Name:
|
|
<input type="text" ng-model="user.name" ng-model-options="{ debounce: 250 }" /><br />
|
|
</form>
|
|
<pre>username = "{{user.name}}"</pre>
|
|
</div>
|
|
</file>
|
|
<file name="script.js">
|
|
angular.module('debounceExample', [])
|
|
.controller('ExampleController', ['$scope', function($scope) {
|
|
$scope.user = {};
|
|
}]);
|
|
</file>
|
|
</example>
|
|
|
|
# Custom Validation
|
|
|
|
Angular provides basic implementation for most common HTML5 {@link ng.directive:input input}
|
|
types: ({@link input[text] text}, {@link input[number] number}, {@link input[url] url},
|
|
{@link input[email] email}, {@link input[date] date}, {@link input[radio] radio}, {@link input[checkbox] checkbox}),
|
|
as well as some directives for validation (`required`, `pattern`, `minlength`, `maxlength`,
|
|
`min`, `max`).
|
|
|
|
With a custom directive, you can add your own validation functions to the `$validators` object on
|
|
the {@link ngModel.NgModelController `ngModelController`}. To get a hold of the controller,
|
|
you require it in the directive as shown in the example below.
|
|
|
|
Each function in the `$validators` object receives the `modelValue` and the `viewValue`
|
|
as parameters. Angular will then call `$setValidity` internally with the function's return value
|
|
(`true`: valid, `false`: invalid). The validation functions are executed every time an input
|
|
is changed (`$setViewValue` is called) or whenever the bound `model` changes.
|
|
Validation happens after successfully running `$parsers` and `$formatters`, respectively.
|
|
Failed validators are stored by key in
|
|
{@link ngModel.NgModelController#$error `ngModelController.$error`}.
|
|
|
|
Additionally, there is the `$asyncValidators` object which handles asynchronous validation,
|
|
such as making an `$http` request to the backend. Functions added to the object must return
|
|
a promise that must be `resolved` when valid or `rejected` when invalid.
|
|
In-progress async validations are stored by key in
|
|
{@link ngModel.NgModelController#$pending `ngModelController.$pending`}.
|
|
|
|
In the following example we create two directives:
|
|
* An `integer` directive that validates whether the input is a valid integer. For example,
|
|
`1.23` is an invalid value, since it contains a fraction. Note that we validate the viewValue
|
|
(the string value of the control), and not the modelValue. This is because input[number] converts
|
|
the viewValue to a number when running the `$parsers`.
|
|
|
|
* A `username` directive that asynchronously checks if a user-entered value is already taken.
|
|
We mock the server request with a `$q` deferred.
|
|
|
|
<example module="form-example1">
|
|
<file name="index.html">
|
|
<form name="form" class="css-form" novalidate>
|
|
<div>
|
|
Size (integer 0 - 10):
|
|
<input type="number" ng-model="size" name="size"
|
|
min="0" max="10" integer />{{size}}<br />
|
|
<span ng-show="form.size.$error.integer">The value is not a valid integer!</span>
|
|
<span ng-show="form.size.$error.min || form.size.$error.max">
|
|
The value must be in range 0 to 10!</span>
|
|
</div>
|
|
|
|
<div>
|
|
Username:
|
|
<input type="text" ng-model="name" name="name" username />{{name}}<br />
|
|
<span ng-show="form.name.$pending.username">Checking if this name is available ...</span>
|
|
<span ng-show="form.name.$error.username">This username is already taken!</span>
|
|
</div>
|
|
|
|
</form>
|
|
</file>
|
|
|
|
<file name="script.js">
|
|
var app = angular.module('form-example1', []);
|
|
|
|
var INTEGER_REGEXP = /^\-?\d+$/;
|
|
app.directive('integer', function() {
|
|
return {
|
|
require: 'ngModel',
|
|
link: function(scope, elm, attrs, ctrl) {
|
|
ctrl.$validators.integer = function(modelValue, viewValue) {
|
|
if (ctrl.$isEmpty(modelValue)) {
|
|
// consider empty models to be valid
|
|
return true;
|
|
}
|
|
|
|
if (INTEGER_REGEXP.test(viewValue)) {
|
|
// it is valid
|
|
return true;
|
|
}
|
|
|
|
// it is invalid
|
|
return false;
|
|
};
|
|
}
|
|
};
|
|
});
|
|
|
|
app.directive('username', function($q, $timeout) {
|
|
return {
|
|
require: 'ngModel',
|
|
link: function(scope, elm, attrs, ctrl) {
|
|
var usernames = ['Jim', 'John', 'Jill', 'Jackie'];
|
|
|
|
ctrl.$asyncValidators.username = function(modelValue, viewValue) {
|
|
|
|
if (ctrl.$isEmpty(modelValue)) {
|
|
// consider empty model valid
|
|
return $q.when();
|
|
}
|
|
|
|
var def = $q.defer();
|
|
|
|
$timeout(function() {
|
|
// Mock a delayed response
|
|
if (usernames.indexOf(modelValue) === -1) {
|
|
// The username is available
|
|
def.resolve();
|
|
} else {
|
|
def.reject();
|
|
}
|
|
|
|
}, 2000);
|
|
|
|
return def.promise;
|
|
};
|
|
}
|
|
};
|
|
});
|
|
</file>
|
|
</example>
|
|
|
|
# Modifying built-in validators
|
|
|
|
Since Angular itself uses `$validators`, you can easily replace or remove built-in validators,
|
|
should you find it necessary. The following example shows you how to overwrite the email validator
|
|
in `input[email]` from a custom directive so that it requires a specific top-level domain,
|
|
`example.com` to be present.
|
|
Note that you can alternatively use `ng-pattern` to further restrict the validation.
|
|
|
|
<example module="form-example-modify-validators">
|
|
<file name="index.html">
|
|
<form name="form" class="css-form" novalidate>
|
|
<div>
|
|
Overwritten Email:
|
|
<input type="email" ng-model="myEmail" overwrite-email name="overwrittenEmail" />
|
|
<span ng-show="form.overwrittenEmail.$error.email">This email format is invalid!</span><br>
|
|
Model: {{myEmail}}
|
|
</div>
|
|
</form>
|
|
</file>
|
|
|
|
<file name="script.js">
|
|
var app = angular.module('form-example-modify-validators', []);
|
|
|
|
app.directive('overwriteEmail', function() {
|
|
var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+/=?^_`{|}~.-]+@example\.com$/i;
|
|
|
|
return {
|
|
require: 'ngModel',
|
|
restrict: '',
|
|
link: function(scope, elm, attrs, ctrl) {
|
|
// only apply the validator if ngModel is present and Angular has added the email validator
|
|
if (ctrl && ctrl.$validators.email) {
|
|
|
|
// this will overwrite the default Angular email validator
|
|
ctrl.$validators.email = function(modelValue) {
|
|
return ctrl.$isEmpty(modelValue) || EMAIL_REGEXP.test(modelValue);
|
|
};
|
|
}
|
|
}
|
|
};
|
|
});
|
|
</file>
|
|
</example>
|
|
|
|
|
|
# Implementing custom form controls (using `ngModel`)
|
|
Angular implements all of the basic HTML form controls ({@link ng.directive:input input},
|
|
{@link ng.directive:select select}, {@link ng.directive:textarea textarea}),
|
|
which should be sufficient for most cases. However, if you need more flexibility,
|
|
you can write your own form control as a directive.
|
|
|
|
In order for custom control to work with `ngModel` and to achieve two-way data-binding it needs to:
|
|
|
|
- implement `$render` method, which is responsible for rendering the data after it passed the
|
|
{@link ngModel.NgModelController#$formatters `NgModelController.$formatters`},
|
|
- call `$setViewValue` method, whenever the user interacts with the control and model
|
|
needs to be updated. This is usually done inside a DOM Event listener.
|
|
|
|
See {@link guide/directive `$compileProvider.directive`} for more info.
|
|
|
|
The following example shows how to add two-way data-binding to contentEditable elements.
|
|
|
|
<example module="form-example2">
|
|
<file name="index.html">
|
|
<div contentEditable="true" ng-model="content" title="Click to edit">Some</div>
|
|
<pre>model = {{content}}</pre>
|
|
|
|
<style type="text/css">
|
|
div[contentEditable] {
|
|
cursor: pointer;
|
|
background-color: #D0D0D0;
|
|
}
|
|
</style>
|
|
</file>
|
|
|
|
<file name="script.js">
|
|
angular.module('form-example2', []).directive('contenteditable', function() {
|
|
return {
|
|
require: 'ngModel',
|
|
link: function(scope, elm, attrs, ctrl) {
|
|
// view -> model
|
|
elm.on('blur', function() {
|
|
scope.$apply(function() {
|
|
ctrl.$setViewValue(elm.html());
|
|
});
|
|
});
|
|
|
|
// model -> view
|
|
ctrl.$render = function() {
|
|
elm.html(ctrl.$viewValue);
|
|
};
|
|
|
|
// load init value from DOM
|
|
ctrl.$setViewValue(elm.html());
|
|
}
|
|
};
|
|
});
|
|
</file>
|
|
</example>
|