Skip to content

Latest commit

 

History

History
546 lines (378 loc) · 29.5 KB

File metadata and controls

546 lines (378 loc) · 29.5 KB

九、前端基础知识

在前几章中,我们已经讨论了如何创建 web 服务器、web 服务和 API。我们还展示了如何在数据库中保存数据。现在我们将探索前端。在这一章中,我们将深入探讨单页应用(SPA)的概念,并使用 AngularJS 创建一个。我们的前端将与一个简单的 Express web 服务通信,该服务将我们的数据存储在一个 MongoDB 数据库中。

像往常一样,在我们开始这一旅程之前,我们将解释这一领域的所有重要概念,并为我们的技术选择提供理由,以便您对其基本原则有深刻的理解。

什么是 SPA?

在 SPA 中,应用的所有基本代码(HTML/CSS/JavaScript)都是在第一次向 web 服务器发出请求时预先加载的。一个常见的 SPA 示例是 Google 的 Gmail ( www.gmail.com)网站。

在传统网站中,当你从一个页面导航到另一个页面时,整个页面被重新加载,如图图 9-1 所示。

9781484201886_Fig09-01.jpg

图 9-1 。传统网站体验

这对于网站来说是一种不错的体验,但对于 web 应用来说却不是一种好的体验。在 SPA 中,一旦您请求一个 web 页面,服务器就会返回主模板(通常称为index.html)以及必要的客户端 JavaScript 和 CSS。一旦这个初始加载完成,用户与网站的交互所做的就是使用客户端 xhr(XMLHttpRequest)从服务器加载更多的数据。然后,这些数据通过 JavaScript 使用已经下载的 HTML/CSS 呈现在客户端上,如图 9-2 所示,让用户体验到更多的桌面应用体验。

9781484201886_Fig09-02.jpg

图 9-2 。单页面应用用户体验

你可以自己编写这些代码;然而,SPA 框架已经解决了一些技术难题(例如,如何将服务器返回的数据与 HTML 结合起来显示一个呈现的页面)。我们将使用这样一个 SPA 框架:AngularJS。

Image 注意XMLHttpRequest【XHR】是一个在所有现代浏览器中都可用的全局类,允许您使用 JavaScript 发出 HTTP 请求。名称是XMLHttpRequest,以确保所有的浏览器都遵循这个类的相同名称。这个名称中有 XML ,因为这是最初用于发出 HTTP 请求的数据格式,但是现在已经没有关系了,因为它可以用于发出任何格式的 HTTP 请求。其实现在大部分人只是用 JSON 格式。

为什么是安圭拉人?

有很多高质量的、社区驱动的单页应用框架,比如 AngularJS、EmberJS、ReactJS、KnockoutJS 和 BackboneJS,但是到目前为止,最大的社区兴趣是围绕 Google 创建的 AngularJS。这可以从图 9-3 中显示的这些框架的谷歌搜索趋势中看出。

9781484201886_Fig09-03.jpg

图 9-3 。各种 SPA/数据绑定框架的 Google 搜索趋势

它受欢迎的主要原因是它来自 Google 的一个团队,它很简单,而且功能丰富:

  • 数据绑定/模板化:允许您根据底层 JavaScript 对象的变化来更新 DOM(呈现的 HTML)
  • URL 处理/路由:处理浏览器的地址栏,根据需要加载和呈现模板,为用户提供流畅的导航体验
  • 依赖注入:给你清晰的指导来组织你的客户端 JavaScript,以增强团队的工作流程并简化可测试性

我们将在本章中使用 AngularJS 的特性,看看它是如何工作的,但现在你有几个理由对 AngularJS 感到兴奋。AngularJS 可以从https://angularjs.org/下载。

Twitter Bootstrap 简介

我们将使用 Twitter 引导来设计/样式我们的应用的前端。HTML/CSS 为你设计 UI 提供了基础。你可以从头开始设计任何你想要的东西,但是你最好还是使用别人已经为你创造的东西。Twitter Bootstrap 来自于twitter.com的一个设计师团队。可以从http://getbootstrap.com/下载 Bootstrap。

在其核心,Bootstrap 基本上是一堆 CSS 类,允许您快速定制呈现 HTML 的方式。例如,考虑一个只有一个按钮的简单 HTML 页面:

<button>I am a button</button>

默认情况下,在 Windows 上看起来如下所示:

9781484201886_unFig09-01.jpg

如果在 HTML 中添加了对引导 CSS 文件的引用,就可以访问许多 CSS 类来设计按钮的样式。这正是我们在清单 9-1 中所做的。

清单 9-1 。bs/bs.html

<!-- Add Bootstrap CSS -->
<link rel="stylesheet" type="text/css" href="./bootstrap/css/bootstrap.css">

<!-- Use Bootstrap CSS classes -->
<button class="btn btn-default">Default style button</button>
<button class="btn btn-primary">Primary style button</button>
<button class="btn btn-success">Success style button</button>
<button class="btn btn-danger">Danger style button</button>

每个平台上都有风格一致的按钮:

9781484201886_unFig09-02.jpg

Bootstrap 还附带了一些提供高级用户交互的 JavaScript 组件。Bootstrap JavaScript 依赖 JQuery ( http://jquery.com/download/)来提供一致的 DOM 操作 API。为了使用 JavaScript 组件,我们需要包含 JQuery 和引导 JavaScript 文件。清单 9-2 展示了如何使用引导工具提示。

清单 9-2 。操作系统/bsjs.html

<!-- Add JQuery + Bootstrap JS + CSS-->
<script src="./jquery/jquery.js"></script>
<script src="./bootstrap/js/bootstrap.js"></script>
<link rel="stylesheet" type="text/css" href="./bootstrap/css/bootstrap.css">

<!-- Use a button with a nice tooltip shown at the bottom -->
<button class="btn btn-default"
        data-toggle="tooltip" data-placement="bottom" title="Nice little tooltip message">
    Hover over me to see the tooltip
</button>

<!-- on page loaded initialize the tooltip plugin -->
<script>
$(function(){ // on document ready
    $('button').tooltip(); // add tooltip to all buttons
});
</script>

在这个代码示例中,有趣的是变量$的用法,它是由 JQuery 提供的,用于注册一个回调函数,一旦浏览器(on document ready)呈现了 HTML 文档,就会调用这个回调函数。然后在回调中,我们使用$通过$('button')选择所有按钮标签,然后调用 bootstrap 工具提示插件根据元素的属性(data-toggledata-placementtitle)对其进行初始化。如果您运行这个应用并将鼠标悬停在按钮上,您将看到一个包含 title 属性内容的漂亮工具提示:

9781484201886_unFig09-03.jpg

我们将使用 Bootstrap 来给我们的 UI 一个好看的外观。Bootstrap 也有一些整页的布局让你开始你的项目,你可以从http://getbootstrap.com/getting-started/下载。

Image 注意 JQuery 是目前最流行的 JavaScript 库。它提供了一致的 API 来跨所有浏览器访问文档对象模型(DOM)。DOM 基本上是浏览器提供的 API,用于使用 JavaScript 与呈现的 HTML 进行交互。由于不同的供应商引入了不同的特性来相互竞争,DOM API 传统上一直受到浏览器之间不一致的困扰。JQuery 处理了这些不一致性,提供了一个统一的 API 和增值特性,比如一个非常棒的 DOM 查询 API(类似于 CSS 选择器)。

建立一个简单的 AngularJS 应用

制作我们的单页应用的第一步是创建一个 Express 服务器来服务客户端 JavaScript HTML 和 CSS,如清单 9-3 所示。这是一个用我们已有的知识完成的琐碎任务(第 7 章)。

清单 9-3 。angularstart/app.js

var express = require('express');

var app = express()
    .use(express.static(__dirname + '/public'))
    .listen(3000);

这提供来自公共文件夹的 HTML。现在我们将在我们的公共文件夹中创建一个供应商文件夹来包含我们的 JQuery、AngularJS 和 Bootstrap 文件,如清单 9-4 所示。最后,我们有一个简单的index.html文件。

清单 9-4 。angularstart/public/index.html

<html ng-app="demo">
<head>
    <title>Sample App</title>

    <!-- Add JQuery + Bootstrap JS / CSS + AngularJS-->
    <script src="./vendor/jquery/jquery.js"></script>
    <script src="./vendor/bootstrap/js/bootstrap.js"></script>
    <link rel="stylesheet" type="text/css" href="./vendor/bootstrap/css/bootstrap.css">
    <script src="./vendor/angular/angular.js"></script>
    <script src="./vendor/angular/angular-route.js"></script>

    <!-- Our Script -->
    <script>
        var demoApp = angular.module('demo', []);
        demoApp.controller('MainController', ['$scope', function ($scope) {
            $scope.vm = {
                name: "foo",
                clearName: function () {
                    this.name = ""
                }
            };
        }]);
    </script>
</head>
<body ng-controller="MainController">
    <!-- Our HTML -->
    <label>Type your name:</label>
    <input type="text" ng-model="vm.name" />
    <button class="btn btn-danger" ng-click="vm.clearName()">Clear Name</button>
</body>
</html>

突出显示了该文件的重要部分。为了简单起见,我们将把整个客户端脚本放在一个位置。(我们将在未来对此使用简单的单个脚本标记,而不是将其内联,如此处所示)。此外,我们的整个 HTML 将位于body标签中的一个位置。我们将在进行过程中充实这些部分。如果您现在运行这个 Express 服务器并访问http://localhost:3000,您将看到一个简单的 AngularJS 应用。如果在输入框中输入你的名字,其下方的 div 会实时更新,如图图 9-4 所示。此外,您也可以按“清除姓名”按钮来清除姓名。

9781484201886_Fig09-04.jpg

图 9-4 。在浏览器中运行的 Angularstart 示例

现在让我们进一步检查我们的 HTML 页面。重要的部分如下:

  • 在我们的 HTML ng-app/ng-controller/ng-model里面有棱角的指令T3。
  • 主角 模块在我们的 JavaScript 中创建,名为demo。也在ng-app指令中使用,它将 JS 模块粘合到 HTML。
  • 主角度控制器在我们 JS 里叫Main Controllerng-controller指令将 JS 控制器粘合到 HTML 上。
  • $scope 通过角度注入控制器。范围是 HTML 和控制器之间的双向数据绑定粘合剂。

现在你已经对一个简单而实用的 Angular 应用有了一个大致的了解,让我们看看 AngularJS 中的模块、指令、控制器和作用域是什么意思。

AngularJS 中的模块

AngularJS 中的模块允许您包含和管理所有的控制器、指令等等。您可以使用ng-app指令关联一个特定的模块来管理 HTML 的一部分(这就是我们所做的)。我们还使用该模块将MainController注册到 Angular ( demoApp.controller)。模块只是一个便于管理的容器。

角度中的指令

指令基本上是当 Angular 在 HTML 中找到匹配的字符串时,您希望 Angular 执行(以提供行为)的代码段。例如,在我们的应用中,我们要求 Angular 使用我们的html标签(<html ng-app="demo">))上的ng-app HTML 属性来运行ng-app指令。类似地,我们触发了ng-controllerng-model指令。

带有前缀的命名空间指令是一种惯例。Angular 附带的指令使用ng-前缀。使得在 HTML 中更容易观察到这个标签上有一个指令。如果初学者看到ng-app,他或她会得到一个提示,这是一个角度指令,一些自定义行为将被应用。

创建你自己的指令和严肃的 AngularJS 应用开发的组成部分是非常容易的。但是现在我们将坚持 Angular 附带的指令,它可以带你走很长的路。

控制器和$scope

控制器是 AngularJS 的心脏和灵魂。这些是双向数据绑定的一半。我们所看到的其他一切(模块、指令)都可以被认为是控制器之旅。

控制器之所以称为控制器,是因为模型-视图-控制器(MVC) 模式。在 MVC 模式中,控制器负责保持视图和模型的同步。在 Angular 中,视图和模型之间的同步是由 Angular 使用双向数据绑定来完成的。两者(视图和模型)之间的粘合剂是角度$scope,它被传递给控制器。控制器基于我们的应用逻辑设置$scope。视图和模型之间的$scope同步如图图 9-5 所示。

9781484201886_Fig09-05.jpg

图 9-5 。演示$scope 是视图和模型之间的粘合剂

由于控制器中的这个模型实际上是用于视图的,所以通常称之为 ViewModel **,**或者简称为vm,正如我们在示例中所称的那样。还要注意的是,$scope通过 Angular 注入控制器。我们通过在数组成员中指定来明确要求$scope。相关片段再次显示:

demoApp.controller('MainController', ['$scope', function ($scope) {

初始数组成员(本例中只有'$scope')驱动作为参数传递给最终数组成员的内容,这是我们的控制器功能。这是 Angular 支持的依赖注入的一种形式。我们将在本章的其他例子中看到更多 Angular 的依赖注入。

我们使用 HTML using 指令中的$scope。在我们的例子中,下面的 HTML 的ng-model保持输入元素与vm.name属性同步:

<input type="text" ng-model="vm.name" />

类似地,我们可以在用户点击时使用ng-click指令调用控制器上的函数:

<button class="btn btn-danger" ng-click="vm.clearName()">Clear Name</button>

创建一个简单的待办事项列表应用

Angular 的伟大之处在于,创建和设计完全独立于任何服务器代码的前端极其简单。准备就绪后,您可以将其连接到后端,这正是我们在这里要做的。现在我们已经基本了解了$scope是视图和模型之间的粘合剂,我们将为待办事项列表设计一个简单的 JavaScript 模型,并为它设计一个前端。我们的整个模型(vm ) JavaScript 如清单 9-5 所示。

清单 9-5 。todostart/public/main.js

var demoApp = angular.module('demo', []);
demoApp.controller('MainController', ['$scope', 'guidService', function ($scope, guidService) {

    // Setup a view model
    var vm = {};

    vm.list = [
        { _id: guidService.createGuid(), details: 'Demo First Item' },
        { _id: guidService.createGuid(), details: 'Demo Second Item' }
    ];

    vm.addItem = function () {
        // TODO: send to server then,
        vm.list.push({
            _id: guidService.createGuid(),
            details: vm.newItemDetails
        });
        vm.newItemDetails = '';
    };

    vm.removeItem = function (itemToRemove) {
        // TODO: delete from the server then
        vm.list = vm.list.filter(function (item) { return item._id !== itemToRemove._id; });
    };

    // For new items:
    vm.newItemDetails = '';

    // expose the vm using the $scope
    $scope.vm = vm;
}]);

demoApp.service('guidService', function () {
    return {
        createGuid: function () {
            return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
                var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
                return v.toString(16);
            });
        }
    };
});

在第 8 章的中,我们展示了为数据库中的每个条目创建一个不可变的唯一 id 总是最好的(自然主键与代理主键的讨论)。这里我们使用一个 createGuid函数在客户端创建这样一个惟一的 id。这个函数本来可以放在控制器内部,但是我们选择将其作为一个可重用的角度服务,称为 guidService。Angular 服务只是 Angular 调用一次来获取对象的函数。然后,它将这个对象传递给通过键(这里的键是'guidService')请求它的任何其他服务、控制器等等。

在我们的控制器中,我们将'guidService'指定为依赖注入数组的成员:

demoApp.controller('MainController', ['$scope', 'guidService', function ($scope, guidService) {

Angular 将寻找并传递从我们的guidService服务注册函数返回的值。这里的值只有一个成员(函数 T1),我们在控制器(guidService.createGuid)中使用。createGuid函数本身是一个非常标准的函数,使用随机化算法从 JavaScript 创建全局唯一标识符(GUIDs)。

创建和消费角度服务之后,我们可以看到 MainController函数本身的其余部分非常简单。它用于管理项目列表(vm.list)—添加到列表(vm.addItem)和删除列表(vm.removeItem)—以及一个简单的成员,允许我们从视图中的数据绑定字段获取用户输入的详细信息(vm.newItemDetails)。现在我们可以在此基础上设计一个简单的 Angular + Bootstrap HTML 用户界面。我们的 HTML 如清单 9-6 所示。

清单 9-6 。todostart/public/index.html

<body ng-controller="MainController">
    <!-- Our HTML -->
    <div class="container">
        <h1>List</h1>

        <!-- Existing items rows -->
        <div class="row">
            <div ng-repeat="item in vm.list track by item._id" style="padding:10px">
                <button class="btn btn-danger" ng-click="vm.removeItem(item)">x</button>
                {{item.details}}
            </div>
        </div>

        <!-- New Item row -->
        <div class="row">
            <form role="form">
                <div class="form-group">
                    <label for="newItemDetails">New Item Details:</label>
                    <input type="text" class="form-control"
                           placeholder="Details of new todo item"
                           ng-model="vm.newItemDetails">
                </div>
                <button type="submit" class="btn btn-primary"
                        ng-click="vm.addItem()"
                        ng-disabled="!vm.newItemDetails">Add</button>
            </form>
        </div>
    </div>
</body>

我们有一堆 HTML 标签,它们有特定的引导类(例如,containerrowform-groupbtnbtn-primarybtn-danger等等)。这些给了应用一个体面的外观——设计者会定制这些类(并使用 CSS 创建更多的类),使应用看起来更好,但即使在当前状态下,它也不坏。(参见图 9-6 。)

9781484201886_Fig09-06.jpg

图 9-6 。todostart 示例在浏览器中运行

更让人印象深刻的是功能齐全!单击 x 按钮从列表中删除该项目。通过输入一些新的条目细节,我们启用 Add 按钮,按下该按钮将条目添加到列表中。所有这些都要感谢 Angular 附带的指令,这些指令与您已经看到的视图模型(vm)进行对话。HTML 中的条目列表是用一个ng-repeat指令生成的,再次显示在清单 9-7 中。

清单 9-7 。来自 todostart/public/index.html 的片段

<div ng-repeat="item in vm.list track by item._id" style="padding:10px">
     <button class="btn btn-danger" ng-click="vm.removeItem(item)">x</button>
     {{item.details}}
</div>

ng-repeat指令获取指定的 DOM 元素,并为列表中的每个元素克隆它(vm.list)。作为一种优化,我们告诉它条目的惟一性是由_id属性(track by)决定的,这有助于 Angular 将 DOM 元素与列表中的条目关联起来。ng-repeat指令还在重复元素(我们称之为item)内的作用域(item in vm.list)中创建了一个新项,您可以进一步绑定到({{item.details}})并在其他指令中使用它(例如,我们有一个ng-click,通过传递给vm.removeItem函数来移除该项)。现在让我们检查一下在清单 9-8 中再次显示的添加项目 HTML 。

清单 9-8 。来自 todostart/public/index.html 的片段

<form role="form">
    <div class="form-group">
        <label for="newItemDetails">New Item Details:</label>
        <input type="text" class="form-control"
               placeholder="Details of new todo item"
               ng-model="vm.newItemDetails">
    </div>
         <button type="submit" class="btn btn-primary"
                 ng-click="vm.addItem()"
                 ng-disabled="!vm.newItemDetails">Add</button>
</form>

我们使用一个ng-model将简单输入连接到vm.newItemDetails,使用一个ng-click指令将添加按钮连接到vm.addItem功能。如果当前的vm.newItemDetails是 falsy(记住空字符串在 JavaScript 中是 falsy),我们还使用ng-disabled指令禁用添加按钮。

就这样!我们在客户端上有一个功能齐全的待办事项列表。现在,它需要做的就是与服务器通信,以便保存和加载信息。

创建 REST API

当我们详细研究 ExpressJS 时,我们已经有了从第 7 章创建 REST API 的经验。对于这个简单的应用,我们的 REST API 需要做的只是获取列表中的所有项目,向列表中添加项目的 POST(它应该返回 ID),以及从列表中删除项目的 DELETE。在第 8 章中,我们看到了如何使用 MongoDB。结合我们对这两者的了解,清单 9-9 为我们的基于 ExpressJS 路由的 API 提供了一个简单的设置,它将数据持久化到 MongoDB。

清单 9-9 。todocomplete/app.js

var express = require('express');
var bodyParser = require('body-parser');

// The express app
var app = express();

// Create a mongodb connection
// and only start express listening once the connection is okay
var MongoClient = require('mongodb').MongoClient;
var db, itemsCollection;
MongoClient.connect('mongodb://127.0.0.1:27017/demo', function (err, database) {
    if (err) throw err;

    // Connected!
    db = database;
    itemsCollection = db.collection('items');

    app.listen(3000);
    console.log('Listening on port 3000');
});

// Create a router that can accept JSON
var router = express.Router();
router.use(bodyParser.json());

// Setup the collection routes
router.route('/')
      .get(function (req, res, next) {
          itemsCollection.find().toArray(function (err, docs) {
              res.send({
                  status: 'Items found',
                  items: docs
              });
          });
      })
      .post(function (req, res, next) {
          var item = req.body;
          itemsCollection.insert(item, function (err, docs) {
              res.send({
                  status: 'Item added',
                  itemId: item._id
              });
          });
      })

// Setup the item routes
router.route('/:id')
      .delete(function (req, res, next) {
          var id = req.params['id'];
          var lookup = { _id: new mongodb.ObjectID(id) };
          itemsCollection.remove(lookup, function (err, results) {
              res.send({ status: 'Item cleared' });
          });
      });

app.use(express.static(__dirname + '/public'))
   .use('/todo', router);

将 MongoDB 与 Express 集成的重要部分是,我们只有在确认与 MongoDB 的连接正常后,才启动 Express 服务器。我们还存储了对包含待办事项的items集合的引用。

代码的其余部分是不言自明的,这里没有什么是你不知道的。我们有用于 GET(获取列表)和 POST(向列表中添加一个项目并返回其 ID)的集合级路由,以及用于删除单个项目的项目路由。此时,你可以使用curl来测试你的 API,就像我们在第 7 章中所做的那样。现在让我们完成我们的前端,以便它与后端对话。

用 REST API 连接前端

与 Angular 的休息服务中心交谈再简单不过了。Angular 附带了一个$http服务,它包装了浏览器的XMLHttpRequest对象,以便与 Angular digest 循环一起工作。它还使 API 在不同浏览器之间保持一致,并通过使用承诺使其更容易使用。承诺是我们将在下一章详细讨论的主题,但是在你看过代码之后,我们将在这里给出一个简要的概述。

您可以访问$http服务,就像您访问我们自己的自定义服务guidService一样,我们在前面已经看到了。为了访问 REST API,我们将创建自己的定制 Angular 服务,该服务将使用 Angular 的内置$http服务与服务器通信。包括控制器在内的完整客户端 JavaScript 如清单 9-10 所示。

清单 9-10 。todocomplete/public/main.js

var demoApp = angular.module('demo', []);
demoApp.controller('MainController', ['$scope', 'todoWebService', function ($scope, todoWebService) {

    // Setup a view model
    var vm = {};

    vm.list = [];

    // Start the initial load of lists
    todoWebService.getItems().then(function (response) {
        vm.list = response.data.items;
    });

    vm.addItem = function () {
        var item = {
            details: vm.newItemDetails
        };

        // Clear it from the UI
        vm.newItemDetails = '';

        // Send the request to the server and add the item once done
        todoWebService.addItem(item).then(function (response) {
            vm.list.push({
                _id: response.data.itemId,
                details: item.details
            });
        });
    };

    vm.removeItem = function (itemToRemove) {
        // Remove it from the list and send the server request
        vm.list = vm.list.filter(function (item) { return item._id !== itemToRemove._id; });
        todoWebService.removeItem(itemToRemove);
    };

    // For new items:
    vm.newItemDetails = '';

    // expose the vm using the $scope
    $scope.vm = vm;
}]);

demoApp.service('todoWebService', ['$http', function ($http) {
    var root = '/todo';
    return {
        getItems: function () {
            return $http.get(root);
        },
        addItem: function (item) {
            return $http.post(root, item);
        },
        removeItem: function (item) {
            return $http.delete(root + '/' + item._id);
        }
    }
}]);

同样,代码实际上非常容易管理。为了简单起见,我们放弃了任何错误检查或 UI 通知。首先,请注意我们名为todoWebService 的定制角度服务。这里面的逻辑是不言自明的。它只有一些获取、添加和删除项目的函数。它使用 Angular 的$http服务来针对我们的 REST API 端点(换句话说,'/todo ')发出 get、post 和 delete HTTP 请求,这些请求位于为我们的 HTML 提供服务的同一台服务器上。值得一提的是,$http的每个方法都返回一个承诺,因此getItems/addItem/removeItem也同样返回承诺。

我们在我们的MainController中使用我们的todoWebService,自从我们最后一次看到它以来,它基本上没有变化。唯一改变的是,它现在使用todoWebService在正确的时间调用服务器。我们提到了todoWebService成员的回报承诺。对于这个应用来说,知道一个承诺有一个then成员函数就足够了,一旦承诺被解析,这个函数就会被调用。现在有一种方法来考虑它们:不是直接传递回调,而是将它传递给 promise 的then成员函数。例如,考虑清单 9-11 中重复的初始载荷。

清单 9-11 。来自 todocomplete/public/main.js 的片段

// Start the initial load of lists
todoWebService.getItems().then(function (response) {
    vm.list = response.data.items;
});

当浏览器发送这个获取列表的网络请求时,它不会阻塞 UI/JavaScript 线程。相反,它需要一个回调函数,一旦从服务器收到 get 响应,就会调用这个回调函数。承诺只是提供了一种更简洁的方式来提供回调。承诺的主要动机是承诺提供的链能力和更好的错误处理,这个话题我们将在下一章详细讨论。

仅此而已。我们已经完成了一个端到端的待办事项列表应用。如果您运行了 MongoDB】,启动 Node.js 服务器并访问您的本地主机。(参见图 9-7 。)

9781484201886_Fig09-07.jpg

图 9-7 。在浏览器中运行的 todocomplete 示例

现在让我们后退一步,看看我们的应用架构。我们可以很容易地在分布式团队中开发这样的应用。前端的 JavaScript 大师可以创建你的控制器,CSS 忍者可以设计你的 HTML,后端的 JavaScript 专家可以创作你的 REST API。最后,我们将它们连接起来,您的 shinny 应用就准备好了。这是使用良好的 SPA 框架(如 AngularJS)结合 REST API 的优势之一。

后续步骤

关于 AngularJS 还有很多可以说的。例如,我们只使用了内置指令,比如ng-click,但是您可以编写自己的指令来创建强大的 web 组件。此外,我们只看到了依赖注入(DI)在 Angular 中的基本用法。在 Angular 中 DI 的主要动机是可测试性。要了解更多关于可测试性的知识,最好查看 AngularJS 团队https://github.com/angular/angular-seed提供的 angular-seed 项目。angular-seed 项目还包含关于如何将客户端项目分割成多个 JavaScript 文件以实现可维护性的指导。

额外资源

推特自举:http://getbootstrap.com/

安圭拉语:T0

有角的种子:https://github.com/angular/angular-seed

摘要

在这一章中,我们看到了如何使用成熟的框架如 AngularJS 来消费 web 服务。我们努力弄清楚代码的作用。我们专注于引导您通过代码,以便您确切地知道发生了什么。这将允许你探索更大的代码库,有更深的理解和更大的信心。

在本章中,我们也试图证明我们使用库的合理性。一路上,我们解释了 SPA 的含义以及您应该关注的原因。在本章的介绍之后,你应该有信心自己探索更多的 JQuery、Bootstrap 和 AngularJS。在下一章,我们将看看承诺和其他简化回调的方法。