1. 程式人生 > >一天之內學AngularJS--權威指南(未完)

一天之內學AngularJS--權威指南(未完)

一天之內學AngularJS–權威指南

AngularJs 是什麼?

Angular是基於javaScript語言構建的一個MVC/MVVM框架,對建立現代化單WEB應用(甚至是整個網站)至關重要。這篇文章是基於我的經驗,建議及最佳實踐而編寫的一個非常全面的速成課程。

術語

Angular有一個較短的學習曲線,你會對它有一個徹底的瞭解,當你掌握了基本操作後。對它的學習主要是慢慢掌握一些術語的概念並且認真去理解MVC思想,即模型 - 檢視 - 控制器。下面是有關angular的更高層次的核心APIs以及一些術語。

MVC

你可能曾聽說過MVC,在許多程式語言裡它被用作構建或設計應用程式/軟體的一種手段。下面是對MVC的分解介紹:

模型:一端特定的應用程式中的資料結構,通常採用JSON進行資料傳輸。在你學習Angular之前,你最好仔細研究一下JSON。因為它在你的伺服器與檢視展示的資料互動中扮演者重要的角色。例如,一組使用者標識可以採用如下的形式:

{
  "users" : [{
    "name": "Joe Bloggs",
    "id": "82047392"
  },{
    "name": "John Doe",
    "id": "65198013"
  }]
}

然後,你將能夠通過XHR(XMLHTTP請求)從伺服器獲取這些資料,在jQuery裡它就是$.ajax方法而Angular則將它包裝為 $http

方法,或者在頁面解析(從資料儲存區/資料庫)時寫入到您的程式碼裡。再然後,您可以更新資料模型,並返回給伺服器。

檢視:檢視很簡單,它可以是你的HTML或著渲染後的輸出結果。使用MVC框架,你可以僅僅更新你的模型資料框架會自動更新您的檢視並在HTML中展示相關資料。

控制器:他們控制東西,但又是什麼東西呢? 資料!控制器是從伺服器到檢視的直接溝通者,也就是中間人,因此您可以在伺服器和客戶端進行通訊並即時更新資料。

建立一個AngularJS專案(最基本的)

首先,我們實際上需要對Angular專案設定一些關鍵元素。再開始之前我們要注意,通常會有一個ng-app 指令來定義你的應用程式,一個Controller

來和你的檢視互動,以及一些DOM 繫結並引入Angular。以下是最基本的設定:

一些帶有ng-* 指令的HTML:

<div ng-app="myApp">
    <div ng-controller="MainCtrl">
        <!-- controller logic -->
    </div>
</div>

一個Angular模組與控制器:

var myApp = angular.module('myApp', []);

myApp.controller('MainCtrl', ['$scope', function ($scope) {
  // Controller magic
}]);

在更深入之前,我們需要建立一個Angular module 並將我們所有的控制邏輯繫結到上面,宣告模組的方法很多,例如你可以連結你的控制邏輯像下面這樣(然而我並不喜歡這種方法):

angular.module('myApp', [])
.controller('MainCtrl', ['$scope', function ($scope) {...}])
.controller('NavCtrl', ['$scope', function ($scope) {...}])
.controller('UserCtrl', ['$scope', function ($scope) {...}]);

在我接觸的Angular專案中,建立一個全域性模組被證明是最佳實踐。由於缺少分號或者“程式鏈”的意外斷開經常適得其反並且產生不必要的編譯錯誤。所以像下面這樣吧:

var myApp = angular.module('myApp', []);
myApp.controller('MainCtrl', ['$scope', function ($scope) {...}]);
myApp.controller('NavCtrl', ['$scope', function ($scope) {...}]);
myApp.controller('UserCtrl', ['$scope', function ($scope) {...}]);

我所建立的每個新檔案都只是簡單的引用了myApp 名稱空間,然後他們就自動的註冊進應用當中。是的,我正在為每個Controller, Directive, Factory 甚至任何其他的都建立這樣的新檔案(你會因此而感謝我的!)將他們統統連結起來並最小化,然後只引入單個指令碼檔案到DOM中(利用像Grunt/Gulp 這樣的命令列構建工具)。

控制器

現在你已經掌握了MVC的基本概念和專案基本的構建,接下來讓我們看看Angular關於如何開始使用Controller的實現方式。

就拿上面的例子來說,我們可以更進一步從一個Controller 中向DOM推送一些資料。Angular採用了一種模板樣式的語法{{ handlebars }} 來告訴HTML。理想情況下,你的HTML不應該包含物理文字或硬編碼值以最大限度利用Angular的特性。下面是向DOM中推一個簡單字串的例子:

<div ng-app="myApp">
    <div ng-controller="MainCtrl">
         {{ text }}
    </div>
</div>
var myApp = angular.module('myApp', []);

myApp.controller('MainCtrl', ['$scope', function ($scope) {

    $scope.text = 'Hello, Angular fanatic.';

}]);

實時輸出結果如下:

這裡的一個關鍵概念就是$scope,利用它將你所有的方法放置到特定的Controller$scope 指的是DOM中當前的元素或區域(不,和DOM不一樣)並且封裝了一個完全在元素範圍內保持資料和邏輯的靈活的作用域。它將JavaScript 公共/私有的範圍界限帶到DOM中,這簡直太棒了。

$scope 的概念可能一開始看起來有點恐怖,但是它是你從伺服器獲取資料放入DOM中的方式(或者是靜態資料,如果你也有的話!)這個例子演示了你該如何向DOM推送資料的基本理念。

讓我們來看一些更具代表性的資料結構,假設這些資料是我們已經從伺服器檢索出的用來顯示使用者登入資訊的資料。現在,我將使用靜態資料;稍後我會告訴你如何獲取動態的JSON資料。

首先我們將組織如下JavaScript :

var myApp = angular.module('myApp', []);

myApp.controller('UserCtrl', ['$scope', function ($scope) {

    // Let's namespace the user details
    // Also great for DOM visual aids too
    $scope.user = {};
    $scope.user.details = {
      "username": "Todd Motto",
      "id": "89101112"
    };
}]);

然後將他植入DOM中來展示這些資料:

<div ng-app="myApp">
    <div ng-controller="UserCtrl">
        <p class="username">Welcome, {{ user.details.username }}</p>
        <p class="id">User ID: {{ user.details.id }}</p>
    </div>
</div>

輸出:

重要的是要記住,控制器僅用於資料操作和建立函式(當然還有事件方法)同我們的伺服器進行通訊並且推/拉JSON資料。DOM操作不應該在這裡做,所以把你的jQuery的工具包去除吧。指令適用於DOM操作,但那是下一節要介紹的。

高階技巧:Angular 官方文件(在寫這篇博文時)的例子展示了這種建立控制器的方法:

var myApp = angular.module('myApp', []);

function MainCtrl ($scope) {
  //...
};

。。。千萬別這樣做。這將你的所有方法暴露到了全域性作用域並且使之不能很好的與你的應用程式融合。這也意味著,你不能方便的壓縮程式碼或執行測試。不要汙染全域性名稱空間,你應該保證你的控制器始終在你的應用範圍內。

指令

一個directive (從這篇文件現有的指令碼或外掛程式中檢索指令)的最簡單的形式是一小段HTML模板,最好是在應用中被多次使用的片段。毫不費力地向你的應用中注入DOM或著執行自定義的DOM互動是一種簡單的方法。指令其實一點也不簡單,要想完全掌握它們將會經歷一個難以置信的學習曲線,但是下一階段將讓你充滿幹勁的開始學習。

那麼,指令究竟用來做什麼呢?其實有很多事情,包括DOM元件,例如選項卡或導航元素 - 這完全取決於你的應用程式在UI層使用了什麼。這樣說吧,如果你使用了ng-showng-hide,那這些就是指令(雖然它們並不會注入DOM)。

在此次練習中,我會始終保持它非常簡單,並建立一個被注入了那些我非常討厭的打出來的一些標記的自定義型別按鈕(被稱為CustomButton)。有多種方法來定義DOM指令,這可能看起來像下面這樣:

<!-- 1: as an attribute declaration -->
<a custom-button>Click me</a>

<!-- 2: as a custom element -->
<custom-button>Click me</custom-button>

<!-- 3: as a class (used for old IE compat) -->
<a class="custom-button">Click me</a>

<!-- 4: as a comment (not good for this demo, however) -->
<!-- directive: custom-button -->

我更喜歡將他們作為一個屬性使用,在HTML5的未來Web元件下的自定義元素即將湧現,但是Angular顯示著在一些老的瀏覽器中表現非常糟糕。

現在你已經知道了如何在那些需要使用或注入指令的地方對他們進行宣告,那就讓我們開始建立這個自定義的按鈕吧。同樣,我將其掛在我的全域性名稱空間myApp內.這是一個指令最簡單的形式:

myApp.directive('customButton', function () {
  return {
    link: function (scope, element, attrs) {
      // DOM manipulation/events here!
    }
  };
});

我用.directive()方法來定義我的指令,並傳入該指令的名稱CustomButton。當你將指令名稱的一個字母大寫時,它的使用方式是在DOM中用連字元分割(如上)。

一個指令只需通過一個物件返回自身,並攜帶一系列引數。對我來說最重要的需要優先掌握的是,restrict, replace, transclude, template, templateUrl當然還有link屬性。讓我們新增這些屬性:

myApp.directive('customButton', function () {
  return {
    restrict: 'A',
    replace: true,
    transclude: true,
    template: '<a href="" class="myawesomebutton" ng-transclude>' +
                '<i class="icon-ok-sign"></i>' +
              '</a>',
    link: function (scope, element, attrs) {
      // DOM manipulation/events here!
    }
  };
});

輸出:

<iframe width="100%" height="300" src="//jsfiddle.net/toddmotto/VC4H2/embedded/result,js,html" allowfullscreen="allowfullscreen" frameborder="0"></iframe>

請務必檢查元素,看那些被注入的額外標記。是的,我知道,這裡並沒有引入圖示,因為我從未引入Font Awesome,但你看到了它是如何工作的。下面是指令屬質的解釋:

restrict:這又回到它的使用上面來,我們如何限制元素的使用情況?如果你正在使用一個需要傳統的IE瀏覽器支援的專案,你可能需要attribute/class的宣告。如果restrict 值為’A’,意味著你限制它作為一個屬性;”E”為元素,’C’類和’M’評論。這些都預設為“EA”。是的,你可以限制為多個用例。
replace:這替換了DOM中用來定義指令的標記,在本例應用中,你會發現DOM最初是如何被替換為該指令的模板。
transclude:簡單地說,使用transclude允許現有的DOM內容被複制到指令。在網頁被渲染後你將看到 ‘Click me’被’轉移’到指令中。
template:模板(如上)允許你定義用來注入的標記。僅將其應用到小的HTML片段上是一個好主意。注入的模板都被Angular所編譯,這也意味著您可以在其中宣告用於繫結的handlebar 模板標籤。
templateUrl:與模板類似,但保留在它自己的檔案或<script>標籤中。你可以用它指定一個模板的URL,您將樂意使用它,當需要可管理的HTML程式碼塊被保留在自己的檔案中時,只需指定路徑和檔名,最好儲存在他們自己的模板目錄中:

myApp.directive('customButton', function () {
  return {
    templateUrl: 'templates/customButton.html'
    // directive stuff...
  };
});

在你的檔案中(檔名一點都不重要)

<!-- inside customButton.html -->
<a href="" class="myawesomebutton" ng-transclude>
  <i class="icon-ok-sign"></i>
</a>

這樣做的好處是,瀏覽器將快取html 檔案,好極了!另一種不被快取的方法是將模板宣告放在<script> 標籤中。

<script type="text/ng-template" id="customButton.html">
<a href="" class="myawesomebutton" ng-transclude>
  <i class="icon-ok-sign"></i>
</a>
</script>

你會告訴Angular,這是一個ng-template 並給它一個ID號。Angular將尋找ng-template*.html檔案,無論你喜歡使用那種方式。而我喜歡建立*.html檔案,因為他們非常易於管理,可以提高效能並保持DOM乾淨,你可能會使用到1或100個指令,你希望能夠通過他們輕鬆導航。

服務

Services 往往是一個令人困惑的問題。從以往的經驗和研究,他們更多的是風格的設計模式,而不是提供很多功能上的差異。在深入鑽研Angular的原始碼之後,他們期待通過相同的編譯器來執行並且他們分享了很多功能。從我的研究來看,你應該使用單例模式啟用Services 並且使用Factories完成更復雜的功能,如物件常量和更復雜的用例。

下面是一個Service 例子,它計算兩個數字的積:

myApp.service('Math', function () {
  this.multiply = function (x, y) {
    return x * y;
  };
});

你將在一個Controller中像下面這樣呼叫它:

myApp.controller('MainCtrl', ['$scope', function ($scope) {
    var a = 12;
    var b = 24;

    // outputs 288
    var result = Math.multiply(a, b);
}]);

是的,乘法非常簡單因此並不需要一個Service,但你得到了其要領。

當你要建立一個Service (or Factory),您需要使用依賴注入來告訴Angular它需要獲取你的新服務的持有著 - 否則,你會得到一個編譯錯誤,你的控制器也將被打破。你現在可能注意到了控制器宣告中的function ($scope)部分,這就是簡單的依賴注入。給它注入程式碼!您還會注意到function ($scope)之前的['$scope'],我會晚一點再介紹它。以下是如何使用依賴注入來告訴Angular你需要你的服務:

// Pass in Math
myApp.controller('MainCtrl', ['$scope', 'Math', function ($scope, Math) {
    var a = 12;
    var b = 24;

    // outputs 288
    var result = Math.multiply(a, b);
}]);

工廠

從 Services 過渡 Factories 應該是很簡單的。現在,我們可以在一個工廠內建立物件字面量或簡單地提供一些更深入的方法:

myApp.factory('Server', ['$http', function ($http) {
  return {
    get: function(url) {
      return $http.get(url);
    },
    post: function(url) {
      return $http.post(url);
    },
  };
}]);

這裡,我對Angular 的XHR進行了自定義的包裝。將其依賴注入進一個Controller後,它的使用變得非常簡單。

myApp.controller('MainCtrl', ['$scope', 'Server', function ($scope, Server) {
    var jsonGet = '//myserver/getURL';
    var jsonPost = '//myserver/postURL';
    Server.get(jsonGet);
    Server.post(jsonPost);
}]);

待續。。。