您的第一個 AngularJS 應用程序的分步教程

已發表: 2022-03-11
AngularJS 已經發展並變得更好。 現在稱為 Angular,它被完全重寫為一個新的開發工作流程。 查看我們新的 Angular 5 教程,甚至更新的 Angular 6 全棧教程,其中包含 Material 和 Firebase。

什麼是 AngularJS?

AngularJS 是由 Google 開發的 JavaScript MVC 框架,可讓您構建結構良好、易於測試和可維護的前端應用程序。

我為什麼要使用它?

如果你還沒有嘗試過 AngularJS,那你就錯過了。 該框架由一個緊密集成的工具集組成,它將幫助您以模塊化的方式構建結構良好、豐富的客戶端應用程序——使用更少的代碼和更大的靈活性。

AngularJS 通過提供向標記添加功能並允許您創建強大的動態模板的指令來擴展 HTML。 您還可以創建自己的指令,製作滿足您需求的可重用組件並抽像出所有 DOM 操作邏輯。

它還實現了雙向數據綁定,將您的 HTML(視圖)無縫連接到您的 JavaScript 對象(模型)。 簡單來說,這意味著模型上的任何更新都將立即反映在您的視圖中,而無需任何 DOM 操作或事件處理(例如,使用 jQuery)。

Angular 在 XHR 之上提供服務,極大地簡化了您的代碼,並允許您將 API 調用抽象為可重用的服務。 有了它,您可以將模型和業務邏輯移動到前端並構建後端不可知的 Web 應用程序。

最後,我喜歡 Angular,因為它在服務器通信方面的靈活性。 與大多數 JavaScript MVC 框架一樣,它允許您使用任何服務器端技術,只要它可以通過 RESTful Web API 為您的應用程序提供服務。 但是 Angular 還在 XHR之上提供了服務,這些服務極大地簡化了您的代碼,並允許您將 API 調用抽象為可重用的服務。 因此,您可以將模型和業務邏輯移至前端並構建後端不可知的 Web 應用程序。 在這篇文章中,我們將一步一步地做到這一點。

那麼,我從哪裡開始?

首先,讓我們決定我們要構建的應用程序的性質。 在本指南中,我們不希望在後端花費太多時間,因此我們將根據可在 Internet 上輕鬆獲得的數據編寫一些內容,例如體育動態應用程序!

由於我碰巧是賽車和一級方程式賽車的忠實粉絲,因此我將使用汽車運動 API 服務作為我們的後端。 幸運的是,Ergast 的人很友善地提供了一個免費的賽車 API,這對我們來說是完美的。

如需了解我們將要構建的內容,請查看現場演示。 為了美化演示並展示一些 Angular 模板,我應用了 WrapBootstrap 的 Bootstrap 主題,但鑑於本文不是關於 CSS 的,我將把它從示例中抽像出來並省略掉。

入門教程

讓我們用一些樣板來啟動我們的示例應用程序。 我推薦 angular-seed 項目,因為它不僅為您提供了一個很好的引導框架,而且還為使用 Karma 和 Jasmine 進行單元測試奠定了基礎(我們不會在這個演示中進行任何測試,所以我們只是暫時把這些東西放在一邊;有關設置項目以進行單元和端到端測試的更多信息,請參閱本教程的第 2 部分)。

編輯(2014 年 5 月):自從我編寫本教程以來,angular-seed 項目經歷了一些重大變化(包括將 Bower 添加為包管理器)。 如果您對如何部署項目有任何疑問,請快速查看其參考指南的第一部分。 在本教程的第 2 部分中,更詳細地介紹了 Bower 以及其他工具。

好的,現在我們已經克隆了存儲庫並安裝了依賴項,我們的應用程序的骨架將如下所示:

angularjs 教程 - 從骨架開始

現在我們可以開始編碼了。 當我們嘗試為賽車錦標賽構建體育提要時,讓我們從最相關的視圖開始:錦標賽表

冠軍錶

鑑於我們已經在我們的範圍內定義了一個驅動程序列表(掛我 - 我們會到達那裡),並忽略任何 CSS(為了可讀性),我們的 HTML 可能如下所示:

 <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>

在這個模板中你會注意到的第一件事是使用表達式(“{{”和“}}”)來返回變量值。 在 AngularJS 開發中,表達式允許您執行一些計算以返回所需的值。 一些有效的表達式是:

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

實際上,表達式是類似 JavaScript 的片段。 但是儘管非常強大,但您不應該使用表達式來實現任何更高級別的邏輯。 為此,我們使用指令。

了解基本指令

您會注意到的第二件事是ng-attributes的存在,這在典型的標記中是看不到的。 這些是指令。

在高層次上,指令是指示 AngularJS 將給定行為附加到 DOM 元素(或對其進行轉換、替換等)的標記(例如屬性、標籤和類名)。 讓我們來看看我們已經看過的那些:

  • ng-app指令負責引導您的應用程序定義其範圍。 在 AngularJS 中,您可以在同一頁面中擁有多個應用程序,因此該指令定義了每個不同應用程序的開始和結束位置。

  • ng-controller指令定義了哪個控制器將負責您的視圖。 在這種情況下,我們表示driversController ,它將提供我們的驅動程序列表( driversList )。

  • ng-repeat指令是最常用的指令之一,用於在遍歷集合時定義模板範圍。 在上面的示例中,它為driversList中的每個驅動程序複製了表中的一行。

添加控制器

當然,沒有控制器我們的視圖是沒有用的。 讓我們將driversController添加到我們的 controllers.js 中:

 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' ]);

通過這行代碼,我們實際上初始化了我們的應用程序並註冊了它所依賴的模塊。 稍後我們將回到該文件 ( app.js )。

現在,讓我們將所有內容放在index.html中:

 <!DOCTYPE html> <html> <head> <title>F-1 Feeder</title> </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> <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/services.js"></script> <script src="js/controllers.js"></script> </body> </html>

模數小錯誤,您現在可以啟動您的應用程序並檢查您的(靜態)驅動程序列表。

注意:如果您需要幫助調試您的應用程序並在瀏覽器中可視化您的模型和範圍,我建議您查看用於 Chrome 的很棒的 Batarang 插件。

從服務器加載數據

由於我們已經知道如何在視圖中顯示控制器的數據,是時候從 RESTful 服務器實際獲取實時數據了。

為了促進與 HTTP 服務器的通信,AngularJS 提供了$http$resource服務。 前者只是 XMLHttpRequest 或 JSONP 之上的一層,而後者提供了更高級別的抽象。 我們將使用$http

為了從控制器中抽像出我們的服務器 API 調用,讓我們創建我們自己的自定義服務,它將獲取我們的數​​據並通過將其添加到我們的services.js來充當$http的包裝器:

 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 ,在輸出數據之前, driversList數組必須通過nameFilter中存儲的值進行過濾。

此時,雙向數據綁定開始發揮作用:每次在搜索字段中輸入值時,Angular 都會立即確保我們與之關聯的$scope.nameFilter使用新值進行更新。 由於綁定是雙向的,當nameFilter值更新時,與之關聯的第二個指令(即ng-repeat )也獲取新值並且視圖會立即更新。

重新加載應用程序並查看搜索欄。

應用搜索欄

請注意,此過濾器將在模型的所有屬性(包括未使用的屬性)上查找關鍵字。 假設我們只想通過Driver.givenNameDriver.familyName進行過濾:首先,我們添加到driversController中,就在$scope.driversList = []; 線:

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

現在,回到index.html ,我們更新包含ng-repeat指令的行:

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

再次重新加載應用程序,現在我們可以按名稱搜索。

路線

我們的下一個目標是創建一個司機詳細信息頁面,讓我們點擊每個司機並查看他/她的職業詳細信息。

首先,讓我們包含$routeProvider服務(在app.js中),它將幫助我們處理這些不同的應用程序路由。 然後,我們將添加兩條這樣的路線:一條用于冠軍桌,另一條用於車手詳細信息。 這是我們的新app.js

 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> </head> <body ng-app="F1FeederApp"> <ng-view></ng-view> <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/services.js"></script> <script src="js/controllers.js"></script> </body> </html>

現在,每當我們瀏覽我們的應用程序路由時,Angular 都會加載關聯的視圖並將其呈現在<ng-view>標記的位置。 我們需要做的就是創建一個名為partials/drivers.html的文件並將我們的冠軍錶 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.nameFilter, 'i'); return !$scope.nameFilter || 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> <a href="./#/drivers"><- Back to drivers list</a> <nav 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}} {{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 (即既不是false也不是null ),該指令只會顯示 HTML 元素。 在這種情況下,只有當控制器將驅動程序對象加載到作用域中時,頭像才會顯示。

收尾工作

添加一堆 CSS 並渲染您的頁面。 你應該得到這樣的結果:

使用 CSS 渲染的頁面

您現在已準備好啟動您的應用程序並確保兩條路線都按預期工作。 您還可以將靜態菜單添加到index.html以改進用戶的導航功能。 可能性是無止境。

編輯(2014 年 5 月):我收到了很多關於我們在本教程中構建的代碼的可下載版本的請求。 因此,我決定在這裡發布它(剝離任何 CSS)。 但是,我真的建議下載它,因為本指南包含了您自己動手構建相同應用程序所需的每一個步驟,這將是一個更加有用和有效的學習練習。

結論

在本教程的這一點上,我們已經涵蓋了編寫簡單應用程序(如一級方程式餵食器)所需的一切。 現場演示中的每個剩餘頁面(例如,車隊冠軍錶、團隊詳細信息、日曆)都共享我們在此處查看過的相同基本結構和概念。

最後,請記住,Angular 是一個非常強大的框架,就它所提供的一切而言,我們幾乎沒有觸及到皮毛。 在本教程的第 2 部分中,我們將舉例說明為什麼 Angular 在其同行前端 MVC 框架中脫穎而出:可測試性。 我們將回顧使用 Karma 編寫和運行單元測試的過程,實現與 Yeomen、Grunt 和 Bower 的持續集成,以及這個出色的前端框架的其他優勢。