表单中有很多控件,如input
, select
, textarea
等,是用来让用户输入数据的。
如果表单支持验证,用户在输入了不合法的数据时,能得到实时提示(比如哪儿错了,如何修正等),会是一种很好的用户体验。需要明白的是,不论“客户端验证”对于提升用户的使用体验如何重要,也不要忘了在服务器端再次验证。因为用户可以绕过客户端验证直接向服务器端发送非法数据。
理解双向绑定最关键的directive是ng-model
这个directive。它通过model数据同步到view,以及将view变化同步到model的方式,提供了双向绑定。另外,它还通过NgModelController
提供了用于改变其它directive的行为的API
indexhtml
<!doctype html>
<html ng-app>
<head>
<script src="http://code.angularjs.org/angular-1.0.1.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>master = {{master | json}}```
</div>
</body>
</html>
**script.js**
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();
}
demo
注意:`novalidate`用来关闭浏览器自己的form验证
### 使用CSS classes
为了让表单跟控件一样可以被修饰,`ngModel`提供了以下CSS classes:
ng-valid
ng-invalid
ng-pristine
ng-dirty
下面这个例子,使用CSS来显示表单中每一个控件的合法性。其中user.name
和user.email
是必填的,但仅当它们被修改过才会显示红色背景。这样保证了用户仅仅在与某个控件交互过之后,才会在输入不合法的时候显示错误信息。
index.html
<script src="http://code.angularjs.org/angular-1.0.1.min.js"></script>
<script src="script.js"></script>
<div ng-controller="Controller">
<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 />
<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>
scripts.js
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();
}
demo
每一个表单都是FormController
的一个实例。如果你给表单增加了一个name
属性,则它自动会加到$scope中,方便引用。同样表单中的控件也可以使用name
添加到$scope中(控件对应的controller是NgModelController
)。这样我们在view中如果想引用某些表单或控制,检查它们的状态,就很方便了。
我们利用这些特性来给前面的例子增加以下功能:
RESET按钮只有当表单发生了变化时,才可点2. SAVE按钮只有当表单内容发生了合法的变化时,才可点3. 为user.email
和user.agree
添加自定义的错误信息
index.html
<script src="http://code.angularjs.org/angular-1.0.1.min.js"></script>
<script src="script.js"></script>
<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>
script.js
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();
}
jsfiddle demo
Angular已经为大多数常用的html5控件:text
,number
,url
,email
,radio
,checkbox
提供了基本的验证功能,比如这些directives(required
,pattern
,minlength
,maxlength
,min
,max
).
你可以通过定义一些给ngModel
(对应于NgModelController
)添加自定义验证函数的directive来定义自己的验证器。
为了取得相应的controller,directive需要指定一些依赖。在下面详述。
验证可以发生在两个地方:
Model向View更新 - NgModelController#$formatters
中的各函数是串联的,只要model发生了变化,每个formatter函数都有机会来格式化数据,并通过NgModelController#$setValidity
来改变表单控件的验证状态。
View向model更新 - 同样,当用户操作控件后,其值发生了变化,控件后调用NgModelController#$setViewValue
。它将继续调用NgModelController#$parsers
中的每个函数,它们每个函数都有机会转换数据,并通过NgModelController#$setValidity()
来改变表单控件的验证状态。
下面的例子中,我们将创建两个指令。
第一个是integer
,它用来验证输入是否是一个合法的integer. 例如1.23
不合法。注意,我们会把验证函数放在数组的最前端,而不是最后面。这是因为我们想让这个验证器首先检查输入的值。
第二个是smart-float
. 它可以把1.2
和1,2
(注意用的是逗号)都转换为一个合法的数字1.2
。注意,我们不能使用html5中的number
控件,因为它不会允许我们输入1,2
。
index.html
<script src="http://code.angularjs.org/angular-1.0.1.min.js"></script>
<script src="script.js"></script>
<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 a valid float number!</span>
</div>
</form>
</div>
scripts.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.$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;
}
});
}
};
});
jsfiddle上的demo
ngModel
)来自定义表单控件Angular实现了所有的基本控件,比如:input
, select
, textarea
,大多数情况下够用了。但如果你需要更灵活的功能,你可以使用directive来创建属于自己的表单控件。
为了让你自定义的控件能与ngModel
一起工作,并且还能“双向绑定”,你需要:
实现render
方法。当它添加到NgModelController#$formatters
后,它将会用来渲染数据。* 调用$setViewValue
方法。每当用户与控件交互后,model都需要被更新,使用这个方法。通常这放在一个DOM事件的listener中完成。
参见$compileProvider
了解更深入的内容。
面的例子展示了如何给一个contentEditable
元素添加“双向绑定”
index.html
<script src="http://code.angularjs.org/angular-1.0.1.min.js"></script>
<script src="script.js"></script>
<div contentEditable="true" ng-model="content" title="Click to edit">Some</div>
<pre>model = {{content}}```
<style type="text/css">
div[contentEditable] {
cursor: pointer;
background-color: #D0D0D0;
}
</style>
script.js
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());
}
};
});