在本章中,我们将探讨将 Angular.js 与 Node.js Express 框架相结合时常见问题的解决方法。本章中使用的示例基于联系人应用程序来管理联系人列表。另外,我们使用 MongoDB 作为我们联系人的后端,因为它需要进一步定制,以便与 Angular 的$resource
服务结合使用。
您希望使用在您的 Express 应用程序中实现的 JSON REST API。
使用$resource
服务,我们将从定义我们的联系人模型和所有 RESTful 操作开始:
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 路线:
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
中引用它们,以保持其较小。应用编程接口实现首先初始化猫鼬库,并为我们的联系人模型定义一个模式:
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。让我们从索引操作开始:
exports.contacts = function(req, res) {
Contact.find({}, function(err, obj) {
res.json(obj)
});
};
跳过错误处理,我们用 Mongoose 提供的find
函数检索所有联系人,并以 JSON 格式呈现结果。show 动作非常相似,只是它使用 URL 参数中的findOne
和id
来检索单个联系人。
exports.contact = function(req, res) {
Contact.findOne({ _id: req.params.id }, function(err, obj) {
res.json(obj);
});
};
作为最后一个例子,我们将创建一个传入请求体的新的 Contact 实例,并调用save
方法来保持它:
exports.createContact = function(req, res) {
var contact = new Contact(req.body);
contact.save();
res.json(req.body);
};
你可以在 GitHub 上找到完整的例子。
让我们再来看看 contact 函数的例子,它检索一个 Contact。它使用_id
而不是id
作为findOne
函数的参数。这个下划线是有意的,被 MongoDB 用于其自动生成的标识。为了自动从id
映射到_id
参数,我们使用了$resource
服务的一个好技巧。看一下触点$resource
定义的第二个参数:{ id: "@_id" }
。使用该参数,Angular 将根据模型属性_id
的值自动设置 URL 参数id
。
您希望将客户端路由与快速后端结合使用。
对后端的每个请求最初都应该呈现完整的布局,以便加载我们的 Angular 应用程序。只有到那时,客户端渲染才会接管。让我们先来看看app.js
中这条“包罗万象”路线的路线定义:
var express = require('express'),
routes = require('./routes');
app.get('/', routes.index);
app.get('*', routes.index);
它使用通配符来捕获所有请求,以便通过routes.index
模块进行处理。此外,它还定义了使用同一模块的路线。该模块再次驻留在routes/index.js
中:
exports.index = function(req, res){
res.render('layout');
};
该实现仅呈现布局模板。它使用翡翠模板引擎:
!!!
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
中的客户端路由定义开始:
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" });
}
]
);
我们定义路由定义来列出、显示和编辑联系人,并使用一组分支和相应的控制器。为了正确加载这些部分,我们需要在后端添加另一条快速路由,为所有这些部分提供服务:
app.get('/partials/:name', function (req, res) {
var name = req.params.name;
res.render('partials/' + name);
});
它使用部分的名称作为 URL 参数,并使用来自partial
目录的给定名称呈现部分。请记住,您必须在捕获路线之前定义该路线,否则它将不起作用。
你可以在 GitHub 上找到完整的例子。
与 Rails 相比,通过为部分定义路由,对部分的处理是显式的。另一方面,能够为我们的部分使用 Jade 模板也是相当不错的。