你的第一个AngularJS应用的分步教程

本文概述

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应用程序。在这篇文章中, 我们将一次完成一个步骤。

那么, 我从哪里开始呢?

首先, 让我们决定要构建的应用的性质。在本指南中, 我们不希望在后端花费太多时间, 因此, 我们将根据互联网上容易获得的数据(例如体育Feed应用程序)编写一些内容!

由于我是赛车和一级方程式赛车的忠实拥护者, 因此我将使用自动运动API服务作为我们的后端。幸运的是, Ergast的家伙很友好, 可以提供免费的赛车API, 对我们来说是完美的。

要初步了解我们要构建的内容, 请观看现场演示。为了美化该演示并展示一些Angular模板, 我应用了WrapBootstrap中的Bootstrap主题, 但是由于本文与CSS无关, 因此我将其从示例中抽象出来并省略。

入门教程

让我们从一些样板开始我们的示例应用程序。我推荐有角度的种子项目, 因为它不仅为你提供了一个很好的引导框架, 而且还为使用Karma和Jasmine进行单元测试奠定了基础(我们将不在此演示中进行任何测试, 因此我们将现在将这些东西放在一边;有关为单元和端到端测试设置项目的更多信息, 请参见本教程的第2部分。

编辑(2014年5月):自从我编写本教程以来, 角度种子项目经历了一些重大更改(包括添加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 |日期}}
  • {{ user.name }}

实际上, 表达式是类似于JavaScript的代码段。但是, 尽管功能非常强大, 但你不应使用表达式来实现任何更高级别的逻辑。为此, 我们使用指令。

了解基本指令

你会注意到的第二件事是ng属性的存在, 在典型的标记中看不到。这些是指令。

在高层次上, 指令是标记(例如属性, 标记和类名称), 它们告诉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, 在输出数据之前, 必须使用存储在nameFilter中的值来过滤driversList数组。

此时, 将进行双向数据绑定:每次在搜索字段中输入值时, Angular都会立即确保使用新值更新与之关联的$ scope.nameFilter。由于绑定以双向方式起作用, 因此在nameFilter值更新时, 与其关联的第二个指令(即ng-repeat)也将获得新值, 并且视图将立即更新。

重新加载应用程序并签出搜索栏。

应用搜索栏

请注意, 此过滤器将在模型的所有属性(包括我们未使用的属性)上寻找关键字。假设我们只想按Driver.givenName和Driver.familyName进行过滤:首先, 我们在$ scope.driversList = []下添加到driversController。线:

$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 id="main">
  <a href="./#/drivers"><- Back to drivers list</a>
  <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}} {{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)。但是, 我真的不建议下载它, 因为本指南包含你用自己的双手来构建同一应用程序所需的每一步, 这将是一个更加有用和有效的学习练习。

总结

在本教程的这一点上, 我们已经介绍了编写一个简单应用程序(例如Formula 1 Feeder)所需的一切。实时演示中的其余每个页面(例如, 构造函数总冠军表, 团队详细信息, 日历)都具有我们在此介绍过的相同基本结构和概念。

最后, 请记住, Angular是一个非常强大的框架, 我们几乎没有涉及到它提供的所有功能。在本教程的第2部分中, 我们将举例说明Angular为什么在其同类前端MVC框架中脱颖而出:可测试性。我们将回顾使用Karma编写和运行单元测试的过程, 以及如何与Yeomen, Grunt和Bower进行持续集成, 以及该出色的前端框架的其他优势。

微信公众号
手机浏览(小程序)
0
分享到:
没有账号? 忘记密码?