在掌握AngularJS核心概念(模块、控制器、指令)后,入门应用的关键是“解决实际业务场景”——比如用户注册表单、数据列表加载、本地数据持久化等。本文以“高频应用场景”为核心,通过三个完整应用案例,带你掌握AngularJS在实际开发中的应用方法,同时梳理应用开发中的通用思路与避坑技巧。
一、应用场景1:用户注册表单(表单验证+数据提交)
表单是前端与用户交互的核心载体,AngularJS提供了完善的内置表单验证能力,无需大量手动JS代码,即可实现“必填项校验、格式校验、实时提示”等功能。以下以“用户注册表单”为例,落地表单应用开发。
1. 需求拆解
实现用户名、手机号、密码、确认密码的输入校验;
实时显示错误提示(如“用户名不能为空”“手机号格式错误”);
所有校验通过后,才能提交表单;
提交时显示加载状态,避免重复提交。
2. 完整应用代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AngularJS入门应用 - 用户注册表单</title>
<script src="https://apps.bdimg.com/libs/angular.js/1.8.2/angular.min.js"></script>
<style>
.form-container {
width: 450px;
margin: 50px auto;
padding: 25px;
border: 1px solid #eee;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
}
.form-title {
margin-top: 0;
margin-bottom: 20px;
color: #333;
text-align: center;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
color: #666;
font-size: 14px;
}
.form-control {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
box-sizing: border-box;
}
/* 输入框校验状态样式:成功(绿色边框)、错误(红色边框) */
.form-control.ng-valid.ng-dirty {
border-color: #43a047;
}
.form-control.ng-invalid.ng-dirty {
border-color: #e53935;
}
.error-message {
margin-top: 5px;
color: #e53935;
font-size: 12px;
height: 16px; /* 固定高度,避免提示显示/隐藏时表单跳动 */
}
.submit-btn {
width: 100%;
padding: 12px;
border: none;
background: #4285f4;
color: white;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
transition: background 0.3s ease;
}
.submit-btn:disabled {
background: #90caf9;
cursor: not-allowed;
}
.submit-success {
margin-top: 15px;
padding: 10px;
background: #e8f5e9;
color: #2e7d32;
border-radius: 4px;
text-align: center;
display: none;
}
</style>
</head>
<!-- 挂载AngularJS模块 -->
<body ng-app="registerApp">
<div class="form-container" ng-controller="registerCtrl">
<h3 class="form-title">用户注册</h3>
<!-- 表单:ng-submit绑定提交事件,novalidate禁用浏览器默认验证 -->
<form name="registerForm" ng-submit="submitForm()" novalidate>
<!-- 用户名输入框 -->
<div class="form-group">
<label for="username">用户名 *</label>
<input type="text"
id="username"
name="username"
class="form-control"
ng-model="user.username"
ng-required="true" <!-- 必填校验 -->
ng-minlength="3" <!-- 最小长度3 -->
ng-maxlength="16"> <!-- 最大长度16 -->
<!-- 错误提示:根据不同校验结果显示不同信息 -->
<div class="error-message">
<span ng-show="registerForm.username.$dirty && registerForm.username.$error.required">用户名不能为空</span>
<span ng-show="registerForm.username.$dirty && registerForm.username.$error.minlength">用户名至少3个字符</span>
<span ng-show="registerForm.username.$dirty && registerForm.username.$error.maxlength">用户名最多16个字符</span>
</div>
</div>
<!-- 手机号输入框 -->
<div class="form-group">
<label for="phone">手机号 *</label>
zhiq.zhaopin.com/moment/86391611
zhiq.zhaopin.com/moment/86391621
zhiq.zhaopin.com/moment/86391629
zhiq.zhaopin.com/moment/86391638
zhiq.zhaopin.com/moment/86391675
zhiq.zhaopin.com/moment/86391696
zhiq.zhaopin.com/moment/86391700
zhiq.zhaopin.com/moment/86391702
zhiq.zhaopin.com/moment/86417690
zhiq.zhaopin.com/moment/86417943
zhiq.zhaopin.com/moment/86418006
zhiq.zhaopin.com/moment/86418083
zhiq.zhaopin.com/moment/86418152
zhiq.zhaopin.com/moment/86418206
zhiq.zhaopin.com/moment/86418366
zhiq.zhaopin.com/moment/86418682
zhiq.zhaopin.com/moment/86419393
zhiq.zhaopin.com/moment/86419459
zhiq.zhaopin.com/moment/86419610
zhiq.zhaopin.com/moment/86419708
<input type="tel"
id="phone"
name="phone"
class="form-control"
ng-model="user.phone"
ng-required="true"
ng-pattern="/^1[3-9]\d{9}$/"> <!-- 手机号格式正则校验 -->
<div class="error-message">
<span ng-show="registerForm.phone.$dirty && registerForm.phone.$error.required">手机号不能为空</span>
<span ng-show="registerForm.phone.$dirty && registerForm.phone.$error.pattern">请输入正确的11位手机号</span>
</div>
</div>
<!-- 密码输入框 -->
<div class="form-group">
<label for="password">密码 *</label>
<input type="password"
id="password"
name="password"
class="form-control"
ng-model="user.password"
ng-required="true"
ng-minlength="6"
ng-maxlength="20">
<div class="error-message">
<span ng-show="registerForm.password.$dirty && registerForm.password.$error.required">密码不能为空</span>
<span ng-show="registerForm.password.$dirty && registerForm.password.$error.minlength">密码至少6个字符</span>
<span ng-show="registerForm.password.$dirty && registerForm.password.$error.maxlength">密码最多20个字符</span>
</div>
</div>
<!-- 确认密码输入框 -->
<div class="form-group">
<label for="confirmPwd">确认密码 *</label>
<input type="password"
id="confirmPwd"
name="confirmPwd"
class="form-control"
ng-model="user.confirmPwd"
ng-required="true"
ng-match="user.password"> <!-- 自定义指令:匹配密码 -->
<div class="error-message">
<span ng-show="registerForm.confirmPwd.$dirty && registerForm.confirmPwd.$error.required">确认密码不能为空</span>
<span ng-show="registerForm.confirmPwd.$dirty && registerForm.confirmPwd.$error.match">两次输入的密码不一致</span>
</div>
</div>
<!-- 提交按钮:表单验证通过(registerForm.$valid)且未加载时才启用 -->
<button type="submit" class="submit-btn" ng-disabled="!registerForm.$valid || isSubmitting">
<span ng-if="!isSubmitting">注册</span>
<span ng-if="isSubmitting">注册中...</span>
</button>
<!-- 提交成功提示 -->
<div class="submit-success" ng-show="submitSuccess">
注册成功!即将跳转到登录页...
</div>
</form>
</div>
<script>
// 1. 创建应用模块
const registerApp = angular.module('registerApp', []);
// 2. 自定义指令ngMatch:用于确认密码与密码匹配校验
registerApp.directive('ngMatch', function() {
return {
require: 'ngModel', // 依赖ngModel指令
scope: {
ngMatch: '=' // 绑定要匹配的目标值(这里是user.password)
},
link: function(scope, element, attrs, ngModel) {
// 监听目标值(密码)和当前值(确认密码)的变化,触发校验
scope.$watchGroup(['ngMatch', ngModel.$viewValue], function() {
ngModel.$setValidity('match', ngModel.$viewValue === scope.ngMatch);
});
}
};
});
// 3. 创建控制器
registerApp.controller('registerCtrl', function($scope, $timeout) {
// 初始化用户数据
$scope.user = {
username: '',
phone: '',
password: '',
confirmPwd: ''
};
$scope.isSubmitting = false; // 提交加载状态
$scope.submitSuccess = false; // 提交成功标记
// 表单提交方法
$scope.submitForm = function() {
// 再次校验表单(防止绕过前端校验提交)
if (!$scope.registerForm.$valid) {
// 若表单无效,触发所有输入框的脏值状态(显示错误提示)
angular.forEach($scope.registerForm.$error.required, function(field) {
field.$setDirty();
});
return;
}
// 显示加载状态
$scope.isSubmitting = true;
// 模拟API提交(实际项目中替换为$http请求)
console.log('提交的用户数据:', $scope.user);
// 模拟提交成功(2秒后)
$timeout(function() {
$scope.isSubmitting = false;
$scope.submitSuccess = true;
// 模拟3秒后跳转(实际项目中用$location.path('/login'))
$timeout(function() {
alert('跳转到登录页');
}, 3000);
}, 2000);
};
});
</script>
</body>
</html>
3. 应用核心要点
内置表单验证指令:ng-required(必填)、ng-minlength(最小长度)、ng-maxlength(最大长度)、ng-pattern(正则匹配),无需手动写校验逻辑;
表单状态变量:AngularJS自动为表单和输入框添加状态变量(如$dirty:是否修改过、$valid:是否有效、$error:错误类型),可直接用于错误提示控制;
自定义指令扩展:通过ngMatch自定义指令实现“确认密码匹配”功能,体现AngularJS的扩展性;
防重复提交:用isSubmitting标记加载状态,禁用提交按钮,避免用户重复点击。
二、应用场景2:商品列表(Ajax数据请求+列表渲染)
实际应用中,数据通常来自后端API,AngularJS提供$http服务用于发送Ajax请求,结合ng-repeat可快速实现“数据加载→列表渲染→状态提示”的完整流程。以下以“商品列表”为例,落地数据请求类应用开发。
1. 需求拆解
页面加载时,发送请求获取商品数据;
加载过程中显示“加载中”提示;
加载失败时显示错误提示,支持重试;
加载成功后,用列表渲染商品(包含图片、名称、价格、销量);
支持按“价格升序/降序”筛选商品。
2. 完整应用代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AngularJS入门应用 - 商品列表</title>
<script src="https://apps.bdimg.com/libs/angular.js/1.8.2/angular.min.js"></script>
<style>
.product-container {
width: 1200px;
margin: 30px auto;
padding: 20px;
box-sizing: border-box;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.filter-select {
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.loading, .error {
text-align: center;
padding: 50px;
color: #666;
}
.error {
color: #e53935;
}
.retry-btn {
padding: 8px 16px;
border: none;
background: #4285f4;
color: white;
border-radius: 4px;
cursor: pointer;
margin-top: 10px;
}
.product-list {
display: flex;
flex-wrap: wrap;
gap: 20px;
}
.product-item {
width: calc(25% - 15px);
border: 1px solid #eee;
border-radius: 8px;
overflow: hidden;
transition: box-shadow 0.3s ease;
}
.product-item:hover {
box-shadow: 0 5px 15px rgba(0,0,0,0.08);
}
.product-img {
width: 100%;
height: 200px;
object-fit: cover;
}
.product-info {
padding: 15px;
}
.product-name {
margin: 0 0 10px;
font-size: 16px;
color: #333;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
height: 40px;
}
.product-price {
margin: 0 0 5px;
font-size: 18px;
color: #e53935;
font-weight: bold;
}
.product-sales {
margin: 0;
font-size: 12px;
color: #999;
}
.no-data {
text-align: center;
padding: 50px;
color: #999;
width: 100%;
}
</style>
</head>
<body ng-app="productApp">
<div class="product-container" ng-controller="productCtrl">
<div class="header">
<h2>商品列表</h2>
<!-- 价格排序筛选 -->
<select class="filter-select"
ng-model="sortType"
ng-change="sortProducts()">
<option value="">默认排序</option>
<option value="priceAsc">价格升序</option>
<option value="priceDesc">价格降序</option>
</select>
</div>
<!-- 加载中状态 -->
<div class="loading" ng-show="isLoading">
<div>加载中...</div>
</div>
<!-- 加载错误状态 -->
<div class="error" ng-show="isError">
<div>加载失败,请重试!</div>
<button class="retry-btn" ng-click="loadProducts()">重试</button>
</div>
<!-- 商品列表 -->
<div class="product-list" ng-show="!isLoading && !isError">
<div class="product-item" ng-repeat="product in products">
<img src="{{ product.imgUrl }}" alt="{{ product.name }}" class="product-img">
<div class="product-info">
<h3 class="product-name">{{ product.name }}</h3>
<p class="product-price">¥{{ product.price.toFixed(2) }}</p>
<p class="product-sales">销量:{{ product.s</doubaocanvas>