# 十、Node Express 后端集成

在本章中，我们将探讨将 Angular.js 与 Node.js [Express](http://expressjs.com/) 框架相结合时常见问题的解决方法。本章中使用的示例基于联系人应用程序来管理联系人列表。另外，我们使用 MongoDB 作为我们联系人的后端，因为它需要进一步定制，以便与 Angular 的`$resource`服务结合使用。

## 使用休息原料药

### 问题

您希望使用在您的 Express 应用程序中实现的 JSON REST API。

### 溶液

使用`$resource`服务，我们将从定义我们的联系人模型和所有 RESTful 操作开始:

```js
    app.factory("Contact", function($resource) {
    return $resource("/api/contacts/:id", { id: "@_id" },
    {
    'create': { method: 'POST' },
    'index': { method: 'GET', isArray: true },
    'show': { method: 'GET', isArray: false },
    'update': { method: 'PUT' },
    'destroy': { method: 'DELETE' }
    }
    );
    });

```

我们现在可以使用`Contact.index()`获取联系人列表，使用`Contact.show(id)`获取单个联系人。这些动作可以直接映射到`app.js`中定义的 API 路线:

```js
    var express = require('express'),
    api = require('./routes/api');

    var app = module.exports = express();

    app.get('/api/contacts', api.contacts);
    app.get('/api/contacts/:id', api.contact);
    app.post('/api/contacts', api.createContact);
    app.put('/api/contacts/:id', api.updateContact);
    app.delete('/api/contacts/:id', api.destroyContact);

```

我喜欢将路线保存在一个单独的文件(`routes/api.js)`中，并在`app.js`中引用它们，以保持其较小。应用编程接口实现首先初始化[猫鼬](http://mongoosejs.com/)库，并为我们的联系人模型定义一个模式:

```js
    var mongoose = require('mongoose');
    mongoose.connect('mongodb://localhost/contacts_database');

    var contactSchema = mongoose.Schema({
    firstname: 'string', lastname: 'string', age: 'number'
    });
    var Contact = mongoose.model('Contact', contactSchema);

```

我们现在可以使用`Contact`模型来实现 API。让我们从索引操作开始:

```js
    exports.contacts = function(req, res) {
    Contact.find({}, function(err, obj) {
    res.json(obj)
    });
    };

```

跳过错误处理，我们用 Mongoose 提供的`find`函数检索所有联系人，并以 JSON 格式呈现结果。show 动作非常相似，只是它使用 URL 参数中的`findOne`和`id`来检索单个联系人。

```js
    exports.contact = function(req, res) {
    Contact.findOne({ _id: req.params.id }, function(err, obj) {
    res.json(obj);
    });
    };

```

作为最后一个例子，我们将创建一个传入请求体的新的 Contact 实例，并调用`save`方法来保持它:

```js
    exports.createContact = function(req, res) {
    var contact = new Contact(req.body);
    contact.save();
    res.json(req.body);
    };

```

你可以在 [GitHub](https://github.com/fdietz/recipes-with-angular-js-examples/tree/master/chapter10/recipe1) 上找到完整的例子。

### 讨论

让我们再来看看 contact 函数的例子，它检索一个 Contact。它使用`_id`而不是`id`作为`findOne`函数的参数。这个下划线是有意的，被 MongoDB 用于其自动生成的标识。为了自动从`id`映射到`_id`参数，我们使用了`$resource`服务的一个好技巧。看一下触点`$resource`定义的第二个参数:`{ id: "@_id" }`。使用该参数，Angular 将根据模型属性`_id`的值自动设置 URL 参数`id`。

## 实现客户端路由

### 问题

您希望将客户端路由与快速后端结合使用。

### 溶液

对后端的每个请求最初都应该呈现完整的布局，以便加载我们的 Angular 应用程序。只有到那时，客户端渲染才会接管。让我们先来看看`app.js`中这条“包罗万象”路线的路线定义:

```js
    var express = require('express'),
    routes = require('./routes');

    app.get('/', routes.index);
    app.get('*', routes.index);

```

它使用通配符来捕获所有请求，以便通过`routes.index`模块进行处理。此外，它还定义了使用同一模块的路线。该模块再次驻留在`routes/index.js`中:

```js
    exports.index = function(req, res){
    res.render('layout');
    };

```

该实现仅呈现布局模板。它使用[翡翠](http://jade-lang.com/)模板引擎:

```js
    !!!
    html(ng-app="myApp")
    head
    meta(charset='utf8')
    title Angular Express Seed App
    link(rel='stylesheet', href='/css/bootstrap.css')
    body
    div
    ng-view

    script(src='js/lib/angular/angular.js')
    script(src='js/lib/angular/angular-resource.js')
    script(src='js/app.js')
    script(src='js/services.js')
    script(src='js/controllers.js')

```

现在我们可以实际呈现初始布局，我们可以从`app.js`中的客户端路由定义开始:

```js
    var app = angular.module('myApp', ["ngResource"]).
    config(['$routeProvider', '$locationProvider',
    function($routeProvider, $locationProvider) {
    $locationProvider.html5Mode(true);
    $routeProvider
    .when("/contacts", {
    templateUrl: "partials/index.jade",
    controller: "ContactsIndexCtrl" })
    .when("/contacts/new", {
    templateUrl: "partials/edit.jade",
    controller: "ContactsEditCtrl" })
    .when("/contacts/:id", {
    templateUrl: "partials/show.jade",
    controller: "ContactsShowCtrl" })
    .when("/contacts/:id/edit", {
    templateUrl: "partials/edit.jade",
    controller: "ContactsEditCtrl" })
    .otherwise({ redirectTo: "/contacts" });
    }
    ]
    );

```

我们定义路由定义来列出、显示和编辑联系人，并使用一组分支和相应的控制器。为了正确加载这些部分，我们需要在后端添加另一条快速路由，为所有这些部分提供服务:

```js
    app.get('/partials/:name', function (req, res) {
    var name = req.params.name;
    res.render('partials/' + name);
    });

```

它使用部分的名称作为 URL 参数，并使用来自`partial`目录的给定名称呈现部分。请记住，您必须在捕获路线之前定义该路线，否则它将不起作用。

你可以在 [GitHub](https://github.com/fdietz/recipes-with-angular-js-examples/tree/master/chapter10/recipe1) 上找到完整的例子。

### 讨论

与 Rails 相比，通过为部分定义路由，对部分的处理是显式的。另一方面，能够为我们的部分使用 Jade 模板也是相当不错的。