AngularJS介绍
AngularJS 是 Angular1.x 的另一个名字罢了,从 Angular 2.0 版本开始,AngularJS 与 Angular2.0 及以上版本就已经不是同一个框架了。AngularJS诞生于2009年,后被Google收购,用于许多Google产品的开发。AngularJS框架让开发人员们更加关注于业务逻辑和数据,而并非将大量的时间去处理页面层级的操作。通过AngularJS我们可以快速构建SPA应用。
指令(Directive)
AngularJS有一套完整的、可扩展的、用来帮助Web开发的指令集,在建立DOM期间,和HTML相关联的指令会被执行。
在AngularJS中,前缀为ng-
这种属性称之为指令,其作用就是为DOM元素调用方法、定义行为、绑定数据等。
总体来说就是 当一个Angular应用启动时,Angular就会遍历DOM树解析HTML,然后根据指令的不同,完成不同的操作。出了系统提供的一套内置指令外,用户还可以自定义指令。
常用内置指令
虽然官方没有将内置指令分类,根据我个人的理解,内置指令大概可以分为两大类,一类是基本指令
,另一类是事件指令
,例如ng-model
就属于基本指令,ng-click
就属于事件指令。
ng-app
ng-app
用于标记一个Angular的应用程序的管辖范围,它标记在一个AngularJS的作用范围的根对象
上面,系统在执行的时候会自动执行根对象范围内的其他指令。
尽管理论上一个页面中可以使用多个ng-app
指令,但是官方并不推荐这么做,AngularJS在页面找到第一个ng-app
后就不会再去往下找了,所以就算我们使用了多个,也不会起作用,例如下面的代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ng-app</title>
</head>
<body>
<div ng-app="app1" ng-controller="app1Controller">
<input type="button" value="Button1" ng-click="do1()">
</div>
<div ng-app="app2" ng-controller="app2Controller">
<input type="button" value="Button2" ng-click="do2()">
</div>
<script src="../bower_components/angular/angular.js"></script>
<script>
var app1 = angular.module("app1", []);
app1.controller("app1Controller", ["$scope", function($scope) {
$scope.do1 = function() {
console.log(1);
};
}]);
var app2 = angular.module("app2", []);
app2.controller("app2Controller", ["$scope", function($scope) {
$scope.do2 = function() {
console.log(2);
};
}]);
</script>
</body>
</html>
按照理论,我们点击Button1会控制台会输出1,点击Button2会输出2,然后实测点击Button1有效,点击Button2无效,这也证实了之前所说,angular找到第一个ng-app
后就不会找第二个了,但是如果实在是想使用多个,怎么办,也不是没有办法,但是需要我们手动的让第二个Div让被app2
管理。需要使用到angular提供的bootstrap方法了,我们加入如下一行JS代码:
angular.bootstrap(document.querySelector('[ng-app="app2"]'),["app2"]);
需要在方法中传入需要被管理的DOM元素以及来管理它的模块名。这样就可以同时使用多个模块了。
但是,这个方法并不是很好,我们通常的做法是把两个模块拼凑成一个模块,也就是说将这两个模块作为依赖注入到一个大的模块当中,然后在两个Div的父级节点 加上ng-app
即可,所以我们的代码是这样的:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ng-app</title>
</head>
<body ng-app="app">
<div ng-controller="app1Controller">
<input type="button" value="Button1" ng-click="do1()">
</div>
<div ng-controller="app2Controller">
<input type="button" value="Button2" ng-click="do2()">
</div>
<script src="../bower_components/angular/angular.js"></script>
<script>
var app1 = angular.module("app1", []);
app1.controller("app1Controller", ["$scope", function($scope) {
$scope.do1 = function() {
console.log(1);
};
}]);
var app2 = angular.module("app2", []);
app2.controller("app2Controller", ["$scope", function($scope) {
$scope.do2 = function() {
console.log(2);
};
}]);
var app = angular.module("app", ["app1", "app2"]);
</script>
</body>
</html>
ng-bind
ng-bind
指令将作用域(scope)中的值绑定到元素的innerHTML上,其效果会比表达式绑定的方式更加友好,如果绑定的内容包含html,会自动转义。如果想绑定一段html代码,可以使用ng-bind-html
指令。
那么ng-bind
和 {{}} 插值语法
有何区别?当网络环境不好的情况下,AngularJS文件还未加载完成,浏览器会将{{}}
当成一段正常的字符串进行渲染,所以会产生闪烁的效果,但是ng-bind
指令是作为属性插入元素中的,哪怕AngularJS还没加载完成,渲染的时候会被浏览器认为是无效属性,会忽略掉,所以并不会产生闪烁的情况。
ng-bind-html
因为ng-bind
指令如果绑定了一段html代码,会进行自动转义,所以如果需要绑定html代码,我们可以使用ng-bind-html
指令,代码如下:
<body ng-app ng-init="username='<h1>Angular<h1>'">
<strong ng-bind-html="username"></strong>
<script src="../bower_components/angular/angular.js"></script>
</body>
讲道理,这段代码应该没有问题,但是Angular会抛出如下错误:
angular.js:13920 Error: [$sce:unsafe] Attempting to use an unsafe value in a safe context.
它说我们正在尝试在一个安全的环境下使用一个不安全的数据,查阅了官方文档,是这样说的:
他说我们需要使用$sanitize
这个服务,将angular-sanitize.js
引入我们的应用中,并将ngSanitize
注入到我们的模块中,用于检测清理不安全的因素。我们按照官方的文档修改代码如下(angular-sanitize.js需要自己单独下载):
<body ng-app="myApp" ng-init="username='<h1>Angular<h1>'">
<strong ng-bind-html="username"></strong>
<script src="../bower_components/angular/angular.js"></script>
<script src="../bower_components/angular-sanitize/angular-sanitize.js"></script>
<script>
var myApp = angular.module("myApp", ['ngSanitize']);
</script>
</body>
这样就可以正常使用ng-bind-html
指令绑定html代码了,不过需要注意的一点的是:angular-sanitize
的版本要和angular保持一致,否则可能导致一些问题不能正常使用。尽管官方提供了这样一种方式,但是绑定html代码仍然是一种非常不安全的操作。
ng-repeat
ng-repeat
指令用来遍历一个数组来重复创建当前元素,是一个很常用的指令如:
<ul>
<li ng-repeat="item in data track by $index">{{item}}</li>
</ul>
因为Angular默认不允许ng-repeat
的数组中存在重复的元素,一般我们会使用track by $ index的方式解决这个问题。
ng-class
ng-class
指令用于给 HTML 元素动态绑定一个或多个 CSS 类。指令的值可以是字符串,对象,或一个数组。
字符串:多个类名使用空格分隔
对象:需要使用 key-value 对,key 为你想要添加的类名,value 是一个布尔值。只有在 value 为 true 时类才会被添加。
数组:可以由字符串或对象组合组成,数组的元素可以是字符串或对象。
ng-show、ng-hide、ng-if
ng-show
与ng-hide
指令都是动态改变元素是否显示,修改元素的display
属性
而ng-if
是动态改变元素的Dom结构是否存在。
ng-src和ng-href
假如我们的模板下有一张这样的图片:
<img src="{{imgUrl}}"/>
那么当我们的页面加载到ng编译完成之前会一直显示找不到图片,因为我们图片的地址{{imgUrl}}
还没有被替换,为了避免这种情况,我们使用ng-src指令,这样在路径被正确得到之前就不会显示找不到图片。同理,<a>
标签的href属性也需要换成ng-href,这样页面上就不会先出现一个地址错误的链接。
ng-switch
ng-switch
指令根据表达式显示或隐藏对应的部分,对应的子元素使用ng-switch-when
指令,如果匹配成功则显示,否则移除,并且可以使用ng-switch-default
指令设置默认选项,如果都没有匹配上,默认的选项就会显示。
一般ng-switch
指令用于比较复杂的显示/隐藏逻辑。使用方法如下:
<select ng-model="selected">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<div ng-switch="selected">
<span ng-switch-when="1">this is 1</span>
<span ng-switch-when="2">this is 2</span>
<span ng-switch-when="3">this is 3</span>
<span ng-switch-default>you have no choice</span>
</div>
常用的事件指令
ng-change
:发生改变
ng-copy
:拷贝完成
ng-click
:单击
ng-dblclick
:双击
ng-blur
:失去焦点
ng-focus
:获得焦点
ng-commit
:表单提交
自定义指令
自定义指令
的目的是封装一些常用并且公用
的东西,并且Angular仍然存在一些DOM操作的可能,这些DOM操作应该都集中在自定义指令中。
定义自定义指令和定义控制器方式一致,都在模块下定义就可以了,下面是一个最简单的自定义指令:
angular.module("myApp", [])
.directive("myBtn", [function(){
return {
template:'<button>MyButton</button>'
}
}]);
需要注意的是,自定义指令的名字推荐使用驼峰命名法
。
除了返回有template
属性,还有以下常用属性:
restrict
:指令类型,E:element A:attribute M:comment C: class
templateUrl
:指令模板地址
replace
:是否使用模板替换原标签
transculde
:是否使用ng-transculde来包含html中指令包含的原有的内容
scope
:隔离父Scope
link
:称为链接函数,可以在里面操作DOM,共三个参数scope,element,attrs
restrict
字母 | 使用方式 | 示例 |
---|---|---|
E | 元素 | <my-button></my-button> |
A | 属性 | <div my-button></div> |
C | 样式类 | <div class=my-button></div> |
M | 注释 | <!-- directive: my-button --> |
template和templateUrl
在一个自定义的指令中,template
和templateUrl
只能选一个。
template
为模板内容。即你要在指令所在的容器中插入的html代码。
templateUrl
为模板内容的地址,当你的template超过10行,建议单独写一个模板文件,不然不易于后期的维护和阅读。
replace
replace
:是否用模板替换当前元素。true
: 将指令标签替换成temple中定义的内容,页面上不会再有<my-directive>
标签;false :则append(追加)在当前元素上,即模板的内容包在<my-directive>
标签内部。默认false。
transculde
是否使用ng-transculde
来包含html中指令包含的原有的内容,接收一个参数true/false
scope
directive默认是可以共享父scope中定义的属性,例如在模版中直接使用父 scope 中的对象和属性,但是我们如果要创建一个需要重复使用的指令就不能依赖于父scope了,因为在不同的地方使用directive
对应的父scope不一样。所以我们需要一个隔离的scope
,我们可以这样定义我们的指令:
app.controller("myController", function ($scope) {
$scope.name = "hello Angular";
}).directive("isolatedDirective", function () {
return {
scope: {},
template: '{{name}}'
}
});
使用隔离 scope
的时候,无法从父 scope 中共享属性。因此下面示例无法输出父 scope 中定义的 name 属性值。但是Angular为我们提供了三种方法同隔离之外的地方交互:
- @ 可以当前dom元素上的属性值绑定到局部scope属性中,结果总是
字符串
,因为dom属性就是字符串 - = 用法和@一致,不过@是单向的绑定,=是双向的绑定
- & 能使
direvtive
可以在父scope中执行一个动作,可以是一个function。
@使用例子
定义指令如下:
angular.module("myApp", [])
.controller("btnController",["$scope", function($scope) {
$scope.name = "Angular";
}]).directive("myButton", [function() {
return {
scope: {
name: "@"
},
template: "Hello {{name}}"
};
}]);
html代码:
<my-button name="{{name}}"></my-button>
我们将父scope中的name
属性通过属性的方式 传入my-button
指令中,隔离scope通过@可以接受到这个参数,不过这是一个单向的绑定
,父scope的name属性发生改变,隔离scope也会相应发生变化,不过隔离scope发生变化,父scope不会相应变化。
= 使用例子
angular.module("myApp", [])
.controller("btnController",["$scope", function($scope) {
$scope.name = "Angular";
}]).directive("myButton", [function() {
return {
scope: {
name: "="
},
template: "Hello {{name}}"
};
}]);
html代码:
<my-button name="{{name}}"></my-button>
如果使用 =
无论是改变父 scope 还是隔离 scope 里的属性,父 scope 和隔离 scope 都会同时更新属性值,因为它们是双向绑定的关系。
& 使用例子
var app = angular.module("myApp", []);
app.controller("btnController", ["$scope", function($scope){
$scope.count = 0;
$scope.add = function() {
$scope.count ++;
};
}]);
app.directive("myButton", [function() {
return {
scope: {
add:"&"
},
template: "<button ng-click='add()'>Add</button>"
}
}]);
html代码:
<my-button ng-click="add()"></my-button>{{count}}
点击按钮就可以触发父scope中的add( )
方法
link
如果想要在指令中操作DOM,我们就需要使用link
参数,link参数要求申明一个函数,称之为链接函数
写法如下:
link: function(scope, element, attrs) {
// 在这里操作DOM
}
双向绑定
其实,说起来,AngularJS最大的魅力就体现在数据的双向绑定
上了,双向绑定意味着我们的view
和model
无论哪一边进行了数据更新,另一方都能同步的得到更新。
例子:
<body ng-app="myApp" ng-controller="appController">
<input type="text" ng-model="name">
<div>Hello <strong>{{name}}</strong></div>
<script src="../bower_components/angular/angular.js"></script>
<script>
angular.module("myApp", []).controller("appController", ["$scope", function($scope) {
$scope.name = "Lan";
}]);
</script>
</body>
效果如下:
当我们在输入框中输入数据的同时,也会对网页的内容进行修改。
我是用了{{}}
实现了最简单的数据绑定,不过这个绑定是单向的,仅仅实现了数据的展示,然后我再input
标签上加上了ng-model
指令,实现了数据的双向绑定。双向绑定最常见的应用场景就是表单的处理,使用了双向绑定后,用户填写完表单,我们不需要任何操作就能拿到用户填写的所有内容。
ng双向绑定的优点
双向绑定最大的优点就在于数据发生改变后,我们不需要手动的去更新view
上面的内容去更新视图,简化了我们的开发过程,让代码更有逻辑性
并且增强了可阅读性
。让开发人员可以更加专注于业务逻辑
而并非数据的处理和繁琐的DOM操作。
ng双向绑定的缺点
当我们绑定的数据层次较深、数据量较大的时候,数据的双向绑定会占据一定的性能开销,因为AngularJS数据双向绑定的性能问题和AngularJS实现双向绑定的机制有关系。
关于AngualrJS双向绑定原理的分析可以参考我的另一篇博客
AngularJS数据双向绑定背后的秘密