AngularJS是为了克服HTML在构建应用上的不足而设计的。HTML是一门很好的为静态文本展示设计的声明式语言,但要构建WEB应用的话它就显得乏力了,所以AngularJS做了一些工作来来解决静态网页技术在构建动态应用上的不足。
AngularJS的初衷是为了简化web APP开发,精髓是简单。但是国内外有很多AngularJS的教程,都着重讲AngularJS的强大之处,从而增加了AngularJS学习的难度,本教程试图用通俗的语言讲解最为基础、最为实用的内容,简化学习进程、降低学习难度是本教程的初衷。
本系列教程以翻译Chris Smith的Angualr Basics为梗概,融合博主自己的理解,为大家提供一个简单明了的学习教程。
本文为系列教程第5篇作用域,翻译自Scope。
在Angular中scope到底是什么?从名字来看,你可能猜测它是表征程序状态的上下文,保护我们不受JS饱受诟病的全局作用域污染。听起来很简单,我们好像可以直接跳到下一章了。
不要太急,本章虽说不长,但是会涉及一些非常重要的内容:scope继承和体系。一个经典的Angular应用可能会创建10几个、上百个甚至近千个scope,这些scope形成一个体系。
在我们更进一步之前,让我们先搭建本章案例的环境。
在基础章,我们学会了通过向html元素添加ng-app
指令告诉Angular需要处理那部分文档。
<!-- index.html -->
<body ng-app="app">
<!-- Other examples to be inserted here. -->
</body>
ng-app
指令的参数就是我们应用的根模块(本案例命名为app,只是为了简单起见)。Angular的模块我们会在下一张详细介绍。现在,我们只需考虑这个启动样板文件(也可以先暂时忽略掉)。
/* module.js */
angular.module(‘app‘, []);
angular.module(‘app‘).config([‘$controllerProvider‘, function($controllerProvider) {
$controllerProvider.allowGlobals();
}]);
现在让我们把这些放到一边,来点实用的。
在上一章,我们学习了如何在$scope引用上附加属性的方式准备模型,让我们重复下练习。
/* name-controller.js */
function NameController($scope) {
$scope.name = "First";
}
使用ng-controller
指令,我们可以在DOM元素环境中调用控制器函数。我们在控制器中指定给scope的任意数据都在p元素内可用。
<!-- name-controller.html -->
<p ng-controller="NameController">
{{name}}
</p>
编译结果为: First
我们调用NameController元素之外的元素,不能访问该控制器的scope。我们来测试一下。
<!-- name-controller-nested.html -->
<div>
<p>
Outside the scope: {{name}}
</p>
<div ng-controller="NameController">
<p>
Inside the scope: {{name}}
</p>
</div>
</div>
编译结果为:
Outside the scope:
Inside the scope: First
现在我们明白了scope和controller相匹配。接下来,我们来看下相反的情况:只有一个scope的Angular应用。
为了帮助我们避免scope使用中可能出现的麻烦,Angular为每一个控制器创建一个新的scope。但是,scope是层次结构的,在每个应用scope层次结构最底端的根scope是单一的。我们可以通过在控制器中声明特定的参数$rootScope
访问它,最好不要使用普通的$scope
引用。
/* root-name-controller.js */
function RootNameController($rootScope) {
$rootScope.name = "First";
}
看起来有点天真,实际上它可以正常工作。
<!-- root-name-controller.html -->
<p ng-controller="RootNameController">
{{name}}
</p>
编译结果为:First.
但是,当我们在另一个控制器里给它指定相同的属性时,就出问题了。
/* second-root-name-controller.js */
function SecondRootNameController($rootScope) {
$rootScope.name = "Second";
}
这个不对,因为$rootScope
在我们的应用中作为单体存在,所以我们只能给它指定一个值。
<!-- root-name-controllers.html -->
<p ng-controller="RootNameController">
{{name}}
</p>
<p ng-controller="SecondRootNameController">
{{name}}
</p>
编译结果为:
Second
Second
实际上,我们的SecondRootNameController
中指定的name值覆盖了RootNameController
中的值,所以这个问题就是全局变量的问题,是吧?
通过自动为每个控制器,Angular可以为我们提供非常安全的环境,让我们重新控制器使用正确的方式发布模型,弃用$rootscope
而使用$scope
。注意一点,上文我们的$rootscope
的案例仅仅是为了演示为什么每一个控制器自动生成一个scope对象。
/* second-name-controller.js */
function SecondNameController($scope) {
$scope.name = "Second";
}
使用本章开头的NameController
和上面的SecondNameController
,我们来演示下$scope
对象的隔离性。
<!-- second-name-controller.html -->
<p ng-controller="NameController">
{{name}}
</p>
<p ng-controller="SecondNameController">
{{name}}
</p>
编译结果为:
First
Second
该案例产生了正确的结果,每个控制器输出了自己的名字。当一个控制器不是另一个控制器的子元素,产生了隔离性(isolation)。如果是嵌套的情况,又会发生什么呢?
我们稍微修改上面的案例,把第二个控制器SecondNameController
移到加载NameController
的div元素的子元素上,来看看嵌套的时候会发生什么。
<!-- nested-second-name-controller.html -->
<div ng-controller="NameController">
<p>
{{name}}
</p>
<p ng-controller="SecondNameController">
{{name}}
</p>
</div>
编译结果为:
First
Second
依然能够正常工作。如果我们改变两个p的位置会怎么样,不妨尝试一下,输出结果也会翻转一下,是吧。
好像看起来嵌套的控制器依然可以相互隔离,但其实是个误导。实际上,Angular基于DOM中的相对位置组织控制器的层次结构,嵌套的控制器会继承祖先的属性。我们这里没有变化的原因是,子scope的name属性覆盖了父scope的name属性。
让我们改变一下子scope的属性名称,来试试看scope嵌套中的继承特性。
/* child-controller.js */
function ChildController($scope) {
$scope.childName = "Child";
}
我们在父元素和子元素中分别使用两个属性。
<-- child-controller.html -->
<div ng-controller="NameController">
<p>
{{name}} and {{childName}}
</p>
<p ng-controller="ChildController">
{{name}} and {{childName}}
</p>
</div>
编译结果为:
First and
First and Child
这样就很明了了,父控制器不能访问子控制器的属性,但是子控制器可以访问自己的属性和父控制器的属性。
因为name是继承的属性,好像我们可以在父scope和子scope中同时看到它的变化,事实是不是这样?我们给父控制器绑定个input来测试下。
<!-- child-controller-edit.html -->
<div ng-controller="NameController">
<p>
{{name}}
</p>
<p ng-controller="ChildController">
{{name}}
</p>
<input type=‘text‘ ng-model=‘name‘>
</div>
编译结果如下动图所示。
如果您尝试编辑name属性,您将会看到期望的结果,如上动图所示。name属性会在两个scope环境内更新。但是,注意我们这里把name绑定在父scope上。
您可能觉得我们修改子scope属性会同步更新到父scope中,是这样的吗?让我们测试下,在两个控制器中增加input。
在下面的案例中,第一步修改头一个input,第二个修改第二个input,看看结果是不是你想的那样。
<!-- child-controller-input.html -->
<div ng-controller="NameController">
<p>
name: {{name}}
<br>
<input type=‘text‘ ng-model=‘name‘>
</p>
<p ng-controller="ChildController">
name: {{name}}
<br>
<input type=‘text‘ ng-model=‘name‘>
</p>
</div>
编译结果如下动图所示。
Angular使用JS的普通原型继承,某种程度上来说不错,因为我们无需学习新的东西,某种程度上又不好,因为JS的原型继承不太直观。
对JS对象设置属性导致对象该属性的自动生成,对于属性继承来说不是好消息,因为它将覆盖对象自己的属性。
哈!简单的说,如果您没有修改第二个input,子scope中没有name属性。一旦您修改了input,自动在子scope建立name属性,赋input的值,并且阻断对父scope中name属性的访问。
明白了吗?不明白的话,可以稍微休息下,去学习下原型继承。如果明白了,我们继续。
如果我们想在继承的scope中修改模型数据,我们该如何做?
很简单,我们只需要把name属性移动另一个对象上。
/* info-controller.js */
function InfoController($scope) {
$scope.info = {name: "First"};
}
/* child-info-controller.js */
function ChildInfoController($scope) {
$scope.info.childName = "Child";
}
<!-- info-controller.html -->
<div ng-controller="InfoController">
<p>
{{info.name}} and {{info.childName}}
<br>
<input type=‘text‘ ng-model=‘info.name‘>
</p>
<p ng-controller="ChildInfoController">
{{info.name}} and {{info.childName}}
<br>
<input type=‘text‘ ng-model=‘info.name‘>
</p>
</div>
编译之后结果动图所示。
注意,ChildInfoController
依赖于它的父控制器创建info对象。 如果您编辑ChildInfoController
的源文件,把函数体替换为$scope.info = {childName: "Second"};
会发生什么呢?尝试一下。
function InfoController($scope) {
$scope.info = {name: "First"};
}
function ChildInfoController($scope) {
$scope.info = {childName: "Second"};
}
<div ng-controller="InfoController">
<p>
{{info.name}} and {{info.childName}}
<br>
<input type=‘text‘ ng-model=‘info.name‘>
</p>
<p ng-controller="ChildInfoController">
{{info.name}} and {{info.childName}}
<br>
<input type=‘text‘ ng-model=‘info.name‘>
</p>
</div>
大部分时候,Angular的双向绑定可以像您期望的那样完成交互: 当您使用input发生改变时,UI也会同步的发生更改。但是,计算属性(指从其它scope数据中拿到的数据),例如下面案例中所示的sum
就是一个计算属性,不是那么一回事儿。
/* sum-controller.js */
function SumController($scope) {
$scope.values = [1,2];
$scope.newValue = 1;
$scope.add = function() {
$scope.values.push(parseInt($scope.newValue));
};
// Broken -- doesn‘t trigger UI update
$scope.sum = $scope.values.reduce(function(a, b) {
return a + b;
});
}
本例的模板文件中,如下所示,我们使用select表单让用户选择数据(1,2,3)添加到数组的末尾。(顺便说一句,控制器给新的值提供了初始值,否则Angular应该为select元素添加空白option以避免武断地给新值赋予第一个option。这个行为对scope用处不大,但是我们有必要了解一下)
<!-- sum-controller.html -->
<p ng-controller="SumController">
<select ng-model="newValue" ng-options="n for n in [1,2,3]"></select>
<input type="button" value="Add" ng-click="add()">
The sum of {{values}} is {{sum}}.
</p>
单击add按钮可以把数字添加到数组里去,但是,悲催的是,没有正确的反应到sum里去。
让我们来做点修正,把计算值得部分移到一个回调函数里去。将这个回调函数(具备一个watchExpression
参数,本案例就是要被计算的属性)作为参数传递给$scope.$watch
,当属性发生改变时调用计算函数求和。
/* sum-watch-controller.js */
function SumController($scope) {
$scope.values = [1,2];
$scope.newValue = 1;
$scope.add = function() {
$scope.values.push(parseInt($scope.newValue));
};
$scope.$watch(‘values‘, function () {
$scope.sum = $scope.values.reduce(function(a, b) {
return a + b;
});
}, true);
}
可以看到,求和的函数就会随着变量的变化发生作用了。
Angular内置的双向绑定指令已经很牛了,但是,我们也经常时不时的有些需要添加的行为。比如,如果您想让用户通过esc
键同时清除文本框当前状态和scope中的绑定状态。我们应该如何编写这个自定义事件?
<!-- escape-controller.html -->
<div ng-controller="EscapeController">
<input type="text" ng-model="message">
is bound to
"<strong ng-bind="message"></strong>".
Press <code>esc</code> to clear it!
</div>
首先,我们需要在控制器中声明一个特定名字的参数$element
,以便于Angular建立一个关联DOM的引用。使用给定元素的bind
方法,我们可以注册一个回调函数,侦听keyup事件。在该回调函数中,我们更新scope属性。简单吧,我们试一下,试着输入点东西,然后按esc键。
/* escape-controller.js */
function EscapeController($scope, $element) {
$scope.message = ‘‘;
$element.bind(‘keyup‘, function (event) {
if (event.keyCode === 27) { // esc key
// Broken -- doesn‘t trigger UI update
$scope.message = ‘‘;
}
});
}
没有正常工作吧。因为我们这里使用Dom,我们需要告诉Angular什么时候重新渲染视图。我们把需要改变的内容打包成一个回调函数传递给$scope.$apply
。
/* escape-apply-controller.js */
function EscapeController($scope, $element) {
$scope.message = ‘‘;
$element.bind(‘keyup‘, function (event) {
if (event.keyCode === 27) { // esc key
$scope.$apply(function() {
$scope.message = ‘‘;
});
}
});
}
这样就可以正常工作了吧。
如果您试图让Angular适应于MVC模式,scopes会是一个难题。刚开始非常简单,Scopes是模型层的一部分,在Angular中,一个对象直到能够作为scope的属性可以访问时,才成为模型。但是,当您研究scopes如何通过控制器或者指令绑定到DOM时,这些东西将变得很有意思(我们后头再说)。幸运的是,排除这些学术问题外,如上面案例所示,scopes非常直观、简单易用。
前端开发whqet,关注前端开发,分享相关资源。csdn专家博客,王海庆希望能对您有所帮助,限于作者水平有限,出错难免,欢迎拍砖!
欢迎任何形式的转载,烦请注明装载,保留本段文字。
本文原文链接,http://blog.csdn.net/whqet/article/details/44925881
欢迎大家访问独立博客http://whqet.github.io
原文地址:http://blog.csdn.net/whqet/article/details/44925881