AngularJS基础教程

$apply()、$timeout()、$watch()

前言

AngularJS 中,应尽量避免在“外部”修改数据,若不可避免,则应尽量使用 $timeout()、$http 这类 AngularJS 内部定义的方法或对象。

$apply()

看下面这个案例:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>$apply()、$timeout()、$watch()</title>
<script src="js/angular.min.js"></script>
</head>
<body>
<div ng-app="myApp">
<div ng-controller="myController">
{{name}}
</div> <div ng-bind="name"></div>
</div>
<script type="text/javascript">
var app = angular.module('myApp', []);
app.controller('myController', function ($scope) {
$scope.name = 'Hello';
setTimeout(function(){
$scope.name = 'AngularJS';
}, 2000);
});
</script>
</body>
</html>

在这段代码中,我们期望在网页上显示“Hello”,等两秒钟后,变成“AngularJS”。

在浏览器中运行,结果却并非我们所期望的。实际运行结果是,网页打开后显示了“Hello”,但等了两秒钟后,却没有如我们期望的那样变成“AngularJS”。也就是说,在 setTimeout 中给 $scope.name 赋的新值并没有展示到页面上去。

这是因为在 AngularJS 中,在 AngularJS 外部对 AngularJS 内部的数据进行修改后,并不会实时作用到 View 层,需要这样写:

var app = angular.module('myApp', []);
app.controller('myController', function ($scope) {
$scope.name = 'Hello';
setTimeout(function(){
// 注意这里的 $apply(),它会强制将当前 $scope 中的数据应用到 View 中去
$scope.$apply(function(){
$scope.name = 'AngularJS';
});
}, 2000);
});

$timeout()

针对以上这个案例,AngularJS 提供了一种更好的解决方案。AngularJS 提供了一个名为 $timeout 的服务,它与 window 对象下的 setTimeout() 的作用类似,也是起到“延迟执行”的作用,并且其语法也是一样的。我们看看使用 $timeout() 修改过的代码:

var app = angular.module('myApp', []);
app.controller('myController', function ($scope) {
$scope.name = 'Hello';
$timeout(function(){
$scope.name = 'AngularJS';
}, 2000);
});

在浏览器中查看,我们会发现网页上先显示了“Hello”,等了两秒后变成了“AngularJS”。

$timeout()setTimeout() 一样,也是“延迟执行”,但在 $timeout() 里修改的数据却不需要使用 $apply() 就能展示在网页上。这是因为在 $timeout() 自身的实现中已经做了类似 $apply() 的事情。

AngularJS 中,应尽量避免在“外部”修改数据,若不可避免,则应尽量使用 $timeout()、$http 这类 AngularJS 内部定义的方法或对象。

$watch()

$watch() 方法用来监视数据的变化。

下面的代码实现了一个简单的订单管理功能。首先我们实现将数据显示在网页上的功能:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>$apply()、$timeout()、$watch()</title>
<script src="js/angular.min.js"></script>
</head>
<body>
<div ng-app="myApp">
<div ng-controller="myController">
<div>
单价:{{order.price}}
</div>
<div>
数量:
<!-- 用 ng-options 为 select 元素生成了一组 option 元素,数据来源于 $scope 中定义的 numOptions -->
<select ng-model="order.num" ng-options="item for item in numOptions"></select>
</div>
<div>
总价:{{order.price * order.num}}
</div>
<div>
运费:10
</div>
<div>
<button>Submit</button>
</div>
</div>
</div>
<script type="text/javascript">
var app = angular.module('myApp', []);
app.controller('myController', function ($scope) {
$scope.numOptions = [1, 2, 3]; // 下拉选择框中的选项
$scope.order = { // 订单数据
price:50,
num:1
};
});
</script>
</body>
</html>

以上代码实现的功能有:当改变数量时,总价也会随之改变。

现在我希望添加另一个功能:当总价大于100时,将运费改为0。因此“运费”那一栏的数值不是应该写死的,应该是计算出来的。另外,我希望在点击“Submit” button 后将最新的运费数据打印在控制台。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>$apply()、$timeout()、$watch()</title>
<script src="js/angular.min.js"></script>
</head>
<body>
<div ng-app="myApp">
<div ng-controller="myController">
<div>
单价:{{order.price}}
</div>
<div>
数量: <select ng-model="order.num" ng-options="item for item in numOptions"></select>
</div>
<div>
总价:{{sum()}}
</div>
<div>
运费:{{sum() > 100 ? 0 : 10}}
</div>
<div>
<button ng-click="submit()">Submit</button>
</div>
</div>
</div>
<script type="text/javascript">
var app = angular.module('myApp', []);
app.controller('myController', function ($scope) {
$scope.numOptions = [1, 2, 3];
$scope.order = {
price:50,
num:1
};
$scope.sum = function(){
return $scope.order.price * $scope.order.num;
};
// 用户点击“Submit” button 后执行的方法
$scope.submit = function(){
// ??? 因为订单数据里没有“运费”,所以这里不知道输出什么
};
});
</script>
</body>
</html>

在这段代码中,由于“运费”是在 HTML 部分计算出来的,因此在 JS 部分无法从订单数据中取得最终的“运费”。所以,“运费”不应该是在 HTML 部分计算出来的,而应该是在 JS 部分计算出来并保存到订单数据中的:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>$apply()、$timeout()、$watch()</title>
<script src="js/angular.min.js"></script>
</head>
<body>
<div ng-app="myApp">
<div ng-controller="myController">
<div>
单价:{{order.price}}
</div>
<div>
数量: <select ng-model="order.num" ng-options="item for item in numOptions"></select>
</div>
<div>
总价:{{sum()}}
</div>
<div>
运费:{{order.ship}}
</div>
<div>
<button ng-click="submit()">Submit</button>
</div>
</div>
</div>
<script type="text/javascript">
var app = angular.module('myApp', []);
app.controller('myController', function ($scope) {
$scope.numOptions = [1, 2, 3];
$scope.order = {
price:50,
num:1
};
$scope.sum = function(){
return $scope.order.price * $scope.order.num;
};
// 用户点击“Submit” button 后执行的方法
$scope.submit = function(){
console.log($scope.ship);
};
// 监听 $scope.sum 的改变
$scope.watch($scope.sum, function(newVal, oldVal){
// newVal 是 $scope.sum() 最新计算出来的值,oldVal 是 上一次计算出来的值
$scope.ship = newVal > 100 ? 0 : 10;
});
});
</script>
</body>
</html>

最后,来个广告

若你觉得此文不错,请分享,若认为尚需改进,请点讚。

结束语