最近在研究跨瀏覽器跨平台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 官網首頁的示例逐條翻譯的,可以進行參考對照。如果你有興趣了解更多,官網上有詳細的文檔,視頻,和示例。詳細你一定能收獲到不少東西的。另外,由於個人能力有限,如果有錯誤,敬請包含,歡迎留言指出問題所在,我會及時進行修改的。