你的第一個AngularJS應用

AngularJS是Google開源出來的一款 Javascript MVC 框架。利用AngularJS,你能夠構建結構清晰、便於測試和維護的前端應用。javascript

使用AngularJS,你能夠經過directive去定義不少本身的HTML元素屬性。AngularJS無縫銜接了HTML(view)和Javascript(model),這樣你就不須要去過多地關注Dom如何變化,你只需專一的處理你的數據。html

AngularJS和服務器通訊很是方便。和大多數Javascript的MVC框架同樣, 只要你的應用提供一個RESTful API,AngularJS就能夠和你的服務器相配合。同時,AngularJS提供了基於XHR的服務,這將大大簡化你的代碼,也方便將可複用的服務抽象成API調用。前端

Raoni Boaventura提供了一個教程,一步一步地教你寫一個簡單的AngularJS應用,讓咱們一塊兒來看一下吧。java

爲了簡化問題,咱們要作的是一個直接從網絡上拉取信息的應用,好比,一個查看方程式賽車比賽信息的應用。直接用Ergast的api獲取信息。git

能夠看一下這個demo,對咱們要作一個什麼樣的應用有個大體的概念。angularjs

開動吧

推薦使用augular-seed,提供了一個很好的項目骨架。github

咱們的應用的骨架大概是這樣的:web

1

如今要開始寫代碼了。從最重要的頁面開始吧:錦標賽的表格。chrome

2

HTML大概是這個樣子的(爲了提升可讀性,先忽略CSS):json

<body ng-app="F1FeederApp" ng-controller="driversController">
  <table>
    <thead>
      <tr><th colspan="4">Drivers Championship Standings</th></tr>
    </thead>
    <tbody>
      <tr ng-repeat="driver in driversList">
        <td>{{$index + 1}}</td>
        <td>
          <img src="img/flags/{{driver.Driver.nationality}}.png" />
          {{driver.Driver.givenName}}&nbsp;{{driver.Driver.familyName}}
        </td>
        <td>{{driver.Constructors[0].name}}</td>
        <td>{{driver.points}}</td>
      </tr>
    </tbody>
  </table>
</body>

我想你會注意到模板裏面有一些{{}} 之類的表達式。咱們能夠在裏面作一些計算。舉一些例子吧:

{{ 1 + 1 }}
{{ 946757880 | date }}
{{ user.name }}

是否是和javascript很像?不過,雖然它們很強大,咱們不該該用它們去實現一些高層級的邏輯——這該交給directive。

理解基本的directive

上面的模板裏還有一些相似ng-attributes的語句,這些正是directive。

directive讓AngularJS把特定的行爲附加到DOM元素中。讓咱們看一下上面的模板中的例子:

  • ng-app 初始化你的應用,定義其做用域。在 AngularJS 中,同一頁面能夠有多個應用,ng-app 指令代表應用的首尾位置。
  • ng-controller 定義視圖由哪一個控制器負責。在咱們的例子中,driversController提供了車手的列表(driversList)。
  • ng-repeat 這個最經常使用。當使用循環的時候,ng-repeat定義模板的範圍。在咱們的例子中,就driversList中的每一個車手,ng-repeat會生成重複的行。

添加控制器

固然,沒有控制器,咱們的視圖什麼也幹不了。讓咱們在controllers.js中添加一個driversController

angular.module('F1FeederApp.controllers', []).
controller('driversController', function($scope) {
    $scope.driversList = [
      {
          Driver: {
              givenName: 'Sebastian',
              familyName: 'Vettel'
          },
          points: 322,
          nationality: "German",
          Constructors: [
              {name: "Red Bull"}
          ]
      },
      {
          Driver: {
          givenName: 'Fernando',
              familyName: 'Alonso'
          },
          points: 207,
          nationality: "Spanish",
          Constructors: [
              {name: "Ferrari"}
          ]
      }
    ];
});

你可能注意到了咱們將$scope傳遞給了控制器。$scope變量將控制器和視圖相鏈接。事實上,它儲存了模板中會用到的全部數據。任何你加入的內容(好比咱們的例子中的driversList)能夠直接在視圖中訪問。如今咱們先用一個靜態的數據數組,稍後咱們會把它換成API服務。

app.js中加入:

angular.module('F1FeederApp', [
  'F1FeederApp.controllers'
]);

這行代碼讓咱們初始化了咱們的應用,同時也登記了須要的依賴。稍後咱們會回到這個文件。

好了,如今讓咱們把這一切在index.html中整合起來:

<!DOCTYPE html>
<html>
<head>
  <title>F-1 Feeder</title>
  <script src="lib/angular/angular.js"></script>
  <script src="lib/angular/angular-route.js"></script>
  <script type="text/javascript" src="js/app.js"></script>
  <script type="text/javascript" src="js/controllers.js"></script>
  <script type="text/javascript" src="js/services.js"></script>
</head>
<body ng-app="F1FeederApp" ng-controller="driversController">
  <table>
    <thead>
      <tr><th colspan="4">Drivers Championship Standings</th></tr>
    </thead>
    <tbody>
      <tr ng-repeat="driver in driversList">
        <td>{{$index + 1}}</td>
        <td>
          <img src="img/flags/{{driver.Driver.nationality}}.png" />
          {{driver.Driver.givenName}}&nbsp;{{driver.Driver.familyName}}
        </td>
        <td>{{driver.Constructors[0].name}}</td>
        <td>{{driver.points}}</td>
      </tr>
    </tbody>
  </table>
</body>
</html>

如今你能夠嘗試運行下這個應用了。

若是你但願調試應用,建議看下Chrome的Batarang 插件。

從服務器獲取信息

既然咱們已經知道如何展現這些數據了,如今該是咱們從RESTful服務器獲取信息的時候了。

AngularJS提供的$http$resource幫助咱們和服務器通信。
$http是以XMLHttpRequest和[JSONP]爲基礎的抽象層,$resource則提供更高層的抽象。在這裏咱們使用$http

爲了將API調用從控制器中抽象出來,咱們建立一個本身定製的服務,該服務將抓取咱們須要的信息,將$http封裝起來。在services.js中加入:

angular.module('F1FeederApp.services', []).
  factory('ergastAPIservice', function($http) {

    var ergastAPI = {};

    ergastAPI.getDrivers = function() {
      return $http({
        method: 'JSONP', 
        url: 'http://ergast.com/api/f1/2013/driverStandings.json?callback=JSON_CALLBACK'
      });
    }

    return ergastAPI;
  });

開頭兩行,咱們建立了一個名爲F1FeederApp.services的新模塊,並在ergastAPIservice模塊內註冊了服務。注意,咱們將$http傳遞給了該服務。這就告訴了Angular的依賴注入引擎咱們的新服務依賴於$http服務。

相似地,咱們須要讓Angular將咱們的新模塊包含到應用中。在app.js註冊下便可:

angular.module('F1FeederApp', [
  'F1FeederApp.controllers',
  'F1FeederApp.services'
]);

如今咱們只需調整一下controller.js,將ergastAPIservice做爲依賴:

angular.module('F1FeederApp.controllers', []).
  controller('driversController', function($scope, ergastAPIservice) {
    $scope.nameFilter = null;
    $scope.driversList = [];

    ergastAPIservice.getDrivers().success(function (response) {
        //Dig into the responde to get the relevant data
        $scope.driversList = response.MRData.StandingsTable.StandingsLists[0].DriverStandings;
    });
  });

好了,從新啓動一下應用,看看結果如何。注意咱們徹底沒有改動模板,只是增長了一個nameFilter。讓咱們把這個變量用起來。

過濾器

好極了!咱們的控制器已經能夠工做了。可是它只能顯示一個車手的列表。讓咱們加一些功能吧。咱們來實現一個簡單的文本搜索框,能夠過濾列表。將如下這行加入到index.html,加在<body>標籤下面:

<input type="text" ng-model="nameFilter" placeholder="Search..."/>

如今咱們要用上 ng-model 指令了。這個指令將文本框綁定到$scope.nameFilter變量,而且確保該變量的值會及時更新。如今讓咱們稍微調整下index.html,加上一行ng-repeat指令。

<tr ng-repeat="driver in driversList | filter: nameFilter">

這一行告訴ng-repeat,在輸出數據以前,車手數組先要通過nameFilter的過濾。

此刻就是雙向數據綁定發揮威力的時候了:每次你在搜索框裏鍵入一些值的時候,Angular會及時更新$scope.nameFilter的內容。因爲綁定是雙向的,因此nameFilter更新的時候,相應的指令(ng-repeat)也會得到新的數值,而後視圖會馬上更新。

重啓你的應用,看看搜索框。

3

注意,過濾器會搜索全部屬性中的關鍵詞,包括你不想包括的內容。假設你只想經過Driver.givenNameDriver.familyName過濾:首先,在driversController文件的$scope.driversList = [];下加入這樣一行:

$scope.searchFilter = function (driver) {
    var keyword = new RegExp($scope.filterName, 'i');
    return !$scope.filterName || keyword.test(driver.Driver.givenName) || keyword.test(driver.Driver.familyName);
};

如今回到index.html,更新包括ng-repeat的那行:

<tr ng-repeat="driver in driversList | filter: searchFilter">

從新啓動應用,如今你能夠經過姓名搜索了。

路由

接下來咱們要建立一個車手詳情頁面,當咱們點擊車手的時候,咱們就能夠看到關於他的一些詳細信息。

首先,咱們在app.js里加入$routeProvider服務,這個服務將幫助咱們處理應用路由。而後咱們加入兩個路由:一個轉向錦標賽表格,另外一個轉向車手詳情。

angular.module('F1FeederApp', [
  'F1FeederApp.services',
  'F1FeederApp.controllers',
  'ngRoute'
]).
config(['$routeProvider', function($routeProvider) {
  $routeProvider.
    when("/drivers", {templateUrl: "partials/drivers.html", controller: "driversController"}).
    when("/drivers/:id", {templateUrl: "partials/driver.html", controller: "driverController"}).
    otherwise({redirectTo: '/drivers'});
}]);

修改以後,訪問http://domain/#/drivers,這將加載driversController,而後在partials/drivers.html尋找須要渲染的部分視圖。等等!咱們好像尚未部分視圖?咱們須要建立他們。

部分視圖

AngularJS容許你將路由綁定到特定的控制器和視圖。不過咱們首先須要告訴Angular在哪裏渲染這些部分視圖。這須要使用ng-view指令。修改一下你的index.html

<!DOCTYPE html>
<html>
<head>
  <title>F-1 Feeder</title>
  <script src="lib/angular/angular.js"></script>
  <script src="lib/angular/angular-route.js"></script>
  <script type="text/javascript" src="js/app.js"></script>
  <script type="text/javascript" src="js/controllers.js"></script>
  <script type="text/javascript" src="js/services.js"></script>
</head>
<body ng-app="F1FeederApp">
  <ng-view></ng-view>
</body>
</html>

如今,只要是經過應用路由瀏覽,Angular 就會加載相應的視圖,而且
<ng-view>標籤處渲染。你所須要作的只是建立一個名爲partials/drivers.html的文件,而後將錦標賽表格放在那裏。同時咱們也將將車手的姓名和詳情頁面鏈接起來。

<input type="text" ng-model="nameFilter" placeholder="Search..."/>
<table>
<thead>
  <tr><th colspan="4">Drivers Championship Standings</th></tr>
</thead>
<tbody>
  <tr ng-repeat="driver in driversList | filter: searchFilter">
    <td>{{$index + 1}}</td>
    <td>
      <img src="img/flags/{{driver.Driver.nationality}}.png" />
      <a href="#/drivers/{{driver.Driver.driverId}}">
        {{driver.Driver.givenName}}&nbsp;{{driver.Driver.familyName}}
      </a>
      </td>
    <td>{{driver.Constructors[0].name}}</td>
    <td>{{driver.points}}</td>
  </tr>
</tbody>
</table>

最後,讓咱們肯定下詳情頁面要展現什麼。一個總結了車手相關信息(好比生日、國籍)的頁面,同時包括一個最近成績的表格。在services.js里加入這些:

angular.module('F1FeederApp.services', [])
  .factory('ergastAPIservice', function($http) {

    var ergastAPI = {};

    ergastAPI.getDrivers = function() {
      return $http({
        method: 'JSONP', 
        url: 'http://ergast.com/api/f1/2013/driverStandings.json?callback=JSON_CALLBACK'
      });
    }

    ergastAPI.getDriverDetails = function(id) {
      return $http({
        method: 'JSONP', 
        url: 'http://ergast.com/api/f1/2013/drivers/'+ id +'/driverStandings.json?callback=JSON_CALLBACK'
      });
    }

    ergastAPI.getDriverRaces = function(id) {
      return $http({
        method: 'JSONP', 
        url: 'http://ergast.com/api/f1/2013/drivers/'+ id +'/results.json?callback=JSON_CALLBACK'
      });
    }

    return ergastAPI;
  });

此次咱們把車手的ID提供給服務,這樣咱們就能夠獲取特定車手的信息了。修改一下controllers.js

angular.module('F1FeederApp.controllers', []).

  /* Drivers controller */
  controller('driversController', function($scope, ergastAPIservice) {
    $scope.nameFilter = null;
    $scope.driversList = [];
    $scope.searchFilter = function (driver) {
        var re = new RegExp($scope.filterName, 'i');
        return !$scope.filterName || re.test(driver.Driver.givenName) || re.test(driver.Driver.familyName);
    };

    ergastAPIservice.getDrivers().success(function (response) {
        //Digging into the response to get the relevant data
        $scope.driversList = response.MRData.StandingsTable.StandingsLists[0].DriverStandings;
    });
  }).

  /* Driver controller */
  controller('driverController', function($scope, $routeParams, ergastAPIservice) {
    $scope.id = $routeParams.id;
    $scope.races = [];
    $scope.driver = null;

    ergastAPIservice.getDriverDetails($scope.id).success(function (response) {
        $scope.driver = response.MRData.StandingsTable.StandingsLists[0].DriverStandings[0]; 
    });

    ergastAPIservice.getDriverRaces($scope.id).success(function (response) {
        $scope.races = response.MRData.RaceTable.Races; 
    }); 
  });

值得注意的是咱們將$routeParams服務插入了車手控制器。這個服務容許咱們使用$routeParams.id訪問URL參數(好比:id)。

如今咱們已經有數據了,咱們只須要處理一下局部視圖了。建立一個partials/driver.html:

<section id="main">

  <nav id="secondary" class="main-nav">
    <div class="driver-picture">
      <div class="avatar">
        <img ng-show="driver" src="img/drivers/{{driver.Driver.driverId}}.png" />
        <img ng-show="driver" src="img/flags/{{driver.Driver.nationality}}.png" /><br/>
        {{driver.Driver.givenName}}<br/>{{driver.Driver.familyName}}
      </div>
    </div>
    <div class="driver-status">
      Country: {{driver.Driver.nationality}}   <br/>
      Team: {{driver.Constructors[0].name}}<br/>
      Birth: {{driver.Driver.dateOfBirth}}<br/>
      <a href="{{driver.Driver.url}}" target="_blank">Biography</a>
    </div>
  </nav>

<div class="main-content">
    <table class="result-table">
      <thead>
        <tr><th colspan="5">Formula 1 2013 Results</th></tr>
      </thead>
      <tbody>
        <tr>
          <td>Round</td> <td>Grand Prix</td> <td>Team</td> <td>Grid</td> <td>Race</td>
        </tr>
        <tr ng-repeat="race in races">
          <td>{{race.round}}</td>
          <td><img  src="img/flags/{{race.Circuit.Location.country}}.png" />{{race.raceName}}</td>
          <td>{{race.Results[0].Constructor.name}}</td>
          <td>{{race.Results[0].grid}}</td>
          <td>{{race.Results[0].position}}</td>
        </tr>
      </tbody>
    </table>
  </div>

</section>

注意此次咱們用上了ng-show。只有當你提供的表達式是true的時候,它纔會顯示HTML元素。在咱們的例子中,只有當控制器加載了車手對象後纔會顯示頭像。

完成

加上一些CSS以後,大體是這樣的效果:

請輸入圖片描述

如今你的應用已經能夠上線了。確認下路由能工做。你能夠在index.html加入一個靜態的菜單以增強導航。一切皆有可能。

結論

咱們已經介紹了開發一個簡單應用所需的一切。最後別忘了,Angular是一個很是強大的框架。咱們只是試了試水而已。之後有機會將向你們展現Angular區別於其餘前端MVC框架的特性:可測試性。咱們將評測使用Karma編寫和運行測試的過程,介紹持續集成的工具YeomenGruntBower,以及Angular的其餘優點。敬請期待。


原文 A Step-by-Step Guide to Your First AngularJS App

編譯 SegmentFault

相關文章
相關標籤/搜索