控件 (input
, select
, textarea
) 是用户输入数据的一种方式。一个表单就是多个控件的集合,用来组织相关的控件。
表单和控件提供验证服务,在用户输入信息有误的时候进行提示。 这提升了用户体验,因为我们在第一时间告知用户什么地方出错了、如何修正错误。 记住,尽管客户端(浏览器)验证在用户体验方面起了重要作用,但是,它很容易被绕过,因此是不能信任的。 为了应用的安全,服务端的验证仍然是必须的。
理解双向绑定的关键指令是ngModel
.
指令ngModel
通过维护“数据到视图”的同步以及“视图到数据”的同步实现了双向绑定。
另外,它还提供了API
来让其他指令扩展其行为。
<!doctype html> <html ng-app> <head> <script src="http://code.angularjs.org/1.2.25/angular.min.js"></script> <script src="script.js"></script> </head> <body> <div ng-controller="Controller"> <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 /> <button ng-click="reset()">RESET</button> <button ng-click="update(user)">SAVE</button> </form> <pre>form = {{user | json}} </pre> <pre>master = {{master | json}} </pre> </div> </body> </html>
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(); }
其中 novalidate
属性用于禁用浏览器自带的表单验证功能。
为了允许对表单和控件自定义样式, ngModel
增加了如下的CSS类:
- ng-valid
- ng-invalid
- ng-pristine
- ng-dirty
接下来的例子中使用这些CSS类来表明每一个表单控件是否有效。
在例子中,user.name
和 user.email
都是必需的(required),它们仅在成为脏数据(dirty)时才会显示红色背景,但不会在初始状态下(也为空)显示。
这样就防止了用户在尚未与该控件进行交互,而该控件的当前状态恰好不符合验证条件的时候,出现不恰当的错误提示。
<!doctype html> <html ng-app> <head> <script src="http://code.angularjs.org/1.2.25/angular.min.js"></script> <script src="script.js"></script> </head> <body> <div ng-controller="Controller"> <form novalidate class="css-form"> Name: <input type="text" ng-model="user.name" required /><br /> 邮箱: <input type="email" ng-model="user.email" required /><br /> 性别: <label><input type="radio" ng-model="user.gender" value="male" />男</label> <label><input type="radio" ng-model="user.gender" value="female" />女</label><br /> <button ng-click="reset()">重置</button> <button ng-click="update(user)">保存</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> </body> </html>
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(); }
一个表单是一个 FormController
的实例。
这个表单实例可以通过 'name' 属性装载到scope中去。
类似的,一个拥有 api/ng.directive:ngModel
指令的输入控件,包含一个NgModelController
实例。
这样一个控件的实例可以使用 'name' 属性装载到表单实例中去,作为表单实例的一个字段。name属性指定了其在表单实例中的字段名。
这意味着,在视图中使用基本的数据绑定形式就可以访问到表单和控件中的内部状态。
这让我们可以为上面的例子扩展以下功能:
user.email
和 user.agree
自定义错误提示信息<!doctype html> <html ng-app> <head> <script src="http://code.angularjs.org/1.2.25/angular.min.js"></script> <script src="script.js"></script> </head> <body> <div ng-controller="Controller"> <form name="form" class="css-form" novalidate> Name: <input type="text" ng-model="user.name" name="uName" required /><br /> E-mail: <input type="email" ng-model="user.email" name="uEmail" required/><br /> <div ng-show="form.uEmail.$dirty && form.uEmail.$invalid">Invalid: <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="!user.agree || !user.agreeSign">Please agree and sign.</div> <button ng-click="reset()" ng-disabled="isUnchanged(user)">RESET</button> <button ng-click="update(user)" ng-disabled="form.$invalid || isUnchanged(user)">SAVE</button> </form> </div> </body> </html>
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(); }
Angular提供了一些常用的html5输入控件的验证实现:(text
, number
, url
, email
, radio
, checkbox
), 以及一些用于验证的指令 (required
, pattern
, minlength
, maxlength
, min
, max
).
定义你自己的验证器可以首先定义一个指令,这个指令为 'ngModel' 控制器
添加自定义验证方法。
我们通过指定一个依赖来获得对这个控制器的引用,从下面的例子中可以看出。
我们的验证在两个时机触发:
数据到视图的更新 -
任何时候,受约束的模型改变时,所有在NgModelController#$formatters
数组中的方法会被管道式调用(即:一个接一个的调用方法),这样一来,所有的方法都有机会对值来进行格式化并改变表单和控件的有效性状态,这将通过调用NgModelController#$setValidity
来实现。
视图到数据的更新 -
类似的,当用户与一个控件交互时,调用NgModelController#$setViewValue
.
这又反过来管式调用了所有在NgModelController#$parsers
数组中的方法,这样一来,所有的方法都有机会对值来进行转换并改变表单和控件的有效性状态,通过NgModelController#$setValidity
来实现。
下面的示例中我们创建了两个指令。
<!doctype html> <html ng-app="form-example1"> <head> <script src="http://code.angularjs.org/1.2.25/angular.min.js"></script> <script src="script.js"></script> </head> <body> <div ng-controller="Controller"> <form name="form" class="css-form" novalidate> <div> 大小 (整数 0 - 10): <input type="number" ng-model="size" name="size" min="0" max="10" integer /><br /> <span ng-show="form.size.$error.integer">这是无效的数字!</span> <span ng-show="form.size.$error.min || form.size.$error.max"> 值必需在0到10之间!</span> </div> <div> 长度 (浮点): <input type="text" ng-model="length" name="length" smart-float /> <br /> <span ng-show="form.length.$error.float"> 这是无效的浮点数!</span> </div> </form> </div> </body> </html>
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)) { // 验证通过 ctrl.$setValidity('integer', true); return viewValue; } else { // 验证不通过 返回 undefined (不会有模型更新) 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; } }); } }; });
Angular实现了所有基本的HTML表单控件((input
, select
, textarea
),它们在大多数情况下都很有效。
然而,如果你需要更多的灵活性,你可以使用指令来实现你的自定义表单控件。
为了能让自定义控件能够与'ngModel'正常工作,达到双向绑定的效果,它需要:
NgModelController#$formatters
之后渲染数据。查看 $compileProvider.directive 获得更多的信息。
接下来的例子展示了如何为一个可编辑元素添加双向绑定。
<!doctype html> <html ng-app="form-example2"> <head> <script src="http://code.angularjs.org/1.2.25/angular.min.js"></script> <script src="script.js"></script> </head> <body> <div contentEditable="true" ng-model="content" title="Click to edit">Some</div> <pre>model = </pre> <style type="text/css"> div[contentEditable] { cursor: pointer; background-color: #D0D0D0; } </style> </body> </html>
angular.module('form-example2', []).directive('contenteditable', function() { return { require: 'ngModel', link: function(scope, elm, attrs, ctrl) { // 视图 -> 模型 elm.on('blur', function() { scope.$apply(function() { ctrl.$setViewValue(elm.html()); }); }); // 模型 -> 视图 ctrl.$render = function() { elm.html(ctrl.$viewValue); }; // 从DOM中初始化数据 ctrl.$setViewValue(elm.html()); } }; });