前端技術的發展是如此之快,各類優秀技術、優秀框架的出現簡直讓人應接不暇,做爲一名業界新秀,緊跟時代潮流,學習掌握新知識天然是不敢怠慢。當聽到AngularJs這個名字並知道是google在維護它時,便一直在關注,看到其在國外已經十分火熱,但是國內的使用狀況卻有不小的差距,參考文獻/網絡文章也很匱乏。牽腸掛肚良久,決定深刻學習angular,並寫系列博客,一方面做爲本身學習路程上的記錄,另外一方面也給有興趣的同窗一些參考。javascript
首先我本身是一名學習者,會以學習者的角度來整理個人行文思路,故該系列博客也不能叫作教程,只能是些探索,有理解或是技術上的錯誤還請你們指出。其次我特別喜歡編寫小例子來把一件事情說明白,故在文中會盡量多的用示例加代碼講解,我相信這會是一鍾比較好的方式。最後,我深知在現有條件下對於angular的學習會困難重重,不過我更相信堅持的力量,因此謹以此文做爲往後學習的動力,讓咱們一塊兒來走進angular的世界吧~css
這個定義必定要定準了,AngularJs(後面就簡稱ng了)是一個用於設計動態web應用的結構框架。首先,它是一個框架,不是類庫,是像backbone同樣提供一整套方案用於設計web應用。它不只僅是一個javascript框架,由於它的核心實際上是對HTML標籤的加強,有圖有真相,請看官網描述:html
何爲HTML標籤加強?其實就是使你可以用標籤完成一部分頁面邏輯,具體方式就是經過自定義標籤、自定義屬性等,這些HTML原生沒有的標籤/屬性在ng中有一個名字:指令(directive)。後面會詳細介紹。那麼,什麼又是動態web應用呢?與傳統web系統相區別,web應用能爲用戶提供豐富的操做,可以隨用戶操做不斷更新視圖而不進行url跳轉。ng官方也聲明它更適用於開發CRUD應用,即數據操做比較多的應用,而非是遊戲或圖像處理類應用。前端
爲了實現這些,ng引入了一些很是棒的特性,包括模板機制、數據綁定、模塊、指令、依賴注入、路由。經過數據與模板的綁定,可以讓咱們擺脫繁瑣的DOM操做,而將注意力集中在業務邏輯上。這些我將在之後的學習中一一研究。java
另一個疑問,ng是MVC框架嗎?仍是MVVM框架?官網有提到ng的設計採用了MVC的基本思想,而又不徹底是MVC,由於在書寫代碼時咱們確實是在用ng-controller這個指令(起碼從名字上看,是MVC吧),但這個controller處理的業務基本上都是與view進行交互,這麼看來又很接近MVVM。讓咱們把目光移到官網那個非醒目的title上:「AngularJS — Superheroic JavaScript MVW Framework」。angularjs
好吧,MVW。W—whatever。隨即是MV什麼好了,因此也有人寫爲了MV*。其實糾結這個也真不必,等從此對整個框架熟悉了,其中結構天然瞭然於心。web
有了一個大概的朦朧的瞭解就夠了,我相信不少概念在使用的過程當中會慢慢清晰。我火燒眉毛的想要讓angular運行起來了。動手~bootstrap
首先從官網http://angularjs.org/下載angular.js,引入你的頁面中,而後咱們使用最簡單的手工啓動方式,直接調用bootstrap方法。全部的代碼以下:數組
<!DOCTYPE html> <html > <head> <meta charset="utf-8" /> <title>運行ng</title> <script type="text/javascript" src="../angular.js"></script> </head> <body> <div> 1+1={{1+1}} </div> <script> angular.bootstrap(document,[]); </script> </body> </html>
只有一行代碼。調用bootstrap方法傳入做用域和初始化的模塊數組(此處爲空)。是否是很簡單。你很快會看到一處比較特別的,就是這裏:瀏覽器
<div> 1+1={{1+1}} </div>
若是你使用過其餘模板庫,應該對這種寫法不陌生了,{{}}雙大括號,這是ng的模板中用於書寫表達式的標記,ng成功運行起來後,{{}}內的表達式會生效,即頁面會顯示以下:
爲了避免讓你把ng看的這麼簡單,我必須告訴你通常是不這麼啓動的,來看稍微修改之後的代碼:
<!DOCTYPE html> <html ng-app="MyApp"> <head> <meta charset="utf-8" /> <title>運行ng</title> <script type="text/javascript" src="../angular.js"></script> </head> <body> <div> 1+1={{1+1}} </div> <script type="text/javascript" charset="utf-8"> var app = angular.module('MyApp', [], function(){console.log('started')}); </script> </body> </html>
在<html>標籤上多了一個屬性ng-app=」MyApp」,它的做用就是用來指定ng的做用域是在<html>標籤之內部分。在js中,咱們調用angular對象的module方法來聲明一個模塊,模塊的名字和ng-app的值對應。關於如何聲明、使用模塊咱們在後面會講。如今咱們只要明白用這種方式能夠優雅的讓ng運行起來就能夠了。
首先須要明確一下模板的概念。在我還不知道有模板這個東西的時候,曾經用js拼接出很長的HTML字符串,而後append到頁面中,這種方式想一想真是又土又笨。後來又看到能夠把HTML代碼包裹在一個<script>標籤中看成模板,而後按須要取來使用。在ng中,模板十分簡單,它就是咱們頁面上的HTML代碼,不須要附加任何額外的東西。在模板中可使用各類指令來加強它的功能,這些指令可讓你把模板和數據巧妙的綁定起來。
綁定這個東西但是ng中的大功臣了。在咱們使用jQuery的時候,代碼中會大量充斥相似這樣的語句:var v = $(‘#id’).val();$(‘#id’).html(str);即頻繁的DOM操做(讀取和寫入),其實咱們的最終目的並非要操做DOM,而是要實現業務邏輯。ng的綁定將讓你擺脫DOM操做,只要模板與數據經過聲明進行了綁定,二者將隨時保持同步,最新的數據會實時顯示在頁面中,頁面中用戶修改的數據也會實時被記錄在數據模型中。
我構思了一個小例子,本篇文章的示例將圍繞這個小例子來進行。我猜你已經厭倦了登陸模塊或者是購物車示例,咱們來點新穎的。我已化身爲一名教師,我要用一個web應用來爲學生出一份在線試題。首先從一道題開始吧~
<!DOCTYPE html> <html ng-app="MyApp"> <head> <meta charset="utf-8" /> <title>模板數據綁定</title> <script type="text/javascript" src="../angular.js"></script> </head> <body> <div ng-controller="testC"> <h1>{{newtitle}}</h1> 題目:<input type="text" ng-model="name" /><br /> 分數:<input type="text" ng-model="fraction" /><br /> <hr> <h1>{{previewtitle}}</h1> <b>{{name}}</b>({{fraction}}分) </div> <script type="text/javascript" charset="utf-8"> var app = angular.module('MyApp', [], function(){console.log('started')}); var testC = function($scope){ $scope.newtitle = '新建試題'; $scope.previewtitle = '預覽試題'; $scope.name = $scope.fraction = ''; } </script> </body> </html>
頁面上有分別表示題目和分數的兩個輸入框,下面有一個實時預覽區域,用來展現題目的最終輸出效果。ng-controller=」textC」用來聲明一個須要和數據進行綁定的模板區域,它的做用域就是這個div以內的東西,並起名爲textC,js代碼中定義了一個名爲textC的函數與它對應,咱們將在這個函數內完成綁定。函數傳入一個參數$scope,表示這個做用範圍。咱們分別爲做用範圍內的newtitle、previewtitle、name、fraction四個變量賦值。此時<h1>標籤內的{{}}便能獲得相應的值了。
經過{{}}只能完成數據向模板的單向綁定。要想進行雙向綁定,咱們須要用到ng-modle這個指令,咱們使用它分別爲題目和分數進行了雙向綁定,這樣當輸入框內的值發生變化時,函數中的變量也會跟隨變化,它的變化會實時反饋在下方的預覽區域中,由於預覽區域中也有一個name和fraction的綁定。
試試在下面的輸入框中進行編輯吧~
咱們並未進行任何DOM操做,框架自動完成了DOM的取值和賦值。在後面我將繼續完善這個例子,並引入更多的新概念。
咱們在使用其餘模板庫時,通常都會有模板的循環輸出、分支輸出、邏輯判斷等相似的控制。ng模板中均可以進行哪些控制呢?來一塊探索之。
繼續上面的例子。試題光有題目和分數還不夠,我想要出一道選擇題,能夠本身添加若干選項並編輯選項內容,代碼該怎麼寫呢?先上代碼:
HTML部分:
<div ng-controller="testC"> <h1>{{question.newtitle}}</h1> 題目:<input type="text" ng-model="question.name" /><br /> 分數:<input type="text" number ng-model="question.fraction" /><br /> 選項:<button ng-click="addOption()">增長選項</button><br /> <ul> <li ng-repeat="o in question.options"> <b>{{$index+1}}.</b> <input type="text" ng-model="o.content" value="o.content" /> <a href="javascript:void(0);" ng-click="delOption($index)">刪除</a> </li> </ul> <hr> <div > <h1>{{question.previewtitle}}</h1> <b>{{question.name}}</b>({{question.fraction}}分) <ul> <li ng-repeat="o in question.options"> <b>{{$index+1}}.</b> <input type="radio" name="optcheck" /> {{o.content}} </li> </ul> </div> </div>
js部分:
var app = angular.module('MyApp', [], function(){console.log('started')}); var questionModel = { newtitle : '新建試題', previewtitle : '預覽試題', name : '', fraction : '', options : [] }; app.controller('testC',function($scope){ $scope.question = questionModel; $scope.addOption = function(){ var o = {content:''}; $scope.question.options.push(o); }; $scope.delOption = function(index){ $scope.question.options.splice(index,1); }; });
請注意個人js代碼有了一點變化,在數據綁定時沒有直接寫成字符串,而是將全部的數據寫成了一個questionModel對象,這只是一個廣泛的js對象,這樣可以使咱們的數據看起來像是「model」的樣子,畢竟咱們是MV*嘛。那麼在模板中,咱們使用name的地方也改爲question.name,其餘的值也同理。另外在controller部分,我沒有用var textC = function(){}這樣的寫法,而是改爲了app.controller(‘textC’,function(){}),這樣明確指定了這個controller屬於MyApp這個模塊,咱們的MV*代碼看起來更專業、更一體了。
在HTML部分我分別在新建區域和預覽區域添加了一個列表,用來放置各選項。並添加了一個「增長選項」按鈕。在<li>標籤上使用了ng-repeat指令來進行循環輸出,<li>標籤將會根據$scopt中的options數組長度被複制多份。在循環範圍內,可使用$index得到當前循環的索引,能夠認爲這是一個公共變量,直接使用。同時,循環輸出的每一個<input>元素也使用ng-model與選項的內容進行了雙向綁定。另外還輸出一個刪除連接,點擊的時候調用$scope中的delOption方法。
這樣綁定好以後,模版中的列表與數據模型中的options數組便保持了同步,咱們在頁面上點擊增長選項,數組中便會增長一個元素,數組中的每一個元素也會實時反饋在模板中。因此在js代碼中,咱們只須操做options數組就夠了,壓根不須要在頁面上append或removed節點。
你能夠在下面點擊增長選項和刪除試試~
在上面的例子中,你是否是發現了,我在處理按鈕的點擊時,使用了叫作ng-click的指令,爲何不直接用onclick呢?是由於ng根據本身的須要進行了封裝。咱們把addOption這個函數定義在了controller範圍以內,用咱們常規的onclick已經沒法訪問到。換言之,咱們頁面上的做用域,ng已經幫咱們都規劃好了,咱們只需按照它提供的方式來使用就夠了。
經過onclick咱們能夠聯想到,HTML節點還有好多其餘屬性,如class、style、href、checked、disabled等等,ng對這些都一一進行了封裝,更厲害的是,除此以外ng還額外提供了許多更加詳細的控制節點的指令。這些指令我之後會詳細研究,在這裏,咱們先拿個其中一個應用到咱們的例子中,看看效果先。
我立刻變回老師身份,譁~
我新建試題的時候,不要侷限於單選題,我想要在單選題和多選題之間可以切換,同時下方的預覽區域內,選擇框也進行單選框和多選框的切換。上代碼:
HTML部分:
<div ng-controller="testC"> <h1>{{question.newtitle}}</h1> 題目:<input type="text" ng-model="question.name" /><br /> 分數:<input type="text" ng-model="question.fraction" /><br /> 類型:<select ng-model="question.type"><option value="1" selected>單選</option><option value="2">多選</option></select><br /> 選項:<button ng-click="addOption()">增長選項</button><br /> <ul> <li ng-repeat="o in question.options"> <b>{{$index+1}}.</b> <input type="text" ng-model="o.content" value="o.content" /> <a href="javascript:void(0);" ng-click="delOption($index)">刪除</a> </li> </ul> <hr> <div preview-panel> <h1>{{question.previewtitle}}</h1> <b>{{question.name}}</b>({{question.fraction}}分) <ul> <li ng-repeat="o in question.options"> <b>{{$index+1}}.</b> <input type="radio" name="optcheck" ng-show="question.type==1" /> <input type="checkbox" ng-show="question.type==2" /> {{o.content}} </li> </ul> </div> </div>
Js代碼中,我只是在questionModel中新增了一項type:1,表示題的類型,1爲單選,2爲多選。並給默認值爲1.
var questionModel = { newtitle : '新建試題', previewtitle : '預覽試題', name : '', fraction : '', type : '1', options : [] };
在HTML中,我新增了一個下拉框,並與question.type創建雙向綁定。須要關注的是下面的預覽區域。我又添加了一個checkbox控件來爲多選題提供選擇框。顯然,radio與checkbox不能同時存在,因此我用ng-show這個指令來控制它們的顯隱,ng-show接收boolean類型的值以及計算結果爲boolean類型的表達式。請注意,ng-show以及其餘全部指令的值不是簡單的字符串(儘管看上去是那樣),而是字符串表達式,擁有計算能力,我如今的理解是它未來會在框架的某個地方放進eval()或是相似的函數執行。{{}}裏的內容也是同樣。
因此在這裏我給radio的ng-show賦值爲question.type==1,checkbox的ng-show賦值爲question.type==2。當試題是單選題時,radio的ng-show便能獲得值true,從而顯示出來。在下面看一下效果:
其餘的節點控制指令也能夠相似這樣使用,思想就是根據你的業務邏輯,賦予它們必定的表達式。其餘的控制還有事件綁定、表單控件等等,篇幅的限制在這裏就不講了,之後專門開一篇介紹。
所謂過濾器是指對輸出的內容進行格式化,如格式化爲美圓、日期等。框架本身提供一些過濾器,如排序、字符串內容篩選。咱們也能夠自定義過濾器。
過濾器在{{}}中使用,表達式後用|隔開使用。拿日期過濾器舉例,方式以下:
$scope.nowTime = new Date().valueOf(); {{nowTime | date : 'yyyy-MM-dd HH:mm:ss'}}
便會輸出格式化的日期。是否是很方便呢。
接下來實戰一下,譁~
接上一步的例子,我想要在預覽區域中的題目前面顯示題型,像[單選題]這樣。咱們的questionModel中,type的值是1和2,因此咱們要作的就是經過過濾器,把1和2顯示爲單選題和多選題。Go~
在js中定義一個名爲typeFilter的過濾器:
app.filter('typeFilter',function(){ var f = function(input){ return input == '1' ? '單選題' : '多選題'; } return f; });
filter函數如何使用以及執行細節不是本篇的討論內容,因此如今只要明白這樣能夠定義一個過濾器就能夠了。結構姑且認爲是固定寫法,代碼不難看懂。
定義後這個filter後咱們即可以在模板中使用了:
<b>[{{question.type | typeFilter}}]{{question.name}}</b>({{question.fraction}}分)
在題目的前面顯示題型,並使用typeFilter,效果以下:
單調的「1」已經華麗轉身變爲了「單選題」。這只是一個簡單的過濾器。你能夠發揮想象力編寫更強大的過濾器。
前面已經提到不少次指令了,如今來正式介紹一下它。指令是ng爲HTML補充的語法擴展,用於加強HTML的表現力。像咱們以前使用的ng-controller、ng-model等都屬於指令。你也能夠自定義指令。ng內部包含了一個強大的DOM解析引擎,因此這些新的標籤或是標籤屬性能夠像使用原生HTML那樣很好的工做。
聽起來很牛的樣子,那咱們來試試本身定義一個指令吧。注意,我要變形了~
我是教師,在新建試題輸入分數的時候應該只能輸入數字纔對,輸入其餘內容是不合法的,並且我但願這個分數是1~10之間的數字。可否只在輸入框上加一個屬性就完成這個驗證呢?就像使用HTML5新增的required同樣。
咱們定義一個叫作fractionNum的指令以下:
app.directive('fractionNum',function(){ return { link : function(scope, elements, attrs, controller){ elements[0].onkeyup = function(){ if(isNaN(this.value) || this.value<1 || this.value>10){ this.style.borderColor = 'red'; } else{ this.style.borderColor = ''; } }; } }; });
哇,代碼好多層級呀,不要慌張,穩住陣腳!其實最後就是返回了帶有link字段的對象,link的值是一個函數,用來定義指令的行爲。從傳入的參數中能夠獲取到當前元素,咱們即可以拿當前元素開刀了。我在此處監聽當前元素的keyup事件,獲取元素的值,若是不是1~10之間的數字,則把輸入框的邊框顏色變爲紅色。這下這個指令就能夠工做了。
至於傳入的四個參數到底都有什麼玄機,我暫時還未研究,也不是本篇的重點,本篇只是作一個概覽,先把ng拉出來溜溜的意思。如今姑且能夠認爲,一個指令的固定寫法大概就是這個結構。
定義好的指令就能夠在模板中使用了,使用方法以下:
分數:<input type="text" ng-model="question.fraction" fraction-num /><br />
把它加在了分數輸入框上,此處要特別當心一個寫法,我定義的時候名字是fractionNum,用在模板中須要寫成fraction-num,就是由於名字中含有大寫字母的緣由,感受上跟使用css屬性名稱有點像。若是定義的時候沒有大寫字母,就沒必要擔憂這一點了。
看效果,如今你能夠去下面蹂躪那個分數輸入框去了~
經過依賴注入,ng想要推崇一種聲明式的開發方式,即當咱們須要使用某一模塊或服務時,不須要關心此模塊內部如何實現,只需聲明一下就可使用了。在多處使用只需進行屢次聲明,大大提升可複用性。
好比咱們的controller,在定義的時候用到一個$scope參數。
app.controller('testC',function($scope){});
若是咱們在此處還需操做其餘的東西,好比與瀏覽器地址欄進行交互。咱們只需再多添一個參數$location進去:
app.controller('testC',function($scope,$location){});
這樣即可以經過$location來與地址欄進行交互了,咱們僅僅是聲明瞭一下,所需的其餘代碼,框架已經幫咱們注入了。咱們很明顯的感受到了這個函數已經不是常規意義上的javascript函數了,在常規的函數中,把形參換一個名字照樣能夠運行,但在此處如果把$scope換成別的名字,程序便不能運行了。由於這是已經定義好的服務名稱。
這即是依賴注入機制。瓜熟蒂落的推斷,咱們能夠本身定義模塊和服務,而後在須要的地方進行聲明,由框架來替咱們注入。
對,個人小例子呢?我如今要想點需求來把依賴注入試驗一下。我以爲試題全是文字太單調了,我但願題目中能含有圖片/音視頻,或者選項中能夠含有圖片/音視頻。而且,我要更靈活一點,我要爲試題提供若干套模板來選擇,選擇不一樣的模板能夠新建不一樣樣式的題,好比模板一爲純文字試題、模板二爲題目中帶圖片/音視頻的試題、模板三爲選項中帶圖片/音視頻的試題,等等。注意此處所說的模板跟ng的模板不是一個概念,是我本身試題的模板,不要混淆。這些模板應該是與試題相分離的,之後能夠爲其餘題型例如填空題啊簡答題啊一樣使用。因此試題模板這個東西就須要作成服務了。
不知道我表達清楚了沒有,確實有點繞。來看下咱們如何定義一個服務:
app.factory('tpls',function(){ return ['tpl1','tpl2','tpl3','tpl4']; });
看上去至關簡單,是由於我在這裏僅僅是直接返回一個數組。在實際應用中,這裏應該是須要向服務器發起一個請求,來獲取到這些模板們。服務的定義方式有好幾種,包括使用provider方法、使用factory方法,使用service方法。它們之間的區別暫且不關心。咱們如今只要能建立一個服務出來就能夠了。我使用了factory方法。一個須要注意的地方是,框架提供的服務名字都是由$開頭的,因此咱們本身定義的最好不要用$開頭,防止發生命名衝突。
定義好一個服務後,咱們就能夠在控制器中聲明使用了,以下:
app.controller('testC',function($scope,tpls){ $scope.question = questionModel; $scope.nowTime = new Date().valueOf(); $scope.templates = tpls; //賦值到$scope中 $scope.addOption = function(){ var o = {content:''}; $scope.question.options.push(o); }; $scope.delOption = function(index){ $scope.question.options.splice(index,1); }; });
此時,若在模板中書寫以下代碼,咱們即可以獲取到服務tpls所提供的數據了:
模板:<a href="javascript:void(0);" ng-repeat="t in templates">{{t}} </a><br />
隨着知識點的一點點增長,個人這個小例子也愈來愈豐滿了,來看看完整版吧:
查看完整代碼請移步到runjs:http://runjs.cn/code/95wlwsfh
乎~能夠鬆一口氣了。文章寫到這裏終於接近尾聲了,不知我上面的陳述可否被你們理解。ng所包含的內容仍是挺多的,以上的每一個概念均可以再拆出幾篇文章來說解。因此我在這裏只能是每個都點到爲止,不揪細節。這篇文章的思想也就是「先了解概念,例子能跑起來就好了」。在之後的文章中會隨着我學習的深刻進行探討。
轉載