AngularJS 是一個 Web 應用框架,它實現了前端的 MVC 架構,能讓開發人員很方便地實現業務邏輯。javascript
舉個栗子,要作到下面的效果,之前可能須要寫一連串的 JavaScript 代碼綁定 N 多事件。而使用 AngularJS 框架,一句 JavaScript 都不用寫就能實現了,神奇吧?查看演示。php
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script> <div data-ng-app> 單價: <input type="number" min=0 ng-model="price" ng-init="price = 299"> <br> 數量: <input type="number" min=0 ng-model="quantity" ng-init="quantity = 1"> <br> 總價: {{ quantity * price }} </div>
這得益於 AngularJS 中的雙向數據綁定特性(Two Way Data-Binding),將 Model 和 View 自動關聯了起來,在更復雜的業務場景下,還有代碼分離的好處,將 DOM 操做和應用邏輯解耦,很是實用。css
不過沒有銀彈,和其餘框架同樣,AngularJS 也有它的侷限。CRUD 類型的操做是它所擅長的,想一想看之前寫過的管理後臺,幾乎大部分都是從數據庫中讀取數據,而後呈如今頁面上,進行各類增刪改查。AngularJS 約定了一套規範(約定優於配置),因而你能夠很便捷地操做數據。而在其餘方面,例如開發複雜的 Web 遊戲,AngularJS 則是無用武之地了。html
上面的例子已經說明了,咱們能夠像 PHP Smarty 模板同樣在 HTML 中寫表達式,用 {{ 和 }} 包起來。在 AngularJS 裏,View 和 Model 是在 Controller 裏面綁定的,因此不管你在 View 的表單中修改了內容,仍是在 Controller 裏經過代碼修改了 Model 值,兩邊都會即時發生變化,同步更新。由於 AngularJS 會監控 (watch) Model 對象的變化,隨時反映到 View 中。前端
Filter 相似 Unix 裏面的 | 管道概念,AngularJS 把它搬到了前端。仍是舉個例子,大家感覺一下——java
<div>{{ 9999 | number }}</div> <div>{{ 9999+1 | number:2 }}</div> <div>{{ 9*9 | currency }}</div> <div>{{ 'Hello World' | uppercase }}</div>
輸出結果:jquery
9,999 10,000.00 $81.00 HELLO WORLD
因爲過去寫 JavaScript 的習慣使然,人們很容易掉進一些 AngularJS 的陷阱裏。下面的內容假設你已經瞭解前端 MVC 概念,並對 AngularJS 有了必定經驗,初學者讀起來可能比較艱深晦澀。git
避免使用 jQuery 來操做 DOM,包括增長元素節點,移除元素節點,獲取元素內容,隱藏或顯示元素。你應該使用 directives 來實現這些動做,有必要的話你還要編寫本身的 directives。angularjs
若是你感到很難改變習慣,那麼考慮從你的網頁中移除 jQuery 吧。真的,AngularJS 中的 $http
服務很是強大,基本能夠替代 jQuery 的 ajax 函數,並且 AngularJS 內嵌了 jQLite —— 它內部實現的一個 jQuery 子集,包含了經常使用的 jQuery DOM 操做方法,事件綁定等等。但這並非說用了AngularJS 就不能用 jQuery 了。若是你的網頁有載入 jQuery 那麼 AngularJS 會優先採用你的 jQuery,不然它會 fall back 到 jQLite。github
須要本身編寫 directives 的狀況一般是當你使用了第三方的 jQuery 插件。由於插件在 AngularJS 以外對錶單值進行更改,並不能即時反應到 Model 中。例如咱們用得比較多的 jQueryUI datepicker 插件,當你選中一個日期後,插件會將日期字符串填到 input 輸入框中。View 改變了,卻並無更新 Model,由於$('.datepicker').datepicker();
這段代碼不屬於 AngularJS 的管理範圍。咱們須要編寫一個directive 來讓 DOM 的改變即時更新到 Model 裏。
var directives = angular.module('directives', []); directives.directive('datepicker', function() { return function(scope, element, attrs) { element.datepicker({ inline: true, dateFormat: 'dd.mm.yy', onSelect: function(dateText) { var modelPath = $(this).attr('ng-model'); putObject(modelPath, scope, dateText); scope.$apply(); } }); } });
而後在 HTML 中引入這個 direcitve
<input type="text" datepicker ng-model="myObject.myDateValue" />
說白了 directive 就是在 HTML 裏寫自定義的標籤屬性,達到插件的做用。這種聲明式的語法擴展了 HTML。
須要說明的是,有一個 AngularUI 項目提供了大量的 directive 給咱們使用,包括 Bootstrap 框架中的插件以及基於 jQuery 的其餘很熱門的 UI 組件。我以前說過 AngularJS 的社區很活躍嘛,生態系統健全。
這是個大坑。若是你去查看 ngOption 生成的 <select>
中的 <option>
的選項值(每一個 <option value="xxx">
的 value 部分),那絕對是枉費心機。由於這裏的值永遠都會是 AngularJS 內部元素的索引,並非你所指定的表單選項值。
仍是要轉變觀念,AngularJS 已經再也不用表單進行數據交互了,而是用 Model。使用 $http 來提交 Model,在 php 中則使用 file_get_contents('php://input')
來獲取前端提交的數據。
在頁面初始化的時候,用戶可能會看到 {{ }},而後閃爍一下才出現真正的內容。
解決辦法:
Controller 不該該直接引用 DOM,而應該控制 view 的行爲。例如「若是用戶操做了 X,應該發生什麼事情」,「我從哪裏能夠得到 X?」
Service 在大部分狀況下也不該該直接引用 DOM,它應該是一個單例(singletons),獨立於界面,與 view 的邏輯無關。它的角色只是「作 X 操做」。
DOM 操做應該放在 directives 裏面。
你所寫的功能極可能 AngularJS 已經實現了,有一些代碼是能夠抽象出來複用的,使用更 Angular 的方式。總之就是不少 jQuery 的繁瑣代碼能夠被替代。
1. ng-repeat
ng-repeat 頗有用。當 Ajax 從服務器得到數據後,咱們常用 jQuery (好比上面講過的例子) 向某些 HTML 容器節點中添加更多的元素,這在 AngularJS 裏是很差的作法。有了 ng-repeat 一切就變得很是簡單了。在你的 $scope 中定義一個數組 (model) 來保存從服務器拉取的數據,而後使用 ng-repeat 將它與 DOM 綁定便可。下面的例子初始化定義了 friends 這個 model
<div ng-init="friends = [{name:'John', age:25}, {name:'Mary', age:28}]"> I have {{friends.length}} friends. They are: <ul> <li ng-repeat="friend in friends"> [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old. </li> </ul> </div>
顯示結果
I have 2 friends. They are: [1] John who is 25 years old. [2] Mary who is 28 years old.
2. ng-show
ng-show 也頗有用。使用 jQuery 來根據條件控制界面元素的顯示隱藏,這很常見。可是 Angular 有更好的方式來作到這一點。ng-show (以及 ng-hide) 能夠根據布爾表達式來決定隱藏和顯示。在 $scope 中定義一個變量:
<div ng-show="!loggedIn"> 點擊 <a href="#/login">這裏</a> 登陸 </div>
相似的內置 directives 還有 ng-disabled, ng-switch 等等,用於條件控制,語法簡潔,都很強大。
3. ng-class
ng-class 用於條件性地給元素添加 class,之前咱們也常常用 jQuery 來實現。Angular 中的 ng-class 固然更好用了,例子:
<div ng-class="{ errorClass: isError, warningClass: isWarning, okClass: !isError && !isWarning }">...</div>
在這裏 ng-class 接受一個 object 對象,key 爲 CSS class 名,值爲 $scope 變量控制的條件表達式,其餘相似的內置 directives 還有 ng-class-even 和 ng-class-odd,很實用。
AngularJS 的雙向數據綁定是最使人興奮的特性了,然而它也不是全能的魔法,在某些狀況下你須要作一些小小的修正。
當你使用 ng-model, ng-repeat 等等來綁定一個元素的值時, AngularJS 爲那個值建立了一個 $watch,只要這個值在 AngularJS 的範圍內有任何改變,全部的地方都會同步更新。而你在寫自定義的 directive 時,你須要定義你本身的 $watch 來實現這種自動同步。
有時候你在代碼中改變了 model 的值,view 卻沒有更新,這在自定義事件綁定中常常遇到。這時你就須要手動調用 scope.$apply() 來觸發界面更新。上面 datepicker 的例子已經說明了這一點。第三方插件可能會有 call back,咱們也能夠把回調函數寫成匿名函數做爲參數傳入$apply()中。
ng-repeat 頗有用,不過它和 DOM 綁定了,很難在同一個元素上使用其餘 directives (好比 ng-show, ng-controller 等等)。
若是你想對整個循環使用某個 directive,你能夠在 repeat 外再包一層父元素把 directive 寫在那兒;若是你想對循環內部的每個元素使用某個 directive,那麼把它放到 ng-repeat 的一個子節點上便可。
Scope 在 templates 模板中應該是 read-only 的,而在 controller 裏應該是 write-only 的。Scope 的目的是引用 model,而不是成爲 model。model 就是咱們定義的 JavaScript 對象。
Scopes 在 AngularJS 中造成必定的層級關係,樹狀結構必然有一個根節點。一般咱們用不到它,由於幾乎每一個 view 都有一個 controller 以及相對應的本身的 scope。
但偶爾有一些數據咱們但願全局應用在整個 app 中,這時咱們能夠將數據注入 $rootScope。由於其餘 scope 都會繼承 root scope,因此那些注入的數據對於 ng-show 這類 directive 都是可用的,就像是在本地 $scope 中的變量同樣。
固然,全局變量是邪惡的,你必須很當心地使用 $rootScope。特別是不要用於代碼,而僅僅用於注入數據。若是你很是但願在 $rootScope 寫一個函數,那最好把它寫到 service 裏,這樣只有用到的時候它纔會被注入,測試起來也方便些。
相反,若是一個函數的功能僅僅是存儲和返回一些數據,就不要把它建立成一個 service。
怎樣組織代碼文件和目錄?這恐怕是初學者一開始就會遇到的問題。AngularJS 應用開發的官方入門項目angular-seed,其文件結構是這樣的:
這種結構對於一個簡單的單頁 app 來講是可行的,只是一旦代碼中存在多個 Controller 或者 Service,就很難找到想要尋找的對象了。咱們能夠對文件按照業務邏輯進行拆分,就像下面這樣:
這種結構把不一樣的業務功能拆分爲獨立的文件,條理清晰,可是仍有必定的侷限性。最大的問題是一個業務功能的代碼分佈在controllers, models, servers 三個不一樣目錄下,要從中挑出正確的文件,創建起代碼關聯,仍是有些麻煩。按照功能進行模塊化劃分目錄結構,應該要更爲合理一些:
這樣也是適合 RequireJS 等模塊加載器的天然直觀的代碼組織方式。