YY遊戲雲的AngularJS實踐(轉載)

爲何選擇AngularJS
輕鬆構建單頁面應用
能夠說,這是咱們最終選擇AngularJS的重要緣由,若是你但願構建一個結構清晰、可維護、開發效率高、體驗好的單頁應用,AngularJS是至關不錯的框架。前端

單頁面應用的魅力git

什麼是單頁面應用?單頁應用,指一種基於Web的應用或者網站,頁面永遠都是局部更新元素,而不是整頁刷新,給用戶的感受就像整個網站都是一個頁面。當用戶點擊某個菜單或者按鍵時,不會跳轉到其餘頁面,前端會從後端獲取對應頁面的數據而不是HTML,以後在頁面中須要更新內容的地方,局部動態刷新,而若是是傳統的多頁網站,當用戶訪問不一樣的頁面時,服務器會直接返回一個HTML,而後瀏覽器負責將這個HTML展示給用戶。目前,大部分雲控制檯,都是單頁應用架構,單頁應用能帶來一種更近似客戶端,而不是網頁的體驗。單頁面應用網站,在體驗方面,具備以下優勢:程序員

作「頁面跳轉」時,永遠都是局部動態刷新,用戶不會感受整個屏幕閃了一下,而僅僅是須要變化的區域作了局部刷新。例如兩個不一樣的頁面,假設頁面元素都是同樣的,只是元素中的文字不同(例如每一個頁面都有一個麪包屑,一排按鈕及一個表格,這幾個元素的佈局也是同樣的),當用戶跳轉到另一個頁面時,會看到整個頁面並無從新渲染,只是文字發生了變化。簡單地說,這有點相似使用App,永遠都是局部發生變化,你見過哪一個App,當點擊到不一樣的功能視圖時,整個屏幕會白屏閃一下的麼?github

URL可收藏,可回退。若是瀏覽器的URL一直不變,那還不能稱爲真正的單頁應用。不一樣的功能,不一樣的資源頁面,對應的URL是不同的。當用戶跳轉到另外的功能時,會發現URL會變成對應的值;經過回退鍵,也能夠回到以前瀏覽的頁面。這個特性有什麼用呢?若是URL不會隨着功能而變化,當用戶刷新當前頁面時,就會回到以前的默認頁面,而不是預期的當前功能頁面,一樣的,URL也不具有可收藏性,由於點開以前收藏的URL,永遠都是網站的默認頁面。這對一些強交互的管控系統來講,體驗很差。json

功能切換時快速流暢。快速流暢主要由於兩個緣由:一是頁面都是局部刷新,從用戶感官來講更快;二是和後臺通訊的內容,都是數據,而不是頁面模板,請求量更少;而傳統網站,在訪問不一樣頁面時,服務端返回的是HTML,體積更大,並且還須要一直重複加載JavaScript、CSS文件。後端

由於網站的單頁化,能夠更好地使用全局類的交互(作頁面切換時,須要一直保持不變的交互)。例如,頁面上須要顯示某次耗時操做的進度,例如上傳文件進度,耗時操做的當前狀態等,你能夠在頁面最右側固定顯示進度。當用戶訪問單頁應用時,他會明白,當點擊其餘模塊時,這個右側的通知欄不會消失,會固定顯示。而若是是多頁網站,用戶則會困惑,擔憂本身跳轉到其餘頁面後,進度通知就會消失。瀏覽器

AngularJS是開發單頁應用的利器緩存

單頁面應用對於代碼分層,結構清晰有更高的要求,而AngularJS是一個MVVM框架,其自身的約定,減小了咱們寫出「一鍋粥」代碼的可能性(在下面討論「編寫更易維護的代碼」時會詳述)。服務器

AngularJS的著名第三方組件UI-Router,是一個控制頁面路由的組件,它支持咱們快速搭建單頁應用(AngularJS自己的路由功能也能夠,但功能會稍微弱一些)。架構

AngularJS的檢查更新機制,使頁面刷新時更加快速天然。 當數據「可能」發生變化時,AngularJS會檢查出全部變化的元素,再「一次性」刷新全部變化的UI元素。

編寫更易維護的代碼
不少人常常會抱怨,不一樣水平的人湊在一塊兒寫JavaScript,到最後項目常常就是一鍋粥,同一個JavaScript文件裏面,各類各樣的邏輯都混在一塊兒,要增刪功能,簡直是惡夢。無規矩不成方圓。做爲框架,AngularJS無疑能大大改善這種情況,使得項目總體的分層明瞭,職責清晰。在筆者看來,AngularJS可以幫助咱們編寫更易維護的代碼,一是由於其「關注點分離」的理念,二是由於其強大的特性爲項目節省了很多代碼量,從而下降程序員犯錯的機率。

關注點分離

關注點分離是AngularJS的一大設計哲學。所謂關注點分離,指的是各個邏輯層職責清晰明確。例如,當你須要修改甚至替換展示層時,無需關注業務層是怎麼實現的。在AngularJS中,服務層(Ajax請求)- 業務層(Controller)- 展示層(HTML 模板)- 交互層(animation)這些都有對應的基礎組件。不一樣組件職責不一樣,也很難將本屬於B組件的職責放到A組件上去實現。舉幾個例子:

HTML及Controller須要協同工做,但職責分明。視圖、交互層面的邏輯,例如展現隱藏某些元素,是HTML模板的職責,Controller只能用於數據初始化。若是你反其道而行,想在Controller中作HTML模板作的事情,將會很是彆扭。這一點很是重要,傳統的JavaScript代碼,常常會出現的狀況,就是JavaScript裏面會有大量DOM操做的邏輯,同時還有大量數據操做相關的邏輯,這些邏輯耦合到一塊兒,當須要單獨重構數據層或者視圖層時(例如項目UI改版),都會捉襟見肘,同時,因爲JavaScript代碼量的迅速膨脹,維護起來也會很麻煩。

你沒法將後臺通訊邏輯放到Controller中實現,而要放到Factory中。後臺通訊邏輯,通常要作成公用的。而因爲Controller之間是不能相互調用的,因此你也不可能將後臺通訊邏輯放到其中一個Controller,而後其餘Controller來調用這個Controller暴露的接口。惟一的辦法,就是將後臺通訊邏輯放到Factory或者Service中。

Filter及Directive看似均可以用於數據轉換,但實則不一樣。因爲Filter只能作數據格式化,不支持引入模板,因此公用的UI交互,涉及到DOM元素或者須要引入HTML模板時時,也只能經過Directive來實現。

綜上所述,AngularJS項目,其展示層、交互層的邏輯,都是在HTML或者指令中,服務層(後臺通訊),只適合出如今Factory(或者Service)中,而業務層則由Controller來負責。這樣每層的邏輯都是輕薄的,而不是糾結在一塊兒。 若是你只是要優化展現邏輯,那改改HTML就能夠了,不用去管Controller是怎麼寫的。這一點咱們有親身體會。項目開發過程當中,咱們重構了視覺效果,全部的HTML都要重寫。但在重構時,咱們的Controller、後臺通訊(Service)、Filter基本都不用改,只要改HTML就好了。而若是項目是用jQuery寫的,顯然不可能作到,你須要從新爲新的HTML增長一些可供jQuery選擇器使用的class或id,而後須要在JavaScript裏面綁定事件,根據新的CSS樣式名來寫新的交互效果,而在AngularJS上,有些不用作了(例如爲了jQuery選擇器,而爲HTML元素增長新的class、id;在JavaScript中綁定事件),有些(例如交互效果)則只要改HTML就行,而不是改JavaScript。

AngularJS爲咱們省去的代碼量

對於任何項目來講,代碼臃腫、冗餘是可維護性的天敵。所以,實現一樣的功能,代碼量越少,抽象度越高,冗餘度越低,在某種程度上意味着項目更方便維護。而能減小代碼量,也是AngularJS被推崇的一大優勢。讓咱們來看看,它是如何減小了代碼量的:

首先,做爲一個大而全的框架(雙刃劍,有利有弊),AngularJS提供的諸多特性,使咱們能夠更專一於業務代碼的編寫。

其次,AngularJS雙向數據綁定的特性,將咱們從大量的值綁定代碼中解放出來。和jQuery對比,AngularJS不用爲了選擇某個元素,而刻意爲HTML加上一些跟樣式無關的class、2-2-2. id;不用寫一堆從HTML元素中取值,設值的代碼;不用在JavaScript代碼中綁定事件;不用在JavaScript值發生變化時寫代碼去更新HTML對應的值。雙向數據綁定,讓咱們告別不少簡單無趣的綁定事件、綁定值的代碼。

Directive、Filter、Factory等,自然的就是一個個能夠複用的組件,減小了冗餘重複代碼。一些須要公用的邏輯,若是放在Controller中,都會至關彆扭,就這樣被AngularJS「逼着」,把公用邏輯都放到Directive、Filter、Factory中去。

開發心得總結
我理解的AngularJS基礎組件
化繁爲簡,幾大基礎組件的使用場景

首先咱們須要理清AngularJS幾個組件的使用場景。AngularJS的一個毛病,就是新概念,新特性太多,新手一會兒要了解這麼多,學習曲線略陡。爲了幫助你們理解,總結下我理解的幾大組件使用場景。

請求資源與數據緩存的東西放進Service。Factory、Service本質上都是Provider的語法糖,二者只是使用方式有所不一樣,建議你們直接都用Service,ES6 class更容易,以後想平滑遷移到Angular2也會更容易,同時也能避免團隊成員在選擇Service仍是Factory時產生困惑。

數據須要格式化的東西用Filter處理。例如把status值轉化爲中文值,把時間戳轉成時間字符串之類。

須要公用的DOM操做,放在指令中去寫。另外,若是須要引入jQuery組件,也能夠寫個指令把jQuery組件初始化代碼放進去。

Controller與視圖按照一對一的關係維護,在Controller內初始化Scope對象與在Scope上添加方法(行爲),爲ViewModel作賦值。其餘全部過程都不該該出如今Controller中。Controller中不該該出現和頁面展現、交互相關的代碼。例如展現隱藏某些元素之類,這些應該是HTML模板或者指令負責。代碼越薄越好。

全局常量值放到Constant。

指令(Directive)魔法

指令這個特性,用「魔法」一詞來形容它,都不爲過。

解決的痛點:一言以蔽之,指令提供了一套前端組件化的方法及約定,這使得編寫,使用UI組件更加方便了。相對於jQuery,它解決了如下痛點:

動態生成了HTML元素後,不用再手動去爲其加上JavaScript特性。舉個例子:HTML原生的checkbox框比較醜,在jQuery時代,能夠將checkbox替換成自定義的效果,若是是頁面一開始就有的checkbox,咱們能夠在document.ready的時候調用自定義checkbox的初始化方法。可是,若是這個checkbox是動態生成的,在每一個動態生成checkbox的地方,咱們都得去調用checkbox的初始化方法,至關麻煩。但用了AngularJS的指令,就不會有這個問題了,只要在模板的chceckbox中加上指令,無論這個模板是動態變化的仍是靜態的,無需經過業務代碼來逐個調用初始化方法,呈現給用戶的,就已是AngularJS替換後的checkbox效果。

一個組件的HTML和JavaScript,是一個總體,而不是割裂的(題外話,這一點React作得比Angular1.x還要好)。基於jQuery的UI組件,其引入方法,常常是這樣的,首先,要求你本身copy一段指定的HTML,而後再調用初始化方法。而指令則支持定義對應的模板HTML,用戶在引入時,可能只要寫一個指令標籤,就會自動生成N行的HTML及綁定對應的JavaScript效果。固然,理論上jQuery也能作到這樣,可是會比Angular的實現麻煩許多。

應用、移除UI特性時方便直觀。假設有這麼一個需求,給一個普通輸入框增長輸入限制,只能輸入特定字符(如字母數字),寫好對應指令,只要給這個input輸入框加上這個指令標籤,就能立刻應用這個特性,以後要移除,只要把標籤去掉就好。相比之下,jQuery就會麻煩多。jQuery下,通常是經過元素選擇器來綁定JavaScript效果。所以,在添加該特性時,你須要考慮給對應的輸入框指定一個合適的元素選擇器。移除特性時,你要考慮:有可能你在動態生成輸入框的地方,都加了這些初始化代碼,這些JavaScript都須要移除;若是元素選擇器用的是class,得考慮是否是其餘輸入框也有這個class,若是是,那麼移除代碼時也會影響到其餘輸入框。

技巧

若是你無可奈何須要引入jQuery組件,你能夠寫一個指令把它包裝起來,在該指令中初始化組件。

要注意require參數中的值是駝峯的,在HTML中就得轉成對應的中劃線命名,例若有require參數phoneKey,那麼HTML中應爲phone-key=」xxx」。雖然這個道理很淺顯,但常常一不當心就會弄錯了,而後發如今指令內部怎麼着都拿不到require參數。

若是你在link中加了elm.bind(‘click’),當click回調函數中,做用域的值發生變化,記得調用scope.$apply(),不然值變化不會生效。

文件、目錄約定
目錄結構

第三方庫、CSS、圖片放置到哪一個目錄,不在本文討論範圍,這裏略過。須要進一步說明的,是業務代碼目錄。

圖片描述
咱們將系統自身的JavaScript、HTML模板都放在pages目錄,其中子目錄common放置公用的JavaScript及模板;其餘子目錄,以功能模塊名做爲目錄名,而後將這個模塊相關的JavaScript及模板放在其中。這樣開發同個模塊功能時,能夠方便地在HTML及對應JavaScript之間切換。有些代碼規範可能還會建議在模塊這一級目錄下,再根據AngularJS的幾大組件Controller、Filter、Service等,建立不一樣的子目錄,例如模塊A/controller,模塊A/service之類,咱們則將全部JavaScript及HTML放在同一級,這樣作主要有幾個緣由:

咱們的項目,平均每一個模塊只有十來個文件,特別是每一個模塊通常只有一個Filter及Service,爲了這一個文件建立一個目錄,顯得畫蛇添足。

經過文件名,已經能夠很方便地區分不一樣的JavaScript組件類型及HTML模板類型,同時,因爲IDE通常會按照文件名字母排序,因此相同功能的JavaScript及HTML會挨在一塊兒,查找對應的模板或JavaScript代碼會方便不少。

文件名約定

這個約定對於Angular來講,特別重要。具體的約定是:
圖片描述
例如,爲「防火牆」模塊開發「建立防火牆」的功能,它的Controller,對應的JavaScript爲:firewall.create.ctrl.js,對應的HTML模板爲:

firewall.create.ctrl.HTML。爲了文件名書寫的方便,定義了組件的簡寫:controller -> ctrl ;factory,service -> svr ; filter-> fil ; directive -> dire。

這樣約定有兩個顯而易見的優勢:

經過文件名,就能知道對應模塊、AngularJS基本組件類型、是模板HTML仍是JavaScript 。

相同功能的JavaScript及HTML,會挨在一塊兒(若是IDE是按照文件命名排序)。

與後端服務器通訊
根據後臺接口規範,結合AngularJS自身能力,咱們作了一些封裝,使接口請求邏輯變得很是簡單。具體問題具體分析,先看看咱們的後臺接口的標準響應格式是怎樣的,前端會按照這個接口返回格式作一些定製:

{

errno:0,
errmsg:"",
data:[
   {
        id:"test",
        name:"test"    
    }
]

}
咱們項目服務端的標準響應是一個json,經過errno描述這次請求的結果碼,經過errmsg描述出錯的緣由(假如請求出錯的話),經過data返回正常數據。

出錯處理

當接口返回的errno!=0時,說明接口返回異常(系統異常或用戶輸入錯誤),這時咱們但願能彈框提示用戶「出錯了」。顯然,若是在每一個接口請求邏輯中,都去寫這個邏輯,會很是累贅,所幸AngularJS提供了攔截器的功能,咱們只要寫一個攔截器,就能夠對全部的異常返回作統一處理。 首先,咱們須要顯式地拋出HTTP錯誤。由於當後臺邏輯出錯或者用戶輸入參數有誤時,返回的HTTP狀態碼都是200(這只是咱們項目的約定),AngularJS並不會認爲200是出錯的狀況,所以,咱們須要作點小動做。

$httpProvider.defaults.transformResponse.push(function (responseData) {

if (responseData.errno != 0) {
            throw responseData;
        }
        ……
  });

AngularJS的$httpProvider.defaults.transformResponse.push(下面簡稱transformResponse)函數,能夠統一處理全部的HTTP響應。在這裏,咱們就經過它捕獲了全部errno!=0的請求,並往外拋一個exception。接着,咱們須要在$httpProvider.interceptors捕獲這個異常並彈框,代碼以下:

$httpProvider.interceptors.push(function () {
return {

responseError: function (response) {
   if (response) {
      if (response.hasOwnProperty("errmsg")) {
          if (response.errno > 0) {
                            alert(response.errmsg);
        } else {
         alert("系統維護中,請稍候重試");
        }

     }
     else {
        if (response.status == 404) {
           alert("抱歉,後臺服務出錯,找不到對應的接口");
        }
        else {
            alert("抱歉,後臺服務出錯");
        }
      }
    }
   }
 }
});

有些人可能會問,爲啥不直接在一開始的transformResponse函數中寫錯誤處理邏輯呢?這是由於,接口正常時,AngularJS會依次調用transformResponse函數,再調用interceptors的responseError。可是,某些異常狀況,並不會調用transformResponse邏輯,例如,當URL不存在時,Web容器默認返回的404頁面,或者當程序出錯時,系統代碼未處理這個錯誤,Web容器會返回默認的500頁面,這時均會直接進入interceptors的responseError中。所以,爲了覆蓋全部的異常狀況,須要在transformResponse中拋出異常,而後由responseError統一處理。

響應內容格式化

因爲前端關心的數據,是放在響應內容的data屬性中。而另外兩個屬性errno、errmsg,當返回正常數據時,前端是不關心的,爲了取數據時更加方便,能夠進一步優化transformResponse中的處理。當errno==0時,都返回responseData.data,這樣,在業務邏輯裏面,就能夠直接使用data了,而不用取xxx.data。

$httpProvider.defaults.transformResponse.push(function (responseData) {

if (responseData && responseData.hasOwnProperty("errno") && responseData.hasOwnProperty("errmsg") && responseData.hasOwnProperty("data")) {
        if (responseData.errno == 0) {
            if (Angular.isArray(responseData.data) || Angular.isObject(responseData.data)) {
                return responseData.data;
            } else {
                return responseData
            }
        }
        else {
            throw responseData;
        }
    }
    else {
        return responseData;
    }
});

對$resource作進一步封裝

項目中每一個模塊,都建立了對應的service文件,用於與後臺進行通訊。例如「硬盤」模塊,對應DiskSvr,「防火牆「模塊,對應FirewallSvr。這樣劃分後,前端全部的後臺請求邏輯,找起來會很方便。

在封裝後臺通訊邏輯時,咱們用到$resource,這是AngularJS自身的一個組件,當你的後臺接口符合RESTFul規範時,你能夠很方便地使用resource和後臺進行通訊。而因爲咱們的後臺並非完整的RESTFul實現,咱們須要作一些簡單的封裝。示意代碼以下:

app.factory("DiskSvr", function () {

var url = "schedule/disk";

var customAPI = {
    clone: {
        method: "post"
    }
}

return getApi(url, customAPI);

});

//公用的,每一個Svr均可以用
getApi = function (path, customAPI) {

Angular.forEach(customAPI, function (value, key) {
    if (!value.url) {
        value.url = util.connectPath(path, key);
    } else {
        value.url = util.connectPath(path, value.url);
    }
});

//全部的
var defaultAPI = {
    detail: {
        url: baseUrl + "/get"
    },
    delete: {
        url: baseUrl + "/delete",
        method: "delete"
    },
    create: {
        url: baseUrl + "/create",
        method: "post"
    },
    update: {
        url: baseUrl + "/update",
        method: "put"
    }
}

return $resource(path, {}, Angular.extend(defaultAPI, customAPI));

}
上面的代碼,主要作了這些事情:

爲全部的svr注入了每一個模塊接口都必備的,最基礎的增刪改查四個接口。這樣就無需在每一個svr中加入這些接口。例如,在業務邏輯中調用DiskSvr.create(),就會用post請求調用schedule/disk/create接口。

簡化了新增接口的配置。新增一個接口,只要配置對應的HTTP方法及名字便可。

經過引用$resource組件、進一步封裝及svr文件,在業務Controller中,不會看到任何的和後臺通訊的基礎代碼,若是在這個Controller中你須要和後臺通訊,你只需注入響應的svr,而後調用對應方法便可。

注入請求header頭

在咱們的項目中,約定了全部的請求都要在header中帶上一些相同的信息,這對AngularJS來講,是很是簡單的事情: 執行如下代碼後,以後全部的HTTP請求都會帶上名爲project的header信息:

圖片描述

Controller間通訊問題
Controller調用

假設controllerA但願調用controllerB的某個函數,告訴同伴Controller,個人某個你所關心的東西改變了,要怎麼作呢?舉具體業務場景,有兩個Controller,一個是主頁Controller,另外主頁上有個彈框表單,這個彈框表單也有個Controller,用戶成功提交了這個表單後,彈框Controller須要告知主頁Controller,「哥們,請更新主頁上的某項數據」。建議的作法,是用AngularJS的消息機制。例如,上面的例子中,彈框Controller是主頁Controller的子Controller。那麼彈框Controller能夠往上冒泡傳遞消息:

圖片描述
其父Controller去捕獲這個消息:

圖片描述

這是一種很好的解耦辦法,假設這兩個Controller是由兩個開發負責的,那麼我開發個人Controller,你開發你的,我不用去關心你那邊的邏輯。

數據共享

多個Controller之間要共享數據,要怎麼作呢? 最簡單但也不推薦的一個作法,就是把數據塞到rootscope中,可是,這就像JavaScript的全局變量,野蠻很差控制。 這裏推薦下咱們的作法:寫一個專門用於存儲、設置共享數據的共享數據Facoty。在裏面定義set方法,全部的共享變量,都須要通過set方法來設置。而後取數據則經過DATA變量獲取。 僞代碼以下:

app.factory('ShareSvr', function(){

var shareData = {
    peopleNum
}
return {
    DATA:shareData,
    setPepleNum:function(num){
        shareData.peopleNum = num;
    }
}

});

app.controller('TestController',function(ShareSvr){

var self = this;
this.DATA = ShareSvr.DATA;

}
這裏並無要求DATA值只能經過get方法獲取,是爲了以後在Controller對應的視圖HTML中取值方便些。

第三方 庫/資源 推薦
UI Router
路由(route),幾乎全部的MVC框架都應該具備的特性,它是前端構建單頁面應用(SPA)必不可少的組成部分。相比原生的ngRouter,UI Router功能更增強大,具有多視圖,嵌套路由等特性,能夠解決路由大部分的應用場景。

ngDialog
一個彈框控件,功能強大,我比較喜歡的地方,是它沒有寫死彈框的HTML、能夠很方便地定義本身想要的彈框模板。例如,咱們項目就經過它作了兩種彈框,一種是普通彈框,一種是側拉框(從屏幕右側滑出,佔滿瀏覽器高度,寬度佔滿一半屏幕(或者其餘自定義寬度)。

AngularJS代碼規範
https://github.com/johnpapa/angular-styl...
https://github.com/mgechev/Angularjs-sty...

Angular-filter
提供了不少實用的filter,string類、math類,集合類等。

w5c-validator
基於Angular.js原有的表單驗證,統一驗證規則和提示信息,在原有的基礎上擴展了一些錯誤提示的功能,讓你們不用在每一個表單上寫一些提示信息的模板,專心的去實現業務邏輯,國人出品。

ng-table
輕量,功能強大的表格組件。能夠很方便地修改表格的樣式、交互效果。

網頁原連接http://geek.csdn.net/news/detail/67432

相關文章
相關標籤/搜索