作用域、控制器、指令
作用域
应用的作用域是和应用的数据模型相关联的,同时作用域也是表达式执行的上下文。$scope对象是定义应用业务逻辑、控制器方法和视图属性的地方。
作用域是应用状态的基础。基于动态绑定,我们可以依赖视图在修改数据时立刻更新$scope,也可以依赖$scope在其发生变化时立刻重新渲染视图。
AngularJS将$scope设计成和DOM类似的结构,因此$scope可以进行嵌套,也就是说可以引用父级$scope中的属性。
作用域提供了监视数据模型变化的能力。它允许开发者使用其中的apply机制,将数据模型的变化在整个应用范围内进行通知。我们在作用域的上下文中定义和执行表达式,同时它也是将事件通知给另一个控制器和应用其他部分的中介。
将应用的业务逻辑都放在控制器中,而将相关的数据都放在控制器的作用域中,这是非常完美的架构。
àng-app同$rootScope绑定,$rootScope是所有$scope对象的最上层。
àscope对象就是普通的JavaScript对象,我们可以在其上随意修改或添加属性
àscope对象在AngularJS中充当数据模型,但与传统数据模型并不一样,它不负责处理和操作数据,它只是视图和HTML之间的桥梁,它是视图和控制器之间的胶水
àscope的所有属性都可以自动被视图访问到。
àAngularJS不会对不包含AngularJS特殊声明的元素进行任何处理
我们可以在AngularJS应用的模版中使用多种标记
指令:将DOM元素增强为可复用的DOM组件的属性或元素
值绑定:模板语法{{}}可以将表达式绑定到视图上
过滤器:可以在视图中使用函数,用来进行格式化
表单控件:用来检验用户输入的控件
作用域能做什么
提供观察者以监视数据模型的变化
可以将数据模型的变化通知给整个应用,甚至是系统外的组件
可以进行嵌套,隔离业务功能和数据
给表达式提供运算时所需的执行环境
作用域包含了渲染视图时所需的功能和数据,它是所有视图的唯一源头。可以将作用域理解成视图模型(view model)
指令和作用域
指令在AngularJS中被广泛使用,指令通常不会创建自己的$scope,但也有例外,比如ng-controller和ng-repeat指令会创建自己的子作用域并将它们附加到DOM元素上。
控制器
控制器的作用是增强视图。AngularJS的控制器是一个函数,用来向视图的作用域中添加额外的功能。我们用它来给作用域对象设置初始状态,并添加自定义行为。
var app = angular.module('app', []);
app.controller("firstController', function($scope) { $scope.message = 'hello'; });
使用依赖注入可以尽可能精简控制器。
AngularJS同其他JavaScript框架最主要的一个区别是,控制器并不适合用来执行DOM操作、格式化操作或数据操作,以及除存储数据模型之外的状态维护操作。它只是视图和$scope之间的桥梁。
设计良好的应用会将复杂的逻辑放到指令和服务中,通过使用指令和服务,我们可以将控制器重构成一个轻量且更易维护的形式。
表达式
{{expression}}将一个变量绑定到$scope上。
à所有表达式都在所属的作用域内部执行,并有访问本地$scope的权限
à如果表达式发生了TypeError和ReferenceError并不会抛出异常
à不允许使用任何流程控制功能(ifelse)
à可以接受过滤器和过滤器链
过滤器
过滤器用来格式化需要展示给用户的数据,AngularJS提供了内置过滤器,也提供饿了自定义过滤器的途径。
指令
自定义HTML元素和属性
指令本质上就是AngularJS扩展具有自定义功能的HTML元素的途径。
angular.module("myApp",[])
.directive("myDirective", function() {
return { restrict:'E',template:'<a href=http://baidu.com>Click me to go to baidu</a>'}; });
通过AngularJS模块API中的.directive()方法,我们可以通过传入一个字符串和一个函数来注册一个新指令,其中字符串是这个指令的名字,函数应该返回一个对象。
directive()方法返回的对象中包含了用来定义和配置指令所需的方法和属性。
事实上,声明指令并不需要创建一个新的自定义元素。声明指令本质上是在HTML中通过元素、属性、类或注释来添加功能。
<my-directive></my-directive>
<div my-directive></div>
<div class="my-directive"></div>
<!—directive:my-directive-->
在指令的定义中,设置restrict,可以告诉AngularJS在编译HTML时用哪种声明格式来匹配指令定义。可以设置一个或多个格式。
Eà元素 Aà属性 Cà类 Mà注释
无论有多少种声明属性的方式,我们坚持使用属性的方式,因为它有比较好的跨浏览器兼容性:restrict:'A'
指令与表达式
由于指令可以用属性的形式调用,那么给属性赋值也是可行的
<h1 ng-init="greeting='helloworld'">The greeting is: {{greeting}}</h1>
用表达式来声明指令
几种合法的表达式声明
<my-directive="someExpression"></my-directive>
<div my-directive="someExpression"></div>
<div class="my-directive:someExpression"></div>
<!—directive: my-directive someExpression -->
向指令中传递数据
AngularJS并没有限制在指令的模板中硬编码字符串
如果不将URL和链接文本混在指令内部,可以为其他使用我们指令的人提供更好的体验。我们的目标是关注指令的公共接口,就像其他任何编程语言一样。
template:'<a href="{{myUrl}}">{{myLinkText}}</a>'
在HTML中可以这样使用指令
<div my-directive my-url="http://www.baidu.com" my-link-text="Click me to go to Google"></div>
在浏览器中运行,根据结果可以发现,标签中href和标签内部文本都没有设置成功。
有好几种途径可以设置指令内部作用域的值,最简单的办法就是使用由所属控制器提供的已经存在的作用域
尽管简单,共享状态会导致很多其他问题,如果控制器被移除,或者在控制器的作用域中也定义了一个叫做myUrl的属性,我们就被迫要修改代码,这是成本很高且让人沮丧的事情。
AngularJS允许通过创建新的子作用域或者隔离作用域来解决这个常见的问题。
修改后的指令,看起来是这样
angular.module("myApp", [])
.directive("myDirective", function() {
return {
restrict:'A',
replace:true,
scope: {
myUrl: '@',
myLinkText:'@'
},
template:'<a href="{{myUrl}}">' + '{{myLinkText}}</a>' }; });
由于指令会创建一个隔离作用域,所以如果要实现双向数据绑定,需要一个新的绑定策略
dirApp.directive("myDirective", function() {
return {
restrict: "A",
replace: true,
scope: {
myUrl: '=someAttr',
myLinkText: "@"
},
template: "
<div>
<label>My Url Field:</label>
<input type='text'
ng-model='myUrl' >
<a href='{{myUrl}}'>{{myLinkText}}</a>
</div>"
};
});
唯一的修改是用"="的绑定策略代替了"@"
内置指令
ng-disabled
示例1:在下面这个例子中,按钮一直禁用,直到用户在文本字段中输入内容:
<input type="text" ng-model="someProperty" placeholder="TypetoEnable">
<button ng-model="button" ng-disabled="!someProperty">AButton</button>
示例2:在下面的例子中,文本字段会被禁用5秒,直到isDisabled属性设置为true
<textarea ng-disabled="isDisabled">Wait5seconds</textarea>
angular.module("myApp", [])
.run(function($rootScope, $timeout) {
$rootScope.isDisabled = true;
$timeout(function() {
$rootScope.isDisabled = false;
},5000); });
ng-readonly
示例1:通过ng-readonly可以将某个返回真或假的表达式同是否出现readonly属性进行绑定
Type here to make sibling readonly:
<input type="text" ng-model="someProperty"><br>
<input type="text" ng-readonly="someProperty" value="Some text here">
ng-checked
示例1:在下面的例子中,我们通过ng-init指令将someProperty的值设置为true。将someProperty同ng-checked绑定在一起,AngularJS会输出标准的HTML checked属性,这样默认会把复选框勾选:
<label>someProperty = {{someProperty}}</label>
<input type="checkbox" ng-checked="someProperty" ng-init="someProperty" ng-init="someProperty=true" ng-model="someProperty">
示例2:这个例子刚好和例1相反
<label>someProperty={{anotherProperty}}</label>
<input type="checkbox" ng-checked="anotherProperty" ng-init="anotherProperty=false" ng-model="anotherProperty">
ng-selected
示例1:ng-selected可以对是否出现option标签的selected属性进行绑定:
<label>select two fish</label>
<input type="checkbox" ng-model="isTwoFish"><br>
<select>
<option>One Fish</option>
<option ng-selected="isTwoFish">Two Fish</option>
<select>
ng-href
当使用当前作用域中的属性动态创建URL时,应该用ng-href代替href
这里的潜在问题是,当用户点击一个由插值动态生成的链接时,如果插值尚未生效,将会跳转到错误的页面。如果使用ng-href,AngularJS会等到插值生效后,再执行点击链接的行为
<!—当href包含一个{{expression}}时总是使用ng-href -->
<a ng-href="{{myHref}}">I am feeling lucky, when I load</a>
<!—用户单击之前,href不会加载-->
<a href="{{myHref}}">I am feeling 404</a>
将插值生效事件延迟2秒来观察实际的行为
angular.module("myApp",[])
.run(function($rootScope, $timeout) {$rootScope.myHref="http://baidu.com";},2000); });
ng-src
AngularJS会告诉浏览器在ng-src对应的表达式生效之前不要加载图像
示例1:
<h1>WrongWay</h1>
<img src="{{imgSrc}}">
<h1>Rightway</h1>
<img ng-src="{{imgSrc}}">
angular.module("myApp",[])
.run(function($rootScope, $timeout) {$timeout(function() { $rootScope.imgSrc='.png';},2000); });
ng-app
任何具有ng-app属性的DOM元素都将被标记为$rootScope的起始点
$rootScope是作用域链的起始点,任何嵌套在ng-app内的指令都会继承它
在JavaScript代码中,通过run()方法来访问$rootScope
可以在整个文档中只使用一次ng-app,如果需要在一个页面中放置多个AngularJS应用,需要手动引导应用。
ng-controller
内置指令ng-controller的作用是为嵌套在其中的指令创建一个子作用域,避免将所有操作和模型都定义在$rootScope上,用这个指令可以在一个DOM元素上放置控制器。
ng-controller接受一个参数expression,这个参数是必需的。expression参数是一个i额AngularJS表达式。子$scope只是一个JavaScript对象,其中含有从父级$scope中通过原型继承得到的方法和属性,包括应用的$rootScope.
$scope对象的职责是承载DOM中指令所共享的操作和模型。
操作指的是$scope上的标准JavaScript方法
模型指的是$scope上保存的包含瞬时状态数据的JavaScript对象。持久化状态的数据应该保存到服务中,服务的作用是处理模型的持久化。
子作用域提供了一个干净的对象供我们使用。
值传递与引用传递
下面这个例子通过引用传递,在父作用域和子作用域之间实现数据双向共享:
<div ng-controller="someController">
{{someModel.someValue}}
<button ng-click="someAction()">Communicate to child</button>
<div ng-controller="childController">
{{someModel.someValue}}
<button ng-click="childAction()">Communicate to parent</button>
</div>
</div>
var myApp = angular.module("myApp",[]);
myApp.controller("someController", function($scope) {
$scope.someModel = {someValue: "nothing"};
$scope.someAction = function() {$scope.someModel.someValue = "parent";};});
myApp.controller("childController", function($scope) {$scope.childAction = function() {$scope.someModel.someValue = "child";};});
ng-include
使用ng-include可以加载、编译并包含外部HTML片段到当前的应用中。模板的URL被限制在与应用文档相同的域和协议下,可以通过白名单或包装成被信任的值来突破限制。更进一步,需要考虑跨域资源共享和同源规则来确保模版可以在任何浏览器中正常加载。
要注意,使用ng-include时AngularJS会自动创建一个子作用域,如果你想使用某个特定的作用域,必须在同一个DOM元素上添加ng-controller指令,比如
<div ng-include="/myTemplateName.html" ng-controller="myController" ng-init="name='world'"> Hello {{name}} </div>
ng-switch
这个指令和ng-switch-when及on="propertyName"一起使用,可以在propertyName发生变化时渲染不同指令到视图中。
示例1:下面这个例子,如果输入的内容为Ari,那么ng-switch-default对应的标签p就会消失,取而代之的ng-switch-when所对应的h1的内容。
<input type="text" ng-model="person.name">
<div ng-switch on="person.name">
<p ng-switch-default>And the winner is</p>
<h1 ng-switch-when="Ari">{{person.name}}</h1>
</div>
ng-view
该指令用来设置将被路由管理和放置在HTML中的视图的位置。
ng-if
使用ng-if指令可以完全根据表达式的值在DOM中生成或移除一个元素。如果赋值给ng-if的表达式的值是false,那对应的元素将会从DOM中移除,否则对应元素的一个克隆将被重新插入DOM中。
ng-if同ng-show以及ng-hide指令最本质的区别是,它不是通过css显示或隐藏DOM节点,而是真正生成或移除节点。
通过ng-if移除了的元素重新加载进来以后会恢复原始状态。因为元素在被移除的同时,相应的作用域也被销毁。
ng-repeat
ng-repeat用来遍历一个集合或为集合中的每个元素生成一个模板实例。集合中的每个元素都会被赋予自己的模板和作用域。同时,每个模板实例的作用域都会暴漏一些特殊的属性。
$index 遍历的进度(0----length-1)
$first 当元素是遍历的第一个时值为true
$middle 当元素不是第一个也不是最后一个时值为true
$last 当元素是遍历的最后一个时值为true
$even 偶数
$odd 奇数
示例1: 下面的例子展示了用$odd和$even来制作一个红蓝相间的列表。记住,JavaScript中数组的索引从0开始,所以用!$even!$odd进行布尔值反转
<ul ng-controller="peopleController">
<li ng-repeat="person in people" ng-class="{even:!$even,odd:!$odd}"> {{person.name}} lives in {{person.city}} </li>
</ul>
<style>
.odd {background-color: blue;}
.even {background-color: red;}
</style>
angular.module("myApp",[]).controller("peopleController", function($scope) {$scope.people = [{name: "Ari", city: "San Francisco"},{name: "Erik", city: "Seattle"}];});
ng-init
ng-init指令用来在指令被调用时设置内部作用域的初识状态。
{{}}
该语法是AngularJS内置的模板语法,它会在内部$scope和视图之间创建绑定,基于这个绑定,只要$scope发生变化,视图就会随之自动更新。
事实上它也是指令,它是ng-bind的简略形式,用这种形式不需要创建新的元素,所以它常被用在行内文本中。
注意,在屏幕可视的区域内使用{{}}会导致页面加载时未渲染的元素发生闪烁,用ng-bind可以避免这个问题
ng-bind
比较这两个例子
<body ng-init="greeting='hello world'">{{greeting}}</body>
<body ng-init="greeting='hello world'"><p ng-bind="greeting"></p></body>
ng-cloak
还有一种办法来避免未渲染元素闪烁,那就是ng-cloak
<body ng-init="greeting='hello world'"><p ng-clock>{{greeting}}</p></body>
ng-cloak指令会将内部元素隐藏,直到路由调用对应的页面时才显示出来。
ng-bind-template
同ng-bind类似,但可以绑定多个表达式
<div ng-bind-template="{{message}}{{name}}"></div>
ng-model
用来将input select textarea或自定义表单控件同包含它们的作用域中的属性进行绑定。它可以提供并处理表单验证功能,在元素上设置相关的CSS类(ng-valid、ng-invalid等),并负责在父表单中注册控件。
它将当前作用域中运算表达式的值同给定的元素进行绑定。如果属性并不存在,它会隐式创建并将其添加到当前作用域中。
我们应该始终用ngModel来绑定$scope上一个数据模型内的属性,而不是$scope上的属性,这可以避免在作用域或后代作用域中发生属性覆盖。
<input type="text" ng-model="modelName.someProperty">
ng-show/ng-hide
元素的显示或隐藏是通过移除或添加ng-hide这个css类来实现的。.ng-hide类被预先定义在了AngularJS的CSS文件中,并且它的display属性的值为none(用了!important标记)
ng-change
这个指令会在表单输入发生变化时计算给定表达式的值。因为要处理表单输入,这个指令要和ngModel联合起来使用。
<div ng-controller="equationController">
<input type="text" ng-model="equation.x" ng-change="change()" >
<code>{{equation.output}}</code>
</div>
angular.module("myApp",[]).controller("equationController", function($scope) {
$scope.equation = {};
$scope.change = function() {$scope.equation.output = parseInt($scope.equation.x) + 2; };
});
ng-form
ng-form用来在一个表单内部嵌套另一个表单。普通的HTML<form>标签不允许嵌套,但ng-form可以。
这意味着内部所有的子表单都合法时,外部的表单才会合法。这对于用ng-repeat动态创建表单是非常有用的。
由于不能通过字符插值来给输入元素动态生成name属性,所以需要将ng-form指令内每组重复的输入字段都包含在一个外部表单元素内。
下面的CSS类会根据表单的验证状态自动设置
表单合法时设置ng-valid
表单不合法 ng-invalid
表单未进行修改时 ng-pristine
表单已经修改 ng-dirty
AngularJS不会将表单提交到服务器,除非它指定了action属性。要指定提交表单时调用哪个JavaScript方法,使用下面两个指令中的一个
ng-submit 在表单元素上使用
ng-click在第一个按钮或submit类型(input[type=submit])的输入字段上使用。
为了避免处理程序被多次调用,只使用上面两个指令中的一个。
ng-click
用来指定一个元素被点击时调用的方法或表达式
ng-select
ng-select用来将数据同HTML的<select>元素进行绑定。这个指令可以和ng-model以及ng-options指令一同使用,构建精细且表现优良的动态表单。
ng-options的值可以是一个内涵表达式,其实这只是一个说法,简单地说,它可以接受一个数组或对象,并对它们进行循环,将内部的内容提供给select标签内部的选项。它可以有下面两种形式
数组作为数据源
用数组中的值做标签
用数组中的值作为选中的标签
用数组中的值做标签组
用数组中的值作为选中的标签组
对象作为数据源
用对象的键-值(key-value)做标签
用对象的键值作为选中的标签
用对象的键值作为标签组
用对象的键值作为选中的标签组
示例1:
<div ng-controller="cityController">
<select ng-model="city" ng-options="city.name for city in cities">
<option value="">Choose City</option>
</select>
Best City: {{city.name}}
</div>
angular.module("myApp",[]).controller("cityController", function($scope) {
$scope.cities = [{name: 'Seattle'},{name: 'San Francisco'},{name: 'Chicago'},{name: 'New York'},{name: 'Boston'}];});
ng-submit
ng-submit用来将表达式同onsubmit事件进行绑定。这个指令同时会阻止默认行为(发送请求并重新加载页面),除非表单不含有action属性。
<form ng-submit="submit()" ng-controller="formController"><input type="submit" name=" name" value="Submit"></form>
ng-class
使用ng-class动态设置元素的类,方法是绑定一个代表所有需要添加的类的表达式。重复的类不会添加。当表达式发生变化时,先前添加的类会被移除,新类会被添加。
示例1:下面的例子会用ng-class在一个随机数大于5时将.red类添加到一个div上:
<div ng-controller="lottoryController">
<div ng-class="{red: x > 5}" ng-if="x > 5">
You won!
</div>
<button ng-click="x = generateNumber()" ng-init="x = 0">
Draw Number
</button>
<p>Number is: {{x}}</p>
</div>
<style>
.red {background-color: red;}
</style>
angular.module("myApp",[]).controller('lottoryController', function($scope) {
$scope.generateNumber = function() {return Math.floor((Math.random()*10+1)); };});
ng-attr-(suffix)
当AngularJS编译DOM时会查找花括号{{some expression}}内的表达式,这些表达式会被自动注册到$watch服务中并更新到$digest循环中,成为它的一部分,但是有些浏览器会对属性进行很苛刻的限制,SVG就是一个例子
<svg><circle cx="{{cx}}"></circle></svg>
运行上面的代码会抛出一个错误,指出我们有一个非法属性,可以用ng-attr-cx来解决
<svg><circle ng-attr-cx="{{cx}}"></circle></svg>