AngularJS 输入验证
AngularJS 输入验证学习笔记(详细版)
一、输入验证概述
AngularJS 提供了一套声明式的客户端输入验证机制,通过在 HTML 元素上添加验证属性,结合 ng-model 自动完成验证逻辑,无需手动编写大量验证代码。
1.1 验证工作原理
用户输入
│
▼
ng-model 捕获输入值
│
▼
$validators / $asyncValidators 执行验证
│
▼
更新 $error 对象 + 自动添加 CSS 类
│
▼
视图根据状态显示提示信息
1.2 前提条件
<!-- 1. 必须有 ng-model —— 没有它验证不会生效 -->
<input ng-model="user.name" required>
<!-- 2. 必须有 name 属性 —— 否则无法通过表单访问控件状态 -->
<form name="myForm">
<input name="username" ng-model="user.name" required>
<!-- 可通过 myForm.username 访问验证状态 -->
</form>
<!-- 3. 建议添加 novalidate —— 禁用浏览器原生验证 -->
<form name="myForm" novalidate>
二、内置验证属性详解
2.1 required / ng-required
必填验证,字段不能为空。
<!-- 静态必填 -->
<input type="text" name="username" ng-model="user.username" required>
<!-- 动态必填:根据条件决定是否必填 -->
<input type="text"
name="companyName"
ng-model="user.companyName"
ng-required="user.type === 'enterprise'">
<!-- 当用户类型为企业时,公司名称才必填 -->
$error 键名: required
<span ng-show="myForm.username.$error.required">此字段必填</span>
验证规则:
-
undefined、null、''(空字符串)→ 验证失败 -
0、false→ 验证通过(有值) - 空格字符串
' '→ 验证通过(如需去除空格,配合ng-trim)
<!-- ng-trim 自动去除首尾空格(默认 true) -->
<input type="text" ng-model="user.name" ng-trim="true" required>
<!-- 输入 " " 会被 trim 为 "",验证失败 -->
<!-- 关闭 trim -->
<input type="text" ng-model="user.name" ng-trim="false" required>
<!-- 输入 " " 不会被 trim,验证通过 -->
2.2 minlength / ng-minlength
最小长度验证。
<!-- 静态最小长度 -->
<input type="text" name="username" ng-model="user.username" ng-minlength="3">
<!-- 动态最小长度 -->
<input type="text" name="password" ng-model="user.password" ng-minlength="minLen">
$scope.minLen = 6; // 可动态修改
$error 键名: minlength
<span ng-show="myForm.username.$error.minlength">至少输入3个字符</span>
注意: minlength(HTML5 原生属性)与 ng-minlength(AngularJS 属性)的区别:
| 属性 | 行为 |
|---|---|
minlength="3" |
HTML5 原生验证,AngularJS 不识别 |
ng-minlength="3" |
AngularJS 验证,正确更新 $error
|
始终使用 ng-minlength 而非 minlength。
2.3 maxlength / ng-maxlength
最大长度验证。
<!-- 静态最大长度 -->
<input type="text" name="username" ng-model="user.username" ng-maxlength="20">
<!-- 动态最大长度 -->
<input type="text" name="bio" ng-model="user.bio" ng-maxlength="maxLen">
$error 键名: maxlength
<span ng-show="myForm.username.$error.maxlength">最多输入20个字符</span>
重要区别:
| 属性 | 行为 |
|---|---|
maxlength="20" |
HTML5 原生属性,物理限制输入,超过20字符无法输入 |
ng-maxlength="20" |
AngularJS 验证,允许输入但标记为无效 |
<!-- ❌ 用户无法输入超过20个字符,体验差 -->
<input type="text" ng-model="user.name" maxlength="20">
<!-- ✅ 用户可以输入,但超过20字符时显示错误 -->
<input type="text" ng-model="user.name" ng-maxlength="20">
2.4 min / max
数值范围验证,适用于 type="number" 和 type="date"。
<!-- 数值范围 -->
<input type="number" name="age" ng-model="user.age" min="1" max="150">
<!-- 日期范围 -->
<input type="date" name="birthday" ng-model="user.birthday" min="1900-01-01" max="2024-12-31">
$error 键名: min、max
<span ng-show="myForm.age.$error.min">年龄不能小于1</span>
<span ng-show="myForm.age.$error.max">年龄不能大于150</span>
注意: min/max 同时被 HTML5 和 AngularJS 识别,无需加 ng- 前缀。
2.5 pattern / ng-pattern
正则表达式验证。
<!-- 静态正则(内联写法,用 /.../ 包裹) -->
<input type="text" name="code" ng-model="user.code" ng-pattern="/^[A-Z]{2}d{4}$/">
<!-- 动态正则(绑定到 scope 变量) -->
<input type="text" name="phone" ng-model="user.phone" ng-pattern="phonePattern">
$scope.phonePattern = /^1[3-9]d{9}$/;
$error 键名: pattern
<span ng-show="myForm.code.$error.pattern">格式:2个大写字母+4位数字</span>
常见正则示例:
// 手机号
/1[3-9]d{9}$/
// 邮箱(简化版)
/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$/
// 身份证号
/d{17}[dXx]$/
// 邮政编码
/d{6}$/
// 密码(至少8位,含字母和数字)
/(?=.*[A-Za-z])(?=.*d)[A-Za-zd]{8,}$/
// IP 地址
/((25[0-5]|2[0-4]d|[01]?dd?).){3}(25[0-5]|2[0-4]d|[01]?dd?)$/
// 中文姓名
/[u4e00-u9fa5]{2,6}$/
2.6 type 属性验证
通过设置 type 属性,AngularJS 自动进行格式验证:
| type 值 | 验证规则 | $error 键名 |
|---|---|---|
email |
邮箱格式 | email |
url |
URL 格式 | url |
number |
数字格式 | number |
date |
日期格式 | date |
time |
时间格式 | time |
week |
周格式 | week |
month |
月格式 | month |
datetime-local |
本地日期时间 | datetimelocal |
<!-- 邮箱验证 -->
<input type="email" name="email" ng-model="user.email" required>
<span ng-show="myForm.email.$error.email">邮箱格式不正确</span>
<!-- URL 验证 -->
<input type="url" name="website" ng-model="user.website">
<span ng-show="myForm.website.$error.url">URL 格式不正确</span>
<!-- 数字验证 -->
<input type="number" name="amount" ng-model="user.amount">
<span ng-show="myForm.amount.$error.number">请输入有效数字</span>
type=“email” 验证规则:
- 基本格式:
xxx@xxx.xxx - 不是严格的 RFC 5322 验证,而是实用简化版
- 空值时如果无
required,验证通过
type=“number” 的特殊行为:
- 非数字输入时,
ng-model不会被更新(值为undefined) -
$error.number为true - 这意味着用户输入 “abc” 时,模型值不会变成 “abc”
三、$error 对象深入解析
3.1 控件级 $error
每个带 ng-model 和 name 的控件都有自己的 $error 对象:
// myForm.username.$error
{
required: true, // 未填写
minlength: true, // 长度不足
pattern: false, // 正则通过
maxlength: false // 长度未超限
}
- 值为
true→ 该验证未通过 - 值为
false或不存在 → 该验证通过或不适用
3.2 表单级 $error
表单的 $error 按验证类型分组,值为未通过该验证的控件数组:
// myForm.$error
{
required: [
myForm.username, // username 未填写
myForm.email // email 未填写
],
email: [
myForm.email // email 格式错误
],
minlength: [
myForm.username // username 长度不足
]
}
3.3 利用 $error 的实用技巧
<!-- 统计表单错误数 -->
<p>共有 {{ countErrors(myForm.$error) }} 个验证错误</p>
$scope.countErrors = function(formError) {
var count = 0;
angular.forEach(formError, function(controls) {
count += controls.length;
});
return count;
};
四、验证状态与 CSS 类
4.1 自动添加的 CSS 类
| 状态 | 添加的 CSS 类 | 移除的 CSS 类 |
|---|---|---|
| 未修改 | ng-pristine |
ng-dirty |
| 已修改 | ng-dirty |
ng-pristine |
| 验证通过 | ng-valid |
ng-invalid |
| 验证失败 | ng-invalid |
ng-valid |
| 未失焦 | ng-untouched |
ng-touched |
| 已失焦 | ng-touched |
ng-untouched |
| 特定验证通过 | ng-valid-xxx |
— |
| 特定验证失败 | ng-invalid-xxx |
— |
示例: <input name="email" type="email" ng-model="user.email" required>
验证失败时,该元素会有以下 CSS 类:
ng-dirty ng-invalid ng-invalid-required ng-invalid-email ng-touched
验证通过时:
ng-dirty ng-valid ng-valid-required ng-valid-email ng-touched
4.2 实用 CSS 样式
/* 基础样式 */
input, select, textarea {
border: 2px solid #ddd;
padding: 8px 12px;
transition: border-color 0.3s;
}
/* 已修改且有效 —— 绿色 */
input.ng-dirty.ng-valid,
select.ng-dirty.ng-valid,
textarea.ng-dirty.ng-valid {
border-color: #4caf50;
}
/* 已修改且无效 —— 红色 */
input.ng-dirty.ng-invalid,
select.ng-dirty.ng-invalid,
textarea.ng-dirty.ng-invalid {
border-color: #f44336;
}
/* 原始状态不显示验证颜色 */
input.ng-pristine,
select.ng-pristine,
textarea.ng-pristine {
border-color: #ddd;
}
/* 特定验证失败的样式 */
input.ng-invalid-required {
/* 可针对 required 做特殊样式 */
}
/* 错误消息动画 */
.error-message {
color: #f44336;
font-size: 0.85em;
animation: fadeIn 0.3s;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-5px); }
to { opacity: 1; transform: translateY(0); }
}
五、ngMessages 验证消息管理
5.1 传统方式的问题
<!-- ❌ 繁琐:每个错误都要写 ng-show 判断 -->
<span ng-show="myForm.email.$dirty && myForm.email.$error.required">邮箱必填</span>
<span ng-show="myForm.email.$dirty && myForm.email.$error.email">邮箱格式错误</span>
<span ng-show="myForm.email.$dirty && myForm.email.$error.minlength">太短</span>
<span ng-show="myForm.email.$dirty && myForm.email.$error.maxlength">太长</span>
5.2 ngMessages 方式
<!-- ✅ 简洁 -->
<div ng-messages="myForm.email.$error" ng-if="myForm.email.$dirty">
<div ng-message="required">邮箱必填</div>
<div ng-message="email">邮箱格式错误</div>
<div ng-message="minlength">太短</div>
<div ng-message="maxlength">太长</div>
</div>
核心优势:
- 默认只显示第一条错误(优先级由上到下)
- 代码量大幅减少
- 支持模板复用
5.3 消息优先级
<div ng-messages="myForm.username.$error" ng-if="myForm.username.$dirty">
<!-- 优先级从上到下,只显示第一条匹配的错误 -->
<div ng-message="required">用户名必填</div> <!-- 优先级1 -->
<div ng-message="minlength">用户名太短</div> <!-- 优先级2 -->
<div ng-message="maxlength">用户名太长</div> <!-- 优先级3 -->
<div ng-message="pattern">格式不正确</div> <!-- 优先级4 -->
</div>
设计原则: 把最重要的错误消息放在最前面。
5.4 显示所有错误
<div ng-messages="myForm.username.$error"
ng-if="myForm.username.$dirty"
ng-messages-multiple>
<!-- ng-messages-multiple:显示所有匹配的错误 -->
<div ng-message="required">用户名必填</div>
<div ng-message="minlength">用户名太短</div>
<div ng-message="pattern">格式不正确</div>
</div>
5.5 复用消息模板
<!-- 定义通用模板 -->
<script type="text/ng-template" id="validation-messages.html">
<div ng-message="required">此字段为必填项</div>
<div ng-message="minlength">输入内容太短</div>
<div ng-message="maxlength">输入内容太长</div>
<div ng-message="email">邮箱格式不正确</div>
<div ng-message="url">URL 格式不正确</div>
<div ng-message="number">请输入有效数字</div>
<div ng-message="min">值太小</div>
<div ng-message="max">值太大</div>
<div ng-message="pattern">格式不符合要求</div>
<div ng-message="date">日期格式不正确</div>
</script>
<!-- 引用模板 -->
<div ng-messages="myForm.email.$error"
ng-if="myForm.email.$dirty"
ng-messages-include="validation-messages.html">
</div>
<!-- 引用模板 + 追加自定义消息(自定义消息优先于模板) -->
<div ng-messages="myForm.password.$error"
ng-if="myForm.password.$dirty"
ng-messages-include="validation-messages.html">
<div ng-message="pattern">密码需至少8位,包含字母和数字</div>
</div>
六、自定义验证
6.1 同步验证器($validators)
// 验证年龄是否为偶数(示例)
app.directive('evenNumber', [function() {
return {
require: 'ngModel',
link: function(scope, element, attrs, ngModelCtrl) {
ngModelCtrl.$validators.evenNumber = function(modelValue, viewValue) {
if (ngModelCtrl.$isEmpty(modelValue)) {
return true; // 空值交给 required 处理
}
return modelValue % 2 === 0;
};
}
};
}]);
<input type="number" name="age" ng-model="user.age" even-number>
<div ng-messages="myForm.age.$error" ng-if="myForm.age.$dirty">
<div ng-message="evenNumber">年龄必须是偶数</div>
</div>
$validators 规则:
- 返回
true→ 验证通过 - 返回
false→ 验证失败,$error[验证名] = true - 函数参数:
(modelValue, viewValue) - 空值时应返回
true,让required单独处理
6.2 异步验证器($asyncValidators)
适用于需要后端验证的场景,如检查用户名是否已存在。
app.directive('uniqueUsername', ['$q', '$http', function($q, $http) {
return {
require: 'ngModel',
link: function(scope, element, attrs, ngModelCtrl) {
ngModelCtrl.$asyncValidators.uniqueUsername = function(modelValue, viewValue) {
if (ngModelCtrl.$isEmpty(modelValue)) {
return $q.resolve();
}
// 返回 Promise
return $http.get('/api/check-username', {
params: { username: modelValue }
}).then(function(response) {
if (response.data.exists) {
return $q.reject('用户名已存在'); // 验证失败
}
return true; // 验证通过
}, function() {
return $q.reject('验证服务不可用'); // 请求失败
});
};
}
};
}]);
<input type="text"
name="username"
ng-model="user.username"
required
unique-username>
<div ng-messages="myForm.username.$error" ng-if="myForm.username.$dirty">
<div ng-message="required">用户名必填</div>
<div ng-message="uniqueUsername">该用户名已被注册</div>
</div>
<!-- 显示验证中状态 -->
<span ng-show="myForm.username.$pending">验证中...</span>
$asyncValidators 规则:
- 返回 resolved Promise → 验证通过
- 返回 rejected Promise → 验证失败
- 验证进行中时,
$pending对象中会包含该验证器 - 异步验证器只在同步验证通过后才执行
6.3 验证器执行顺序
输入值变化
│
▼
1. $parsers 处理(viewValue → modelValue 转换)
│
▼
2. $validators 同步验证
│ ── 任一失败则停止,不执行异步验证
▼
3. $asyncValidators 异步验证
│ ── 并行执行所有异步验证器
▼
4. 更新 $error 和 CSS 类
6.4 复杂自定义验证:密码强度
app.directive('passwordStrength', [function() {
return {
require: 'ngModel',
link: function(scope, element, attrs, ngModelCtrl) {
// 密码强度等级
var STRENGTH = {
WEAK: 'weak',
MEDIUM: 'medium',
STRONG: 'strong'
};
function getStrength(value) {
if (!value) return null;
var score = 0;
if (value.length >= 8) score++;
if (value.length >= 12) score++;
if (/[A-Z]/.test(value)) score++;
if (/[a-z]/.test(value)) score++;
if (/d/.test(value)) score++;
if (/[^A-Za-z0-9]/.test(value)) score++;
if (score <= 2) return STRENGTH.WEAK;
if (score <= 4) return STRENGTH.MEDIUM;
return STRENGTH.STRONG;
}
// 验证器:至少中等强度
ngModelCtrl.$validators.passwordStrength = function(modelValue) {
if (ngModelCtrl.$isEmpty(modelValue)) return true;
var strength = getStrength(modelValue);
return strength !== STRENGTH.WEAK;
};
// 在 scope 上暴露强度值,供视图显示
scope.$watch(function() {
return ngModelCtrl.$viewValue;
}, function(newVal) {
scope.passwordStrength = getStrength(newVal);
});
}
};
}]);
<input type="password"
name="password"
ng-model="user.password"
required
password-strength>
<!-- 强度指示器 -->
<div ng-if="user.password" ng-switch="passwordStrength">
<div ng-switch-when="weak" style="color:red">弱</div>
<div ng-switch-when="medium" style="color:orange">中</div>
<div ng-switch-when="strong" style="color:green">强</div>
</div>
<div ng-messages="myForm.password.$error" ng-if="myForm.password.$dirty">
<div ng-message="required">密码必填</div>
<div ng-message="passwordStrength">密码强度不足,至少需包含大小写字母和数字</div>
</div>
6.5 比较验证:确认密码
app.directive('matchField', [function() {
return {
require: 'ngModel',
scope: {
matchField: '='
},
link: function(scope, element, attrs, ngModelCtrl) {
ngModelCtrl.$validators.matchField = function(modelValue) {
if (ngModelCtrl.$isEmpty(modelValue) && ngModelCtrl.$isEmpty(scope.matchField)) {
return true;
}
return modelValue === scope.matchField;
};
// 当比较字段变化时,重新验证
scope.$watch('matchField', function() {
ngModelCtrl.$validate();
});
}
};
}]);
<input type="password" name="password" ng-model="user.password" required>
<input type="password"
name="confirmPassword"
ng-model="user.confirmPassword"
required
match-field="user.password">
<div ng-messages="myForm.confirmPassword.$error" ng-if="myForm.confirmPassword.$dirty">
<div ng-message="required">请确认密码</div>
<div ng-message="matchField">两次密码不一致</div>
</div>
七、$setValidity 与手动验证控制
7.1 $setValidity
// 在指令中
ngModelCtrl.$setValidity('customError', false); // 设置为无效
ngModelCtrl.$setValidity('customError', true); // 设置为有效
// 在控制器中通过表单访问
$scope.myForm.email.$setValidity('serverError', false);
<div ng-message="serverError">服务器验证失败:{{ serverErrorMsg }}</div>
7.2 服务器端验证反馈
$scope.submit = function() {
$http.post('/api/register', $scope.user)
.then(function(response) {
// 成功
}, function(error) {
// 服务器返回字段级错误
if (error.data.field === 'email') {
$scope.myForm.email.$setValidity('serverError', false);
$scope.serverErrorMsg = error.data.message;
}
});
};
7.3 $validate 手动触发验证
// 手动触发某个控件的所有验证
$scope.myForm.email.$validate();
// 通常在程序修改模型值后使用
$scope.user.email = 'new@example.com';
$scope.myForm.email.$validate();
八、验证时机控制
8.1 默认行为
AngularJS 在每次 ng-model 值变化时都会触发验证,这意味着用户还没输入完就可能看到错误提示。
8.2 只在失焦时显示错误
<!-- 方式一:用 $touched 判断 -->
<div ng-messages="myForm.email.$error" ng-if="myForm.email.$touched">
<div ng-message="required">邮箱必填</div>
<div ng-message="email">邮箱格式错误</div>
</div>
8.3 只在提交后显示错误
<form name="myForm" ng-submit="submit()" novalidate>
<input type="email" name="email" ng-model="user.email" required>
<div ng-messages="myForm.email.$error" ng-if="myForm.$submitted">
<div ng-message="required">邮箱必填</div>
<div ng-message="email">邮箱格式错误</div>
</div>
<button type="submit">提交</button>
</form>
8.4 自定义显示策略指令
// 只在失焦或提交后显示错误
app.directive('showErrors', [function() {
return {
restrict: 'A',
require: '^form',
link: function(scope, element, attrs, formCtrl) {
var inputEl = element[0].querySelector('[name]');
var inputName = attrs.showErrors || inputEl.getAttribute('name');
var inputCtrl = formCtrl[inputName];
scope.$watch(function() {
return {
dirty: inputCtrl.$dirty,
touched: inputCtrl.$touched,
submitted: formCtrl.$submitted,
invalid: inputCtrl.$invalid
};
}, function(state) {
var show = state.invalid && (state.dirty || state.touched || state.submitted);
element.toggleClass('has-error', show);
}, true);
}
};
}]);
<div show-errors>
<label>邮箱</label>
<input type="email" name="email" ng-model="user.email" required>
<div ng-messages="myForm.email.$error" ng-if="myForm.email.$invalid && (myForm.email.$dirty || myForm.email.$touched || myForm.$submitted)">
<div ng-message="required">邮箱必填</div>
<div ng-message="email">邮箱格式错误</div>
</div>
</div>
九、各表单元素验证要点
9.1 input[type=“text”]
<input type="text"
name="username"
ng-model="user.username"
required
ng-minlength="3"
ng-maxlength="20"
ng-pattern="/^[a-zA-Z0-9_]+$/">
9.2 input[type=“email”]
<input type="email" name="email" ng-model="user.email" required>
<!-- $error.email:邮箱格式验证 -->
9.3 input[type=“number”]
<input type="number" name="age" ng-model="user.age" required min="0" max="150">
<!-- $error.number:非数字 -->
<!-- $error.min / $error.max:范围 -->
注意: 非数字输入时 ng-model 为 undefined,不会更新视图值。
9.4 input[type=“url”]
<input type="url" name="website" ng-model="user.website">
<!-- $error.url:URL 格式验证 -->
<!-- 合法格式:http://example.com -->
9.5 input[type=“checkbox”]
<input type="checkbox" name="agree" ng-model="user.agree" required>
<!-- required 验证:必须勾选(值为 true) -->
<span ng-show="myForm.agree.$error.required">必须同意条款</span>
9.6 input[type=“radio”]
<!-- radio 的 required 验证:必须选择一个 -->
<input type="radio" name="gender" ng-model="user.gender" value="male" required> 男
<input type="radio" name="gender" ng-model="user.gender" value="female" required> 女
<span ng-show="myForm.gender.$error.required">请选择性别</span>
9.7 select
<select name="city" ng-model="user.city" required>
<option value="">请选择</option>
<option value="bj">北京</option>
<option value="sh">上海</option>
</select>
<span ng-show="myForm.city.$error.required">请选择城市</span>
<!-- 使用 ng-options(推荐) -->
<select name="city"
ng-model="user.city"
ng-options="c.code as c.name for c in cities"
required>
<option value="">请选择</option>
</select>
9.8 textarea
<textarea name="bio"
ng-model="user.bio"
required
ng-minlength="10"
ng-maxlength="500"
rows="4">
</textarea>
十、验证与 $parsers / $formatters
10.1 管道流程
视图值 (viewValue)
│
▼ $parsers (数组,按顺序执行)
│ ── 验证通常在此进行
│ ── 返回值传递给下一个 parser
▼
模型值 (modelValue)
│
▼ $formatters (数组,按顺序执行)
│ ── 模型→视图的转换
▼
视图值 (viewValue)
10.2 使用 $parsers 添加验证
app.directive('noSpaces', [function() {
return {
require: 'ngModel',
link: function(scope, element, attrs, ngModelCtrl) {
ngModelCtrl.$parsers.push(function(viewValue) {
if (/s/.test(viewValue)) {
ngModelCtrl.$setValidity('noSpaces', false);
return undefined; // 不更新模型
}
ngModelCtrl.$setValidity('noSpaces', true);
return viewValue; // 正常传递
});
}
};
}]);
$parsers vs $validators:
| 特性 | $parsers | $validators |
|---|---|---|
| 执行时机 | viewValue → modelValue 转换时 | 模型值变化后 |
| 能否修改值 | 可以 | 不可以 |
| 推荐程度 | 旧方式(1.3 之前) | 推荐(1.3+) |
| 返回值 | 返回处理后的值或 undefined | 返回 boolean |
建议: 优先使用 $validators,仅在需要转换值时使用 $parsers。
十一、完整实战:多字段注册表单
<!DOCTYPE html>
<html ng-app="regApp">
<head>
<meta charset="UTF-8">
<title>注册表单 - 输入验证</title>
<script src="angular.js"></script>
<script src="angular-messages.js"></script>
<style>
* { box-sizing: border-box; }
body { font-family: Arial, sans-serif; max-width: 500px; margin: 40px auto; }
.form-group { margin-bottom: 20px; }
.form-group label { display: block; margin-bottom: 5px; font-weight: bold; }
input, select, textarea {
width: 100%; padding: 10px; border: 2px solid #ddd;
border-radius: 4px; font-size: 14px;
transition: border-color 0.3s, box-shadow 0.3s;
}
input:focus, select:focus, textarea:focus {
outline: none; border-color: #2196f3;
box-shadow: 0 0 0 3px rgba(33,150,243,0.1);
}
input.ng-dirty.ng-valid, select.ng-dirty.ng-valid {
border-color: #4caf50;
}
input.ng-dirty.ng-invalid, select.ng-dirty.ng-invalid {
border-color: #f44336;
}
.error-messages { color: #f44336; font-size: 0.85em; margin-top: 4px; }
.error-messages div { margin-top: 2px; }
.strength-bar { height: 4px; margin-top: 6px; border-radius: 2px; transition: all 0.3s; }
.strength-weak { background: #f44336; width: 33%; }
.strength-medium { background: #ff9800; width: 66%; }
.strength-strong { background: #4caf50; width: 100%; }
.pending { color: #ff9800; font-style: italic; }
button {
padding: 12px 30px; border: none; border-radius: 4px;
font-size: 16px; cursor: pointer; margin-right: 10px;
}
.btn-primary { background: #2196f3; color: white; }
.btn-primary:disabled { background: #ccc; cursor: not-allowed; }
.btn-reset { background: #eee; color: #333; }
</style>
</head>
<body ng-controller="RegController">
<h2>用户注册</h2>
<form name="regForm" ng-submit="register()" novalidate>
<!-- 用户名 -->
<div class="form-group">
<label>用户名</label>
<input type="text"
name="username"
ng-model="user.username"
required
ng-minlength="3"
ng-maxlength="20"
ng-pattern="/^[a-zA-Z0-9_]+$/"
unique-username
placeholder="3-20位字母、数字或下划线">
<div class="error-messages"
ng-messages="regForm.username.$error"
ng-if="regForm.username.$dirty">
<div ng-message="required">用户名必填</div>
<div ng-message="minlength">用户名至少3个字符</div>
<div ng-message="maxlength">用户名最多20个字符</div>
<div ng-message="pattern">只能包含字母、数字和下划线</div>
<div ng-message="uniqueUsername">该用户名已被注册</div>
</div>
<div class="pending" ng-show="regForm.username.$pending">验证中...</div>
</div>
<!-- 邮箱 -->
<div class="form-group">
<label>邮箱</label>
<input type="email"
name="email"
ng-model="user.email"
required
placeholder="example@domain.com">
<div class="error-messages"
ng-messages="regForm.email.$error"
ng-if="regForm.email.$dirty">
<div ng-message="required">邮箱必填</div>
<div ng-message="email">邮箱格式不正确</div>
</div>
</div>
<!-- 手机号 -->
<div class="form-group">
<label>手机号</label>
<input type="text"
name="phone"
ng-model="user.phone"
required
ng-pattern="/^1[3-9]d{9}$/"
placeholder="11位手机号">
<div class="error-messages"
ng-messages="regForm.phone.$error"
ng-if="regForm.phone.$dirty">
<div ng-message="required">手机号必填</div>
<div ng-message="pattern">请输入有效的手机号</div>
</div>
</div>
<!-- 密码 -->
<div class="form-group">
<label>密码</label>
<input type="password"
name="password"
ng-model="user.password"
required
ng-minlength="8"
password-strength
placeholder="至少8位">
<div class="strength-bar"
ng-class="{'strength-weak': passwordStrength==='weak',
'strength-medium': passwordStrength==='medium',
'strength-strong': passwordStrength==='strong'}"
ng-if="user.password">
</div>
<div class="error-messages"
ng-messages="regForm.password.$error"
ng-if="regForm.password.$dirty">
<div ng-message="required">密码必填</div>
<div ng-message="minlength">密码至少8个字符</div>
<div ng-message="passwordStrength">密码强度不足</div>
</div>
</div>
<!-- 确认密码 -->
<div class="form-group">
<label>确认密码</label>
<input type="password"
name="confirmPwd"
ng-model="user.confirmPwd"
required
match-field="user.password"
placeholder="再次输入密码">
<div class="error-messages"
ng-messages="regForm.confirmPwd.$error"
ng-if="regForm.confirmPwd.$dirty">
<div ng-message="required">请确认密码</div>
<div ng-message="matchField">两次密码不一致</div>
</div>
</div>
<!-- 年龄 -->
<div class="form-group">
<label>年龄</label>
<input type="number"
name="age"
ng-model="user.age"
required
min="1"
max="150"
placeholder="1-150">
<div class="error-messages"
ng-messages="regForm.age.$error"
ng-if="regForm.age.$dirty">
<div ng-message="required">年龄必填</div>
<div ng-message="number">请输入有效数字</div>
<div ng-message="min">年龄不能小于1</div>
<div ng-message="max">年龄不能大于150</div>
</div>
</div>
<!-- 同意条款 -->
<div class="form-group">
<label>
<input type="checkbox" name="agree" ng-model="user.agree" required>
我已阅读并同意服务条款
</label>
<div class="error-messages" ng-if="regForm.agree.$dirty && regForm.agree.$error.required">
必须同意条款才能注册
</div>
</div>
<button type="submit" class="btn-primary" ng-disabled="regForm.$invalid">
注册
</button>
<button type="button" class="btn-reset" ng-click="reset()">
重置
</button>
</form>
</body>
</html>
十二、常见问题与陷阱
12.1 novalidate 必须加
<!-- ❌ 浏览器原生验证和 AngularJS 验证同时生效 -->
<form name="myForm">
<!-- ✅ 禁用浏览器原生验证 -->
<form name="myForm" novalidate>
12.2 ng-model 是前提
<!-- ❌ 没有 ng-model,验证不生效 -->
<input type="text" name="username" required>
<!-- ✅ 必须有 ng-model -->
<input type="text" name="username" ng-model="user.username" required>
12.3 name 属性不可省略
<!-- ❌ 没有 name,无法通过表单访问控件 -->
<input type="text" ng-model="user.name" required>
<!-- ✅ 有 name 才能访问 myForm.username -->
<input type="text" name="username" ng-model="user.name" required>
12.4 type=“number” 的特殊行为
<!-- 输入 "abc" 时:ng-model 为 undefined,$error.number = true -->
<!-- 输入 "12.5" 时:ng-model 为 12.5,验证通过 -->
<!-- 输入 "12.5.3" 时:ng-model 为 undefined,$error.number = true -->
<!-- 如果需要字符串类型的数字,用 type="text" + ng-pattern -->
<input type="text" ng-model="user.amount" ng-pattern="/^d+(.d{1,2})?$/">
12.5 验证与 ng-if 的交互
<!-- ng-if 会销毁/重建元素,导致表单控件从表单中移除/添加 -->
<div ng-if="showField">
<input name="optional" ng-model="data.value" required>
</div>
<!-- 当 showField 为 false 时,myForm.optional 不存在 -->
<!-- 当 showField 为 true 时,myForm.optional 重新创建 -->
12.6 异步验证的防抖
// 异步验证每次输入都会发请求,需要防抖
app.directive('uniqueUsername', ['$q', '$http', '$timeout', function($q, $http, $timeout) {
return {
require: 'ngModel',
link: function(scope, element, attrs, ngModelCtrl) {
var timeoutPromise;
ngModelCtrl.$asyncValidators.uniqueUsername = function(modelValue) {
if (ngModelCtrl.$isEmpty(modelValue)) return $q.resolve();
// 取消上一次的请求
if (timeoutPromise) $timeout.cancel(timeoutPromise);
// 延迟 500ms 发送请求
return $timeout(function() {}, 500)
.then(function() {
return $http.get('/api/check-username', {
params: { username: modelValue }
});
})
.then(function(response) {
if (response.data.exists) {
return $q.reject('已存在');
}
return true;
});
};
}
};
}]);
十三、总结思维导图
AngularJS 输入验证
├── 前提条件
│ ├── ng-model(必须)
│ ├── name 属性(必须)
│ └── novalidate(建议)
├── 内置验证属性
│ ├── required / ng-required ── 必填
│ ├── ng-minlength / ng-maxlength ── 长度
│ ├── min / max ── 范围
│ ├── ng-pattern ── 正则
│ └── type="email|url|number|date" ── 格式
├── 验证状态
│ ├── $valid / $invalid ── 验证结果
│ ├── $pristine / $dirty ── 修改状态
│ ├── $touched / $untouched ── 焦点状态
│ ├── $error ── 错误详情
│ └── $pending ── 异步验证中
├── 自动 CSS 类
│ ├── ng-valid / ng-invalid
│ ├── ng-dirty / ng-pristine
│ ├── ng-touched / ng-untouched
│ └── ng-valid-xxx / ng-invalid-xxx
├── ngMessages
│ ├── ng-messages ── 消息容器
│ ├── ng-message ── 单条消息
│ ├── ng-messages-multiple ── 显示多条
│ └── ng-messages-include ── 复用模板
├── 自定义验证
│ ├── $validators ── 同步验证
│ ├── $asyncValidators ── 异步验证
│ ├── $setValidity() ── 手动设置
│ ├── $validate() ── 手动触发
│ └── $parsers ── 旧方式(1.3前)
├── 验证时机
│ ├── 实时验证(默认)
│ ├── 失焦验证($touched)
│ ├── 提交验证($submitted)
│ └── 自定义策略
└── 常见陷阱
├── novalidate 遗漏
├── ng-model 缺失
├── type="number" 行为
├── ng-if 销毁控件
└── 异步验证需防抖
以上内容系统覆盖了 AngularJS 输入验证的内置属性、状态追踪、消息管理、自定义验证、验证时机控制及常见陷阱,可作为完整的学习和开发参考。