mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-03-17 07:40:22 +00:00
337 lines
14 KiB
Text
337 lines
14 KiB
Text
@ngdoc overview
|
|
@name Developer Guide: Forms
|
|
@description
|
|
|
|
Forms and form controls (`input`, `select`, `textarea`) are user's gateway to your application -
|
|
that's how your application accepts input from the user.
|
|
|
|
In order to provide good user experience while gathering user input, it is important to validate
|
|
this input and give the user hints on how to correct errors. Angular provides several mechanisms
|
|
that make this easier, but keep in mind that while client-side validation plays an important role in
|
|
providing good user experience, it can be easily circumvented and thus a server-side validation is
|
|
still necessary.
|
|
|
|
|
|
# Simple form
|
|
The most important directive is {@link api/angular.module.ng.$compileProvider.directive.ng:model ng-model},
|
|
which tells Angular to do two-way data binding. That means, the value in the form control is
|
|
synchronized in both directions with the bound model (specified as value of `ng-model` attribute).
|
|
|
|
|
|
<doc:example>
|
|
<doc:source>
|
|
<div ng-controller="Controller">
|
|
<form novalidate class="simple-form">
|
|
Name: <input type="text" ng-model="user.name" ng-model-instant /><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 />
|
|
<button ng-click="reset()">RESET</button>
|
|
<button ng-click="update(user)">SAVE</button>
|
|
</form>
|
|
<!-- reading these values outside <form> scope is possible only because we defined these objects
|
|
on the parent scope, and ng-model only change properties of this object -->
|
|
<pre>form = {{user | json}}</pre>
|
|
<pre>master = {{master | json}}</pre>
|
|
</div>
|
|
|
|
<script type="text/javascript">
|
|
function Controller($scope) {
|
|
$scope.master= {};
|
|
|
|
$scope.update = function(user) {
|
|
$scope.master= angular.copy(user);
|
|
};
|
|
|
|
$scope.reset = function() {
|
|
$scope.user = angular.copy($scope.master);
|
|
};
|
|
|
|
$scope.reset();
|
|
}
|
|
</script>
|
|
</doc:source>
|
|
</doc:example>
|
|
|
|
|
|
Note, that the `user.name` is updated immediately - that's because of
|
|
{@link api/angular.module.ng.$compileProvide.directive.ng:model-instant ng-model-instant}.
|
|
|
|
Note, that we use `novalidate` to disable browser's native form validation.
|
|
|
|
|
|
## Scoping issues
|
|
|
|
Angular sets the model value onto current scope. However it can be confusing where are the scope
|
|
borders - in other words, which directives create new scope.
|
|
It's crucial to understand how prototypical inheritance works as well as
|
|
{@link dev_guide.scopes.internals Angular's scopes}.
|
|
|
|
In this example, there are actually two directives, that create new scope (`ng-controller` and `form`).
|
|
Angular sets the value onto the current scope, so the first input sets value to `scope.user.name`,
|
|
where `scope` is the scope on `form` element. Therefore you would not be able to read the value
|
|
outside the `form`, because that's a parent scope. That's why we defined the `$scope.user` object
|
|
on the parent scope (on `div` element), because `ng-model` access this object through prototypical
|
|
inheritance and bind to this object (defined on the parent scope) and we can access it even on
|
|
parent scope.
|
|
|
|
|
|
|
|
# Using CSS classes
|
|
Angular puts some basic css classes onto the form element as well as individual form control
|
|
elements, to allow you to style them differently, depending on their state. These css classes are:
|
|
|
|
- `ng-valid`
|
|
- `ng-invalid`
|
|
- `ng-pristine`
|
|
- `ng-dirty`
|
|
|
|
Here is the same example with some very basic css, displaying validity of each form control.
|
|
Both `user.name` and `user.email` are required, but we display the red background only when they
|
|
are dirty, which means the user has already interacted with them.
|
|
|
|
<doc:example>
|
|
<doc:source>
|
|
<div ng-controller="Controller">
|
|
<form novalidate class="css-form">
|
|
Name: <input type="text" ng-model="user.name" ng-model-instant 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 />
|
|
<button ng-click="reset()">RESET</button>
|
|
<button ng-click="update(user)">SAVE</button>
|
|
</form>
|
|
</div>
|
|
|
|
<style type="text/css">
|
|
.css-form input.ng-invalid.ng-dirty {
|
|
background-color: #FA787E;
|
|
}
|
|
|
|
.css-form input.ng-valid.ng-dirty {
|
|
background-color: #78FA89;
|
|
}
|
|
</style>
|
|
|
|
<script type="text/javascript">
|
|
function Controller($scope) {
|
|
$scope.master= {};
|
|
|
|
$scope.update = function(user) {
|
|
$scope.master= angular.copy(user);
|
|
};
|
|
|
|
$scope.reset = function() {
|
|
$scope.user = angular.copy($scope.master);
|
|
};
|
|
|
|
$scope.reset();
|
|
}
|
|
</script>
|
|
</doc:source>
|
|
</doc:example>
|
|
|
|
|
|
|
|
# Binding to form / form control state
|
|
|
|
Each form has an object, that keeps the state of the whole form. This object is an instance of
|
|
{@link api/angular.module.ng.$compileProvide.directive.form.FormController FormController}.
|
|
In a similar way, each form control with `ng-model` directive has an object, that keeps the state of
|
|
the form control. This object is an instance of
|
|
{@link api/angular.module.ng.$compileProvide.directive.form.NgModelController NgModelController}.
|
|
|
|
The css classes used in the previous example are nothing else than just a reflection of these objects.
|
|
But using css classes is not flexible enough - we need to do more. So this example shows, how to
|
|
access these state objects and how to bind to them.
|
|
|
|
Note, we added `name` attribute to the form element as well as to the form controls, so that we have access
|
|
these objects. When a form has `name` attribute, its `FormController` is published onto the scope.
|
|
In a similar way, if a form control has `name` attribute, a reference to its `NgModelController` is
|
|
stored on the `FormController`.
|
|
|
|
**Some changes to notice:**
|
|
|
|
- RESET button is enabled only if form has some changes
|
|
- SAVE button is enabled only if form has some changes and is valid
|
|
- custom error messages for `user.email` and `user.agree`
|
|
|
|
<doc:example>
|
|
<doc:source>
|
|
<div ng-controller="Controller">
|
|
<form name="form" class="css-form" novalidate>
|
|
Name: <input type="text" ng-model="user.name" name="userName" required /><br />
|
|
E-mail: <input type="email" ng-model="user.email" name="userEmail" required/><br />
|
|
<span ng-show="form.userEmail.$dirty && form.userEmail.$invalid">Invalid:
|
|
<span ng-show="form.userEmail.$error.REQUIRED">Please tell us your email.</span>
|
|
<span ng-show="form.userEmail.$error.EMAIL">This is not a valid email.</span><br />
|
|
</span>
|
|
|
|
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" ng-model-instant required /><br />
|
|
<div ng-show="!user.agree || !user.agreeSign">Please agree and sign.</div>
|
|
|
|
<button ng-click="reset()" disabled="{{isUnchanged(user)}}">RESET</button>
|
|
<button ng-click="update(user)" disabled="{{form.$invalid || isUnchanged(user)}}">SAVE</button>
|
|
</form>
|
|
</div>
|
|
|
|
<script type="text/javascript">
|
|
function Controller($scope) {
|
|
$scope.master= {};
|
|
|
|
$scope.update = function(user) {
|
|
$scope.master= angular.copy(user);
|
|
};
|
|
|
|
$scope.reset = function() {
|
|
$scope.user = angular.copy($scope.master);
|
|
};
|
|
|
|
$scope.isUnchanged = function(user) {
|
|
return angular.equals(user, $scope.master);
|
|
};
|
|
|
|
$scope.reset();
|
|
}
|
|
</script>
|
|
</doc:source>
|
|
</doc:example>
|
|
|
|
|
|
|
|
# Advanced / custom validation
|
|
|
|
Angular provides basic implementation for most common html5 {@link api/angular.module.ng.$compileProvider.directive.input input}
|
|
types ({@link api/angular.module.ng.$compileProvider.directive.input.text text}, {@link api/angular.module.ng.$compileProvider.directive.input.number number}, {@link api/angular.module.ng.$compileProvider.directive.input.url url}, {@link api/angular.module.ng.$compileProvider.directive.input.email email}, {@link api/angular.module.ng.$compileProvider.directive.input.radio radio}, {@link api/angular.module.ng.$compileProvider.directive.input.checkbox checkbox}), as well as some directives for validation (`required`, `pattern`, `minlength`, `maxlength`, `min`, `max`).
|
|
|
|
However, when this is not enough for your application, you can simply define a custom directive.
|
|
This directive can require `ngModel`, which means it can't exist without `ng-model` and its linking
|
|
function gets fourth argument - an instance of `NgModelController`, which is a communication channel
|
|
to `ng-model`, that allows you to hook into the validation process.
|
|
|
|
## Model to View update
|
|
Whenever the bound model changes, all functions in {@link api/angular.module.ng.$compileProvider.directive.ng:model.NgModelController#formatters NgModelController#formatters} array are pipe-lined, so that each of these functions has an opportunity to format the value and change validity state of the form control through {@link api/angualar.module.ng.$compileProvider.directive.ng:model.NgModelController#$setValidity NgModelController#$setValidity}.
|
|
|
|
## View to Model update
|
|
In a similar way, whenever a form control calls {@link api/angular.module.ng.$compileProvider.directive.ng:model.NgModelController#setViewValue NgModelController#setViewValue}, all functions in {@link api/angular.module.ng.$compileProvider.directive.ng:model.NgModelController#parsers NgModelController#parsers} array are pipe-lined, so that each of these functions has an opportunity to correct/convert the value and change validity state of the form control through {@link api/angualar.module.ng.$compileProvider.directive.ng:model.NgModelController#setValidity NgModelController#$setValidity}.
|
|
|
|
In this example we create two simple directives. The first one is `integer` and it validates whether the input is valid integer, so for example `1.23` is an invalid value. Note, that we unshift the array instead of pushing - that's because we want to get a string value, so we need to execute the validation function before a conversion to number happens.
|
|
|
|
The second directive is `smart-float`. It parses both `1.2` and `1,2` into a valid float number `1.2`. Note, we can't use input type `number` here - browser would not allow user to type invalid number such as `1,2`.
|
|
|
|
|
|
<doc:example module="form-example1">
|
|
<doc:source>
|
|
<div ng-controller="Controller">
|
|
<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">This is not 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>
|
|
Length (float): <input type="text" ng-model="length" name="length" smart-float />{{length}}<br />
|
|
<span ng-show="form.length.$error.FLOAT">This is not valid number!</span>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<script type="text/javascript">
|
|
var app = angular.module('form-example1', []);
|
|
|
|
var INTEGER_REGEXP = /^\-?\d*$/;
|
|
app.directive('integer', function() {
|
|
return {
|
|
require: 'ngModel',
|
|
link: function(scope, elm, attrs, ctrl) {
|
|
ctrl.$parsers.unshift(function(viewValue) {
|
|
if (INTEGER_REGEXP.test(viewValue)) {
|
|
// it is valid
|
|
ctrl.$setValidity('INTEGER', true);
|
|
return viewValue;
|
|
} else {
|
|
// it is invalid, return undefined (no model update)
|
|
ctrl.$setValidity('INTEGER', false);
|
|
return undefined;
|
|
}
|
|
});
|
|
}
|
|
};
|
|
});
|
|
|
|
var FLOAT_REGEXP = /^\-?\d+((\.|\,)\d+)?$/;
|
|
app.directive('smartFloat', function() {
|
|
return {
|
|
require: 'ngModel',
|
|
link: function(scope, elm, attrs, ctrl) {
|
|
ctrl.$parsers.unshift(function(viewValue) {
|
|
if (FLOAT_REGEXP.test(viewValue)) {
|
|
ctrl.$setValidity('FLOAT', true);
|
|
return parseFloat(viewValue.replace(',', '.'));
|
|
} else {
|
|
ctrl.$setValidity('FLOAT', false);
|
|
return undefined;
|
|
}
|
|
});
|
|
}
|
|
};
|
|
});
|
|
</script>
|
|
</doc:source>
|
|
</doc:example>
|
|
|
|
|
|
# Implementing custom form control (using ng-model)
|
|
Angular has all the basic form controls implemented ({@link api/angular.module.ng.$compileProvider.directive.input input}, {@link api/angular.module.ng.$compileProvider.directive.select select}, {@link api/angular.module.ng.$compileProvider.directive.textarea textarea}), so most of the time you should be just fine with them. However, if you need more flexibility, you can write your own form control - it's gonna be a directive again.
|
|
|
|
You basically need to do two things to get it working together with `ng-model` binding:
|
|
|
|
- implement `render` method, that knows how to reflect value change to view,
|
|
- call `setViewValue` method, whenever the view value changes - that's usually inside DOM Event listener.
|
|
|
|
See {@link api/angular.module.ng.$compileProvider.directive $compileProvider.directive} for more info.
|
|
|
|
This example shows how easy it is to add a support for binding contentEditable elements.
|
|
|
|
<doc:example module="form-example2">
|
|
<doc:source>
|
|
<script type="text/javascript">
|
|
angular.module('form-example2', []).directive('contenteditable', function() {
|
|
return {
|
|
require: 'ngModel',
|
|
link: function(scope, elm, attrs, ctrl) {
|
|
// view -> model
|
|
elm.bind('blur', function() {
|
|
scope.$apply(function() {
|
|
ctrl.$setViewValue(elm.html());
|
|
});
|
|
});
|
|
|
|
// model -> view
|
|
ctrl.render = function(value) {
|
|
elm.html(value);
|
|
};
|
|
|
|
// load init value from DOM
|
|
ctrl.$setViewValue(elm.html());
|
|
}
|
|
};
|
|
});
|
|
</script>
|
|
|
|
<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>
|
|
</doc:source>
|
|
</doc:example>
|