AngularJS學習筆記

近日經過圖靈社區的教程開始學習AngularJS,在此記錄下要點。javascript


準備

教程中有一些不完善的地方,包括php

  • Github上的代碼已經更新,主要部分大致相同,但測試部分已經不能像教程中說的那樣工做,所以先略過測試部分,待完整了解AngularJS後再作研究;
  • nodeJS開啓服務器的方式也和文章所述不一樣,下面會提到。

所須要的工具css

  • nodeJS
  • git
  • 代碼庫:git clone git://github.com/angular/angular-phonecat.git

node配置html

教程中的方法已經失效,應使用以下方法:前端

在git中先轉到angular-phonecat目錄,輸入:java

npm install //這一步要一段時間,要耐心等完。強制退出將致使失敗,失敗後須要刪除目錄下的node_modules文件夾重來
npm start

每一個教程之間都須要用到checkout命令,建議另開個git bash,這樣就不須要重複開啓node服務器了。node

一些說明git

  • 一些名詞會比較混淆,例如 ngRoute$routeProvider,個人理解是: ngRoute 是模塊名,而$routeProvider開頭的是服務名。加載模塊後纔可使用對應的服務。
  • angular.module(‘newModelName’, [‘relayModelName’,’…’]) 這個語句是聲明一個模塊,第一個參數是聲明的模塊名,第二個參數是一個數組,包含了該模塊所依賴的模塊,能夠是空數組。
  • 相關JS文件的引入,只須要保證angular.js放在第一,其餘文件的順序沒有關係,例如
<head>
  <meta charset="utf-8">
  <title>Google Phone Gallery</title>
  <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css">
  <link rel="stylesheet" href="css/app.css">
  <!-- angular.js要放在第一位 -->
  <script src="bower_components/angular/angular.js"></script>

  <!-- 下面的引入順序沒有影響 -->
  <script src="bower_components/angular-route/angular-route.js"></script>
  <script src="bower_components/angular-resource/angular-resource.js"></script>
  <script src="js/app.js"></script>
  <script src="js/controllers.js"></script>
  <script src="js/filters.js"></script>
  <script src="js/services.js"></script>
</head>

基本入門

ng-app標記了AngularJS的做用範圍:angularjs

<html lang="en" ng-app>

雙花括號綁定表達式,相似於JavaScript表達式,能夠用於計算,動態輸出,例如:github

<p>1 + 2 = {{ 1 + 2 }}</p>

也能夠是從其餘地方的變量讀取過來,例如:

<html ng-app> <head> ... <script src="lib/angular/angular.js"></script> <script src="js/controllers.js"></script> </head> <body ng-controller="PhoneListCtrl"> <ul> <li ng-repeat="phone in phones"> {{phone.name}} <p>{{phone.snippet}}</p> </li> </ul> </body> </html>

PhoneListCtrl是controllers.js定義的控制器的名字,controllers.js的內容,大概長這樣:

function PhoneListCtrl($scope) {
  $scope.phones = [
    {"name": "Nexus S",
     "snippet": "Fast just got faster with Nexus S.",
     "age": 0},
    {"name": "Motorola XOOM™ with Wi-Fi",
     "snippet": "The Next, Next Generation tablet.",
     "age": 1},
    {"name": "MOTOROLA XOOM™",
     "snippet": "The Next, Next Generation tablet.",
     "age": 2}
  ];

  $scope.orderProp = 'age';
}

phones是controllers.js中定義的數據,其做用域$scope是定義ng-controller元素之內的區域(即body)

ng-repeat是AngularJS的迭代器,會遍歷phones數組


MVC

AngularJS使用了MVC模式,即模型Model-視圖View-控制器Controller。

根據個人初步瞭解,能夠做以下對應:

模型:HTML標籤,例如:

 <ul> <li ng-repeat="phone in phones"> {{phone.name}} <p>{{phone.snippet}}</p> </li> </ul>

視圖:咱們在頁面上看到的實時數據
控制器:在上個例子中,controllers.js就是控制器,他負責模型和視圖的同步。


雙向綁定

抽象地說,雙向綁定即數據從視圖到模型的綁定,以及從模型到視圖的綁定。

在實際的例子中,能夠這樣理解:假如視圖中一個input元素(視圖)綁定了query變量(模型),那麼輸入框內容(視圖)的修改,會實時更新到這個變量(模型)中;反之亦然。

能夠看下面迭代器過濾這個例子


迭代器過濾/排序

迭代器中使用filter能夠實時過濾輸出

下面這個輸入框綁定了query,當輸入框(視圖)修改內容時,變量query(模型)的值會實時更新,這個更新的直接表現爲filter:query改變,致使列表結果的更新。

<input ng-model="query">

在迭代器中添加filter:query,能夠實時顯示匹配搜索框的結果

<ul class="phones">
  <li ng-repeat="phone in phones | filter:query">
    {{phone.name}}
    <p>{{phone.snippet}}</p>
  </li>
</ul>

相似地,若是爲每一個phone實例添加一個能夠比較大小的屬性,如age,那麼咱們就能夠有name和age兩種排序方式能夠選擇。

Search: <input ng-model="query"> Sort by: <select ng-model="orderProp"> <option value="name">Alphabetical</option> <option value="age">Newest</option> </select> <ul class="phones"> <li ng-repeat="phone in phones | filter:query | orderBy:orderProp"> {{phone.name}} <p>{{phone.snippet}}</p> </li> </ul>

select選中項的value值(視圖)會實時更新到orderProp(模型)中,迭代器會根據實際值來進行排序輸出。


XHR和依賴注入

XHR即XMLHttpRequest。以前咱們的數據都是定義在js文件內的,如今咱們也能夠用XHR異步請求一個JSON文件,並在請求成功後的回調函數裏,將解析後的JSON賦值給模型。

而所謂依賴注入,即AngularJS會根據你的須要,加載須要的服務(這裏咱們須要的是$http),以及該模塊依賴的服務。

function PhoneListCtrl($scope, $http) {//將須要的模塊名做爲參數傳入
  $http.get('phones/phones.json').success(function(data) {
    $scope.phones = data;
  });

  $scope.orderProp = 'age';
}

PhoneListCtrl.$inject = ['$scope', '$http'];

最後一行代碼是爲了防止JS壓縮時,參數名字被修改致使沒法識別正確的模塊名而致使的錯誤。

也可使用傳入數組的方式:

var PhoneListCtrl = ['$scope', '$http', function($scope, $http) { /* constructor body */ }];

延遲替換

對於img元素,若是src中帶有{{…}},因爲在AngularJS解析前瀏覽器就嘗試直接去獲取改src的資源,便會致使錯誤。所以,能夠將src屬性改寫爲ng-src:

<img ng-src="{{phone.imageUrl}}">

相似的還有Title元素,能夠用ng-bind-template來替換,如:

<title ng-bind-template="Google Phone Gallery: {{query}}">Google Phone Gallery</title>

關於ng-bind-template和ng-bind的區別,官方解釋以下:

ng-bind-template

The ngBindTemplate directive specifies that the element text content should be replaced with the interpolation of the template in the ngBindTemplate attribute. Unlike ngBind, the ngBindTemplate can contain multiple {{ }} expressions. This directive is needed since some HTML elements (such as TITLE and OPTION) cannot contain SPAN elements.

即ng-bind-template能夠包含{{}},而ng-bind則不能包含{{}}(ng-bind內的模型直接使用便可,不須要{{}})


路由與多視圖

AngularJS能夠方便地實現前端路由和多視圖功能,能夠在一個頁面內,在不徹底刷新的狀況下跳轉到另外一個頁面。

在這種狀況下,index.html是一個空模版:

<!doctype html>
<html lang="en" ng-app="phonecatApp">
<head>
  <meta charset="utf-8">
  <title>Google Phone Gallery</title>
  <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css">
  <link rel="stylesheet" href="css/app.css">
  <script src="bower_components/angular/angular.js"></script>
  <script src="bower_components/angular-route/angular-route.js"></script>
  <script src="js/app.js"></script>
  <script src="js/controllers.js"></script>
</head>
<body>

  <div ng-view></div>

</body>
</html>

body內的div具備ng-view屬性,表明他是載入其餘頁面的容器。

AngularJS的路由功能,可讓該容器,在不一樣URL上載入不一樣的頁面模版。

使用路由功能的JS代碼以下:

'use strict';

/* App Module */

var phonecatApp = angular.module('phonecatApp', [
  'ngRoute',
  'phonecatControllers'
]);

phonecatApp.config(['$routeProvider',
  function($routeProvider) {
    $routeProvider.
      when('/phones', {
        templateUrl: 'partials/phone-list.html',
        controller: 'PhoneListCtrl'
      }).
      when('/phones/:phoneId', {
        templateUrl: 'partials/phone-detail.html',
        controller: 'PhoneDetailCtrl'
      }).
      otherwise({
        redirectTo: '/phones'
      });
  }]);

$routeProvider.when告訴了瀏覽器路由規則,包括使用的模版頁面以及對應的控制器。

注意/phones/:phoneId,冒號聲明的變量將會被提取,並存放到$routeParams中,調用方法以下:

controllers.js

'use strict';

/* Controllers */

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

phonecatControllers.controller('PhoneListCtrl', ['$scope', '$http',
  function($scope, $http) {
    $http.get('phones/phones.json').success(function(data) {
      $scope.phones = data;
    });

    $scope.orderProp = 'age';
  }]);

phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams', '$http',
  function($scope, $routeParams, $http) {
    $http.get('phones/' + $routeParams.phoneId + '.json').success(function(data) {
      $scope.phone = data;
    });
  }]);

過濾器(映射器)

過濾器,這名字乍一聽,以爲會和前面講的」迭代過濾器」混淆。對他進行一番瞭解後,以爲」映射器」的名字更爲合適,也更容易理解。他的功能很容易理解:

假若有一個模型的值是true或者false,咱們能夠利用過濾器,將其在視圖中映射爲√或者×。

過濾器關鍵代碼以下:

'use strict';

/* Filters */

angular.module('phonecatFilters', []).filter('checkmark', function() {
  return function(input) {
    return input ? '\u2713' : '\u2718';//√和×的unicode編碼
  };
});

這樣咱們新建了一個名爲phonecatFilters的模塊,其中有名爲checkmark的過濾器。以後在phonecatApp裏添加對phonecatFilters模塊的依賴:

var phonecatApp = angular.module('phonecatApp', [
  'ngRoute',
  'phonecatControllers',
  'phonecatFilters'
]);

以後就能夠在模型中使用了:

...
    <dl>
      <dt>Infrared</dt>
      <dd>{{phone.connectivity.infrared | checkmark}}</dd>
      <dt>GPS</dt>
      <dd>{{phone.connectivity.gps | checkmark}}</dd>
    </dl>
...

事件處理

AngularJS有本身的一套事件處理機制,click事件綁定以下,首先定義事件處理函數:

function PhoneDetailCtrl($scope, $routeParams, $http) {
...
 $scope.setImage = function(imageUrl) {
    $scope.mainImageUrl = imageUrl;
  }
}

而後在模版中進行綁定

<img ng-src="{{mainImageUrl}}" class="phone">

...

<ul class="phone-thumbs">
  <li ng-repeat="img in phone.images">
    <img ng-src="{{img}}" ng-click="setImage(img)">
  </li>
</ul>
...

定製服務

我對定製服務的理解還不是很是深入。定製服務的優點,原文是這樣描述的

對咱們應用所作的最後一個改進就是定義一個表明RESTful客戶端的定製服務。有了這個客戶端咱們能夠用一種更簡單的方式來發送XHR請求,而不用去關心更底層的$http服務(API、HTTP方法和URL)。

咱們定義的服務寫在services.js中,另外咱們還須要ngResource模塊的$resource服務,他被定義在angularjs-resource.js中。

services.js

'use strict';

/* Services */

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

phonecatServices.factory('Phone', ['$resource',
  function($resource){
    return $resource('phones/:phoneId.json', {}, {
      query: {method:'GET', params:{phoneId:'phones'}, isArray:true}
    });
  }]);

app.js中引入phonecatServices模塊

var phonecatApp = angular.module('phonecatApp', [
  'ngRoute',
  'phonecatControllers',
  'phonecatFilters',
  'phonecatServices'
]);

以後,咱們就能夠把controllers.js改寫爲下面這樣

'use strict';

/* Controllers */

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

phonecatControllers.controller('PhoneListCtrl', ['$scope', 'Phone',
  function($scope, Phone) {
    $scope.phones = Phone.query({phoneId:'phones'});
    $scope.orderProp = 'age';
  }]);

phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams', 'Phone',
  function($scope, $routeParams, Phone) {
    $scope.phone = Phone.get({phoneId: $routeParams.phoneId}, function(phone) {
      $scope.mainImageUrl = phone.images[0];
    });

    $scope.setImage = function(imageUrl) {
      $scope.mainImageUrl = imageUrl;
    }
  }]);

services.js做一些說明:

var phonecatServices = angular.module('phonecatServices', ['ngResource']);
//聲明一個phonecatServices模塊

 phonecatServices.factory('Phone', ['$resource',
//使用工廠方法,聲明一個名爲Phone的服務
  function($resource){
    return $resource('phones/:phoneId.json', {}, {
      query: {method:'GET', params:{phoneId:'phones'}, isArray:true}
//定義Phone服務的query方法,用於GET資源'phones/:phoneId.json',其中:phoneId是變量,該默認值被定義爲"phones"。固然也能夠不聲明默認變量,而是在調用時傳入實參,下面會說起。
    });
  }]);

controller.js做一些說明:

'use strict';

/* Controllers */

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

phonecatControllers.controller('PhoneListCtrl', ['$scope', 'Phone',
  function($scope, Phone) {
    $scope.phones = Phone.query();
/* 以前:$http.get('phones/phones.json').success(function(data) { $scope.phones = data;}); 如今:$scope.phones = Phone.query(); 可見這裏被簡化了。若是不給Phone.query傳入實參,他會使用默認參數phones,固然咱們也能夠手動傳入參數:Phone.query({phoneId:'phones'}) */
    $scope.orderProp = 'age';
  }]);

phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams', 'Phone',
  function($scope, $routeParams, Phone) {
    $scope.phone = Phone.get({phoneId: $routeParams.phoneId}, function(phone) {
      $scope.mainImageUrl = phone.images[0];
    });
/* 在services.js中並無定義Phone.get方法,爲何可使用它呢? 經過查閱API文檔可知,$resource方法的返回值是一個可被擴展的對象,其自帶get,save,query(query已經被咱們擴展了),remove,delete函數。 經過get方法,並傳入被點擊的連接對應的phoneId做爲參數,獲取手機詳細頁所須要的json文件,在成功後執行回調函數。 */
    $scope.setImage = function(imageUrl) {
      $scope.mainImageUrl = imageUrl;
    }
  }]);
相關文章
相關標籤/搜索