AngularJS 介绍
最近在研究跨浏览器跨平台app方案时找到了Angular JS,他是Google出品的JS框架。我粗略地看了一下它的介绍,突然发现,这不就是我要找的东西吗?这样的好东西可千万不能埋没了,得让它发扬光大,我就以这篇文章作为开始,围绕着angularjs的官方文档做一个系列的研究。
首先我们看到了它的一句广告语“HTML enhanced for web app”我对这句话的理解是“让HTML对你的web app作最给力的支撑”,看着是不是觉得很唬人?光靠HTML难道能通吃一切吗?它可没说大话哦,让我们继续看下去。
为什么要选择AngularJS?
HTML是一个很好的静态解释性语言,但是当你对其添加越来越多的动态响应后,HTML语言就会变得越来越不清晰,难以维护,AngularJS可以让你对现有的HTML词汇定义进行扩展(XHTML?HTML5?),这样一来你的app就会变得非常易读而又富有表现力,同时也能加快你的开发效率。
与其他JS框架的不同之处
其他的JS框架通过将HTML, CSS, Javascript或者前2者与JS结合的内容进行抽象或者通过命令方式来操作DOM来弥补HTML的不足。不过这二者都没有解决HTML不是为了动态视图而设计的这一根本命题。
扩展能力
AngularJS是一套非常适合组建你自己的应用的工具集。你可以随意对其功能进行扩展或者与其他库结合使用。其中每一个功能你都可以自定义或者将其替换以满足你的开发过程和需求。你可以通过阅读文档找到该问题的答案。
一个简单的示例
在name文本框内输入文字竟会同时显示在下方的Label区域内。我们来看一下实现该功能的代码:
index.html
<!doctype html> <html ng-app> <head> <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.min.js"></script> </head> <body> <div> <label>Name:</label> <input type="text" ng-model="yourName" placeholder="Enter a name here"> <hr> <h1>Hello {{yourName}}!</h1> </div> </body> </html>
这里我们先来做一些注释:
ng-app
: 告诉AngularJS在页面上激活的位置,在本例中意味着全文件有效。
angular.min.js
: 加载AngularJS
ng-model: 将form和model建立关系并绑定。这意味着你对该控件内的任何修改都会对model内的数据作实时更新,相对的,model内数据的变更也会改变控件的显示。
{{yourName}}
: “{{ }}”是一种在HTML页面上制定绑定显示位置的申明方式,AngularJS会在“yourname”值变更的同时自动替换该处的文本。
让我们动手试一试?点我玩一下
添加一些控制功能
先穿插一点背景知识和特性:
Data Binding: Data-binding是一种在model数据变更时自动更新显示的一种方式,反之亦然。这个功能十分方便,因为他可以让你省去许多对DOM的操作。
Controller: controller定义了DOM背后的行为。AngularJS可以让你用一种间接的高可读性,非公式化的表现方式来更新DOM,以及注册回调方法(callbacks),或者监听model的变化。
朴实的JS: 和其他JS框架不同,你无需去继承一个专门的类型,用专门的访问器包裹你的model。只需要传统的、朴素的Javascript就好了。这将是你的程序更容易测试,维护,重用,而且从公式化中得到解放。
一个经典的Todo List的实现,来看代码:
index.html
<!doctype html> <html ng-app> <head> <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.min.js"></script> <script src="todo.js"></script> <link rel="stylesheet" href="todo.css"> </head> <body> <h2>Todo</h2> <div ng-controller="TodoCtrl"> <span>{{remaining()}} of {{todos.length}} remaining</span> [ <a href="" ng-click="archive()">archive</a> ] <ul class="unstyled"> <li ng-repeat="todo in todos"> <input type="checkbox" ng-model="todo.done"> <span class="done-{{todo.done}}">{{todo.text}}</span> </li> </ul> <form ng-submit="addTodo()"> <input type="text" ng-model="todoText" size="30" placeholder="add new todo here"> <input class="btn-primary" type="submit" value="add"> </form> </div> </body> </html>
注释:
todo.js
: 该文件内存放了所有控制方法。
ng-controller
: 在这个元素内部的行为都将交由todo.js
内的TodoCtrl
类进行管理。
ng-click
: 用定义的方式制定调用Controller中的行为,而不是通过注册event-handlers。在本例中点击链接后将调用archive() 方法。
ng-repeat
: ng-repeat用来显示一个集合,在本例中,针对每一个在todos里的对象,AngularJS都会创建一个<li>
副本。当新的对象加入到todos中时,它也会自动追加<li>
项,反之亦然。这是反映AngularJS指令灵活度特性的其中之一。
ng-submit
: 拦截表单提交事件,并用“addTodo()”方法取代,在该方法内会读取“todoText”属性并且将其插入到todos数组内。
todo.js
function TodoCtrl($scope) { $scope.todos = [ {text:'learn angular', done:true}, {text:'build an angular app', done:false}]; $scope.addTodo = function() { $scope.todos.push({text:$scope.todoText, done:false}); $scope.todoText = ''; }; $scope.remaining = function() { var count = 0; angular.forEach($scope.todos, function(todo) { count += todo.done ? 0 : 1; }); return count; }; $scope.archive = function() { var oldTodos = $scope.todos; $scope.todos = []; angular.forEach(oldTodos, function(todo) { if (!todo.done) $scope.todos.push(todo); }); }; }
注释:
TodoCtrl
: Controller是页面背后的控制代码,你可以清楚地看到应用的行为,因为其中没有任何Dom操作,或者框架独有的格式,只有简单的,非常易读的JS。
$scope
: $scope内包含了你的model数据。它将页面和控制器粘合在了一起。$scope仅仅是能被注入到控制器的服务的其中之一。
todos
: 在本例中在初始化model时创建了2个todo项,请注意你只需简单地分配你的model到$scope上,AngularJS就会自动帮你将数据显示在页面上。而这个model数据就是普通的,传统的js对象,不用费力的把它包在proxy里,或者通过特定的setter方法去访问。
todoText
: 由于数据的双向绑定,model内的数据永远是最新的。这意味着我们可以简单地读取用户的输入,而不需要再去注册callbacks,事件监听器,或者使用框架提供的API。
todo.css
.done-true { text-decoration: line-through; color: grey; }
注释:
.done-true
: 为完成的项添加打勾的样式。
连接后端
背景知识和特性:
Deep Linking: 深度的链接反映了用户在app内的所在位置,当用户想要收藏当前页面或者将链接通过邮件发送出去时就显得十分有用。传统的web应用没有这个问题,但是AJAX应用天生就不支持这个功能。AngularJS将深度链接和类似桌面应用的开发的优点结合在了一起。
表单验证: 客户端验证是用户体验中的重要部分。AngularJS让你无需编写任何JS代码就能制定表单的验证条件。写更少的代码,开发地更快更好。
与服务端通信: AngularJS提供了建立于XHR之上的服务,这样戏剧化地简化了你的代码。我们封装了XHR并且提供了异常处理和成功允诺。而这个允诺进一步地简化了原先你代码内处理异步返回结果的部分。它能让同步地分配属性但是其实执行的是异步的操作。
SUID操作示例,来看代码:
index.html
<!doctype html> <html ng-app="project"> <head> <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.min.js"></script> <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular-resource.min.js"> </script> <script src="project.js"></script> <script src="mongolab.js"></script> </head> <body> <h2>JavaScript Projects</h2> <div ng-view></div> </body> </html>
注释:
ng-app="project"
: ng-app在本页面区域内激活了project模块。这样定义意味着可以在页面内存在调用多个模块。
angular-resource.min.js
: 加载AngularJS Resource模块。
project.js
: 页面的Controller,其中定了了页面的行为。
mongolab.js
: AngularJS将该应用与http://mongolab.com连接的模块,用来作数据持久化。ng-view: 定义了ng-view以后,我们就可以把这个div作为一个partial页面或者模板的容器,而在它范围之外页面部分将保持静态。在这个例子中我们将使用这个容器,让它在现实列表和添加、删除表单模板之间切换。
project.js
angular.module('project', ['mongolab']). config(function($routeProvider) { $routeProvider. when('/', {controller:ListCtrl, templateUrl:'list.html'}). when('/edit/:projectId', {controller:EditCtrl, templateUrl:'detail.html'}). when('/new', {controller:CreateCtrl, templateUrl:'detail.html'}). otherwise({redirectTo:'/'}); }); function ListCtrl($scope, Project) { $scope.projects = Project.query(); } function CreateCtrl($scope, $location, Project) { $scope.save = function() { Project.save($scope.project, function(project) { $location.path('/edit/' + project._id.$oid); }); } } function EditCtrl($scope, $location, $routeParams, Project) { var self = this; Project.get({id: $routeParams.projectId}, function(project) { self.original = project; $scope.project = new Project(self.original); }); $scope.isClean = function() { return angular.equals(self.original, $scope.project); } $scope.destroy = function() { self.original.destroy(function() { $location.path('/list'); }); }; $scope.save = function() { $scope.project.update(function() { $location.path('/'); }); }; }
注释:
project
: 这样就直接定义了project模块。通过使用模块,你可以配置现有的服务,定义新的服务,指令,过滤器等等。在这里,我们将设置映射URL和部件的路由。AngularJS负责监控浏览器,一旦URL有变动将自动更新部件内容。
mongolab
: 一个模块可以依赖于其他模块,这里,project依赖于mongolab以处理本应用的持久化事务。
config
: 通过config()
可以配置已存在的服务,这里,我们将配置$routeProvider
来处理URL和部件的映射关系。
when
: 当URL为/
时,程序会在页面上加载list.html
并关联ListCtrl
控制器。你可以直观的通过查阅路由定义来了解该应用的结构。
/edit/:projectId
: 你一定在路由定义里发现有一个:
,你可以通过使用冒号来传参,让Controller接受,现在EditCtrl
可以通过查询projectId
属性来定位要编辑的记录了。
otherwise
: 这条路由用来定义不满足所有情况时显示的内容,类似switch的default。
Project
: 这是一个用来持久化以及获取数据的类。他已经在mongolab.js
中定义,并自定注入到了controller中。他的目的是使服务器的交互抽象化,让我们腾出更多精力专注于控制行为而不是处理复杂的服务器交互。
query
: 该方法会从服务器请求Project类的集合。注意尽管这是一个异步的调用,但是我们就像同步且没有回调函数的那样使用,这实在太酷了!事实上这里query
返回的是一种叫做promise
的东东。一旦最后从服务器得到了返回数据,兑现了promise
, AngularJS的数据绑定功能将自动地在任何我们使用该方法的地方更新显示。
$location
: 你可以通过调用此服务读取浏览器地址。
save
: 当用户点击save按钮后将调用此方法。
path
: 使用此方法可以更改应用的当前URL,URL的变动也会自动地激活路由服务,解释并显示新的内容,在这里,会显示/edit/
内容。
$routeParams
: 这里我们请求AngularJS为我们注入$routeParams
服务,通过使用它,我们可以读取在路由里定义的参数。
projectId
: 这里将URL中的 projectId 读取出来。有了它可以让Controller支持deep-linking。
original
: 我们先把原始的Project保存起来,以便可以发现用户是否有对其进行更改。
isClean
: 检查用户是否通过表单更改了信息,我们在这里通过此功能控制是否启用save按钮。
destroy
: 当用户点击了delete按钮后调用该方法。
list.html
<input type="text" ng-model="search" class="search-query" placeholder="Search"> <table> <thead> <tr> <th>Project</th> <th>Description</th> <th><a href="#/new"><i class="icon-plus-sign"></i></a></th> </tr> </thead> <tbody> <tr ng-repeat="project in projects | filter:search | orderBy:'name'"> <td><a href="{{project.site}}" target="_blank">{{project.name}}</a></td> <td>{{project.description}}</td> <td> <a href="#/edit/{{project._id.$oid}}"><i class="icon-pencil"></i></a> </td> </tr> </tbody> </table>
注释:
ng-model
: 将输入框与search属性绑定。该属性用来过滤project包含用户输入的关键字的数据。
#/new
: 指向/new
路由的链接,该路由已在project.js
里定义。注意我们一直遵从web的准则。为一个链接注册回调实在很不象话,我们只是简单地将它指向一个新的URL。这将会自动更新浏览器的历史,激活deep-linking。但是和普通的服务器客户端两头跑的应用不同,这里的跳转时间将会立刻在浏览器上得到反馈。
ng-repeat
: 通过使用它来遍历集合。在这里,为projects
里的每一个project,AngularJS都会创建一个新的<tr>
节点。
filter
: 它通过调用search
来返回在projects
中符合条件的项。当你在搜索框里输入关键字后,filter
将会将列表的显示范围进一步缩小以满足条件,然后ng-repeat
会在table里添加或删除不符和条件的项目。
orderBy
: 返回根据name
排序的project列表。
#/edit/{{project._id.$oid}}
: 创建独有的编辑链接,通过在URL里内嵌project id,这样一来就可以实现deep-linking,你可以通过浏览器返回上一步,也可以通过URL调用EditCtrl
来编辑项。
detail.html
<form name="myForm"> <div class="control-group" ng-class="{error: myForm.name.$invalid}"> <label>Name</label> <input type="text" name="name" ng-model="project.name" required> <span ng-show="myForm.name.$error.required" class="help-inline"> Required</span> </div> <div class="control-group" ng-class="{error: myForm.site.$invalid}"> <label>Website</label> <input type="url" name="site" ng-model="project.site" required> <span ng-show="myForm.site.$error.required" class="help-inline"> Required</span> <span ng-show="myForm.site.$error.url" class="help-inline"> Not a URL</span> </div> <label>Description</label> <textarea name="description" ng-model="project.description"></textarea> <br> <a href="#/" class="btn">Cancel</a> <button ng-click="save()" ng-disabled="isClean() || myForm.$invalid" class="btn btn-primary">Save</button> <button ng-click="destroy()" ng-show="project._id" class="btn btn-danger">Delete</button> </form>
注释:
myForm
: 创建一个表单并为其命名,我们将会声明验证规则并处理输入错误和控制按钮操作。
ng-class
: 当name
不正确时,为div添加一个error
的CSS class。
required
: 当输入值为空值判定为错误。
ng-show
: 当myForm的name框required判定为错误时显示错误信息。
url
: 该类型会自动验证输入值的格式。
ng-disabled
: 当表单没有变动或者有错误时禁用'save'按钮
mangolab.js
// This is a module for cloud persistance in mongolab - https://mongolab.com angular.module('mongolab', ['ngResource']). factory('Project', function($resource) { var Project = $resource('https://api.mongolab.com/api/1/databases' + '/angularjs/collections/projects/:id', { apiKey: '4f847ad3e4b08a2eed5f3b54' }, { update: { method: 'PUT' } } ); Project.prototype.update = function(cb) { return Project.update({id: this._id.$oid}, angular.extend({}, this, {_id:undefined}), cb); }; Project.prototype.destroy = function(cb) { return Project.remove({id: this._id.$oid}, cb); }; return Project; });
注释:
ngResource
: AngularJS的ngResource
模块提供了一个面向RESTFul服务的通用接口。
factory
: 通过调用ngResource
模块的该方法来定义新的服务。在这里定义的任何服务都会在你调用的任何地方被自动注入。
Project
: 在这里是为了Project类定义一个服务,该类负责每个project
的数据装载,并且带有读写数据的方法。
$resource
: 该服务专门用来创建资源类。每一个资源类预定义配有query()
,get()
,save()
和remove()
方法。这些方法作为与持久化服务交互的API。此外,资源类还可以根据你的应用的需要进行扩展。
apiKey
: 向mangolab
数据存储引擎请求时必要的参数,伴随所有请求一并传入。
update
: 在资源类上定义update方法,其将会使用HTTP put方法作为请求。
prototype
: 这里我们将扩展资源类的方法来与持久化引擎作交互。
创建组件
背景知识和特性:
指令 (Directives): 指令是AngularJS独有的功能,而且他非常强大,Directive能让你在你的应用中创造独有的HTML语句。
组件可重用: 使用指令的目的的其中之一是为了重用。可以将一些复杂的DOM结构,css语句,行为等封装成一个组件,从而让你腾出更多精力专注于应用的表现方式上。
本地化: 本地化是一个严谨的应用其中的一个重要组成部分。Angular中的local aware filters和stemming directives提供了现成的功能模块从而使你的应用能解决大多数本地化的问题。
来看代码:
index.html
<!doctype html> <html ng-app="components"> <head> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.6/angular.min.js"></script> <script src="components.js"></script> <script src="beers.js"></script> </head> <body> <tabs> <pane title="Localization"> Date: {{ '2012-04-01' | date:'fullDate' }} <br> Currency: {{ 123456 | currency }} <br> Number: {{ 98765.4321 | number }} <br> </pane> <pane title="Pluralization"> <div ng-controller="BeerCounter"> <div ng-repeat="beerCount in beers"> <ng-pluralize count="beerCount" when="beerForms"></ng-pluralize> </div> </div> </pane> </tabs> </body> </html>
注释:
components
: 直接在此页面范围内定义了components组件,该组件包含了<tabs>
和<pane>
这两个HTML扩展组件。
tabs
: 我们为HTML解释库添加了一个tabs
组件,该组件将复杂的HTML标签的实现和结构抽象话,直接展现结果,这样增加了代码的可读性而且能够很方便地去重用。
pane
: 我们在这里又定义了一个名为pane
的新组件,为每个标签加载面板。
title
: 自定义组件可以带参,在这里title
表示为标签的显示抬头。
localization
: 作为一个示例演示AngularJS的本地化功能,日期、数字、和汇率的格式显示。
Pluralization
: 作为一个示例演示AngularJS的单复数显示功能,注意此功能会根据locale的不同产生变化。
BeerCounter
: 我们使用名为BeerCounter
的Controller来设置基于不同locale的显示规则。
ng-pluralize
: 该指令会根据每个local显示正确的单复数格式。不是所有的语言都想英语那样,根据数量的不同,其他语言可能会有更复杂的复数表现形式。
count
: 绑定显示的阿拉伯数字。
when
: 绑定单复数的输出规则。
components.js
angular.module('components', []). directive('tabs', function() { return { restrict: 'E', transclude: true, scope: {}, controller: function($scope, $element) { var panes = $scope.panes = []; $scope.select = function(pane) { angular.forEach(panes, function(pane) { pane.selected = false; }); pane.selected = true; } this.addPane = function(pane) { if (panes.length == 0) $scope.select(pane); panes.push(pane); } }, template: '<div class="tabbable">' + '<ul class="nav nav-tabs">' + '<li ng-repeat="pane in panes" ng-class="{active:pane.selected}">'+ '<a href="" ng-click="select(pane)">{{pane.title}}</a>' + '</li>' + '</ul>' + '<div class="tab-content" ng-transclude></div>' + '</div>', replace: true }; }). directive('pane', function() { return { require: '^tabs', restrict: 'E', transclude: true, scope: { title: '@' }, link: function(scope, element, attrs, tabsCtrl) { tabsCtrl.addPane(scope); }, template: '<div class="tab-pane" ng-class="{active: selected}" ng-transclude>' + '</div>', replace: true }; })
注释:
directive
: 通过调用该方法来定义新的HTML语义扩展。
tabs
: 定义了tabs
扩展。
restrict
: 他规定了HTML组件的格式,在这里<tabs>
必须是一个HTML元素。
transclude
: 定义了AngularJS进行自定义组件的转义后,原先定义的内容显示的位置,要实现这点还要结合ng-transclude
指令(见下文)。
scope
: 我们定义的组件需要有一个私有的边界以保证其内部的显示内容不被外界不小心更改。如果你确实需要这么做,你可以定义input/output属性,可参考下文中的<pane>
组件用法。
controller
: 就像整体应用一样,组件同样也可以指定一个controller,来负责解释该组件的行为。
$scope
: 当前组件的边界。
$element
: 当前组件的DOM元素。
select
: 发布一个名为select
的方法来负责转换标签时的显示。
addPane
: 通常组件需要相互协作来达到一个整体效果,在这个例子里,pane
组件会通过addPane
方法来讲自己注册到<tabs>
容器中。
template
: 顾名思义,他就是用来存放替换自定义组件的HTML代码存放点。注意,里面也同样可以包含其他的指令。
active
: 我们通过设置active
这么一个CSS样式来实现激活标签的效果。
ng-click
: 通过点击选中标签。
ng-transclude
: 标记原有<tabs>
元素存放的位置。
replace
: 告诉AngularJS替换原有的<tabs>
元素,而不是在它后面追加template
里的内容。
require
: 指定了<pane>
组件必须包含在<tabs>
组件内。这样也同样可以让<pane>
组件访问<tabs>
组件的controller的方法,在这里,就是addPane
这个方法了。
tabsCtrl
: 我们已经通过定义require
在指定<tabs>
作为容器,我们就可以只用它的controller示例了。
beers.js
function BeerCounter($scope, $locale) { $scope.beers = [0, 1, 2, 3, 4, 5, 6]; if ($locale.id == 'en-us') { $scope.beerForms = { 0: 'no beers', one: '{} beer', other: '{} beers' }; } else { $scope.beerForms = { 0: 'žiadne pivo', one: '{} pivo', few: '{} pivá', other: '{} pív' }; } }
注释:
$locale
: 该服务包含了当前locale的元数据。AngularJS有很多locale模块对应各种不同语言的locale。
beers
: 设置beers的计数数组。我们将迭代这个数组来得到每项的值。
id
: 为每个不同的locale建立不同的复数形式。在实际项目中,除了要加载locale以外,还要处理翻译的问题。
beerForms
: 基于英语的复数形式。
结尾
好了终于把欠的债补完了,本文是根据AngualarJS 官网首页的示例逐条翻译的,可以进行参考对照。如果你有兴趣了解更多,官网上有详细的文档,视频,和示例。详细你一定能收获到不少东西的。另外,由于个人能力有限,如果有错误,敬请包含,欢迎留言指出问题所在,我会及时进行修改的。