本篇目的
在首页显示数据库内所有的用户,方法多式多样,可以从前端到后端,反之亦可。我们现在就参照以下步骤实现吧:
现在开始创建数据库
这儿我们需要用到一个免费的可视化工具,Just like Navicat For MysqL 这样的东西。下载传送门:Robomongo,安装好以后继续下面的步骤:
确保 MongoDB 服务启动后运行 Robomongo
填写完毕后点一下左下角的 Test,得到两个小绿勾说明连接测试成功,可以点 Save 了。如果失败,说明服务有异常,赶紧 Google 一下或留言。
对着左上角的连接点击右键,Create Database,然后输入数据库名,就叫“ForUsers”吧
接着双击“ForUsers”再右键 Collections,选择 Create Collection,创建一个
Json
集合,这和关系型数据库里的“表”类似,表明我们就叫users
吧。结果如图:
最后我们往这个表里插入一些用户数据,右键
users
,选择Insert Document
,弹出一个 Json 编辑器,想想一个用户一般包括哪些信息呢?姓名、年龄、性别,好,就这三个,参考如下。可以复制进去点击 Validate 进行验证,格式无误,会提示你“JSON is valid!”表明正确,此时可以点击Save
。
{
"name": "小明","age": 20,"gender": "男" }
{
"name": "小强","age": 22,"gender": "男" }
{
"name": "小张","age": 21,"gender": "女" }
- 现在双击
users
,就能在右边看到刚刚插入的数据了,上面的db.getCollection('users').find({})
为查询语句,和select * from users;
类似。你会发现每个用户里都多出了一个_id
字段,这是 MongoDB 自动添加的。现在我们数据库创建好了,开始干正事了!
通过 Express 创建一个 API
在这个步骤中,你将学习到关于 Node 模块系统,Express 路由和用 Monk 从 MongoDB 中获取数据。
我们先在 VSCode 中打开 ForUsers 项目文件夹,然后在根目录打开 app.js,文件中第 7 行前的第一部分包含了几个 require 函数调用,require 是 Node 中的内置方法,主要用来引用其它文件中定义的模块:
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
第 8、9 行代码引入了整个框架的路由模块,1 个路由模块定义了 1 个或多个关联的端点以及对应的处理器。Express Generator 生成的框架中,默认包含两个路由:index 和 users
var index = require('./routes/index');
var users = require('./routes/users');
让我们来看一看 index 这个路由,routes->index.js,内容如下:
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/',function(req,res,next) {
res.render('index',{ title: 'Express' });
});
module.exports = router;
在第 1 行,我们在当前模块引用了 express,当使用 require 方法时,依赖于目标模块是怎样实现的,require 方法可能返回 1 个方法或者对象,类似于面向对象编程里的 new 一个对象。在这个例子中,这个 express 变量就是 1 个对象,它提供了一个叫做 Router 的方法,我们在第 2 行就调用了这个方法,用来访问 express 中的路由对象。我们用 1 个路由来定义我们应用中的端点,我们在这些端点中接收请求,每个端点会被关联到一个路由处理程序,处理程序负责处理端点中接收的请求。
现在看看下一行中路由配置的示例:
router.get('/',{ title: 'Express' });
});
我们使用路由里的 get 方法来定义 1 个路由和他的处理程序。第 1 个参数就是这个端点;在这里,“/”代表网站的根路径或者主页。第 2 个参数就是路由的处理程序。
在 Express 中,所有的路由处理程序都具有相同的签名。第 1 个参数是请求对象,第 2 个是响应,第 3 个是当前引用链中的下一个处理程序。Express 使用了链接在一起的中间件函数。当使用 Express 构建中间件时,有时候你可能想要调用链中的下一个中间件函数。你可以使用 next 变量实现。但是当我们使用路由的时候,我们几乎不需要这个操作,所以在这里你可以安全地删除 next 变量。
现在,看看这个方法的主体。 res 变量表示响应。 响应对象有很多有用的方法:
这里,我们渲染 index 视图,它在 views->index.jade 中已经定义。
这是路由的基本结构。 现在,我们需要为我们的用户创建一个RESTful API。 我们将在像 /api/users 这样的端点上展示我们的用户。
我们直接打开 routes->users.js,开始编辑
var express = require('express');
var router = express.Router();
// 连接数据库
var monk = require('monk');
var db = monk('localhost:27017/ForUsers');
router.get('/',res) {
var collection = db.get('users');
/* 还记得 Robomongo 里的那句 db.getCollection('users').find({}); 吗 * 同样的,db.get('表名'),然后再 .find({}) 表示查找所有 * 类似 MysqL 里的 select * from users; */
collection.find({},function(err,users) {
if (err) throw err;
res.json(users);
});
});
module.exports = router;
前两行像之前一样,我们引入 Express 并获取路由对象。
接下来,我们引入 Monk,它是 MongoDB 上的一个持久化模块。还有另一个流行的持久化模块用于与 Mongo 一起工作,称为 Mongoose,但在本教程中,我更喜欢使用 Monk,因为它比 Mongoose 更容易。
前面说过,根据模块的实现,require 方法可能返回 1 个对象或方法。这里,当我们导入 Monk 时,我们收到一个方法,而不是一个对象。所以,monk 变量是一个方法,我们调用以访问我们的数据库:
var db = monk('localhost:27017/ForUsers');
现在,看看我们的路由处理程序的实现。
function(req,res) {
var collection = db.get('users');
collection.find({},users){
if (err) throw err;
res.json(users);
});
}
首先我们调用 db 对象的 get 方法,传递集合的名称(users)。它返回一个集合对象,此集合对象提供了许多方法来处理该集合中的文档。
- insert
- find
- findOne
- update
- remove
在这里,我们使用 find 方法来获取集合中的所有用户,此方法的第一个参数是确定过滤的对象。由于我们想要得到所有的用户,我们传递一个空对象作为参数。第二个参数是当从数据库返回结果时执行的回调方法,此方法遵循“error-first”【错误优先】回调模式,这是 Node 中回调方法的标准协议。使用此模式,回调方法的第一个参数应该是错误对象,第二个参数应该是结果(如果有)。当你使用 Node 开发更多的应用程序时,你将看到更多的回调模式。
在这个回调中,首先我们检查 err 对象是否被设置,如果没有错误作为获取用户文档的一部分,err 将为 null;否则将被设置。我们在这里抛出的错误将停止程序的执行,并向用户报告错误。如果没有错误,我们只需通过调用 res.json 方法返回一个 JSON 对象。
最后,看看这个模块的最后一行:
module.exports = router;
使用此行,我们指定在另一个模块中需要此模块时返回的对象(或方法)。在这种情况下,我们在 Express 中返回路由对象。 所以,这个模块的目的是获取路由并注册几个路由,然后返回它。
还有一小步。我们构建了一个模块来为我们的新 API 配置路由,但我们没有在任何地方使用这个模块。再次打开 app.js,在顶部附近查找以下代码:
var index = require('./routes/index');
var users = require('./routes/users');
Oh,因为我们直接使用的 users.js,这里面已经引入了这个文件,所以不必再写进入,如果是自己创建的,就得添加进来。
我们已经引入了用户模块并将其存储在一个名为 users 的变量中。 接下来,我们需要使用它。在 app.js 中向下滚动一下,找到以下部分:
app.use('/',index);
app.use('/users',users);
把 app.use('/users',users);
修改为
app.use('/api/users',users);
好了,记得在 ForUsers
目录里运行 nodemon
,然后浏览器访问:http://127.0.0.1:3000/api/users
高亮的原因是 Chrome 扩展 —— Prism Pretty
Angular,上!
在这一步,你将学习 Angular 的基础知识。如果您已经熟悉 Angular,可以跳过解释,但请务必应用所有代码到项目中。
Angular 是构建单页应用程序(SPA)的流行前端框架,它提供了路由、依赖注入、可测试性和遵循 MVC 架构模式的代码解耦。如果这一切听起来太 Geeky,不要担心,在本节中,您将看到大多数这样的功能。
首先,我们需要添加 Angular 到我们的应用程序。转到 views->layout.jade,并在 head 的末尾添加这三个引用:(我这里使用的 bootcss 镜像)
script(src='//cdn.bootcss.com/angular.js/1.6.0/angular.min.js')
script(src='//cdn.bootcss.com/angular.js/1.6.0/angular-resource.min.js')
script(src='//cdn.bootcss.com/angular.js/1.6.0/angular-route.min.js')
现在你的 layout.jade 文件看起来应该是这样的:
doctype html
html
head
title= title
link(rel='stylesheet',href='/stylesheets/style.css')
script(src='//cdn.bootcss.com/angular.js/1.6.0/angular.min.js')
script(src='//cdn.bootcss.com/angular.js/1.6.0/angular-resource.min.js')
script(src='//cdn.bootcss.com/angular.js/1.6.0/angular-route.min.js')
body
block content
确保它们每行都使用相同的缩进级别,因为Jade(Express 中的默认模板引擎)对空格非常敏感。Express Generator 生成的 Jade 视图使用两个空格字符进行缩进。所以,你需要遵循相同的规则,你不能在同一个视图中和制表符【Tab 缩进】混合使用。否则你运行时会得到一个错误。
这些脚本是什么? 第一个是主要的 Angular 框架,第二个(angular-resource)用于调用 RESTful API,第三个(angular-route)用于管理路由。通过路由,我们定义用户在浏览应用程序时所看到的内容。
接下来,在 public->javascripts 下创建一个名为 forusers.js
的新文件,我们将在这里写所有的 JavaScript 代码。
在 layout.jade 中 Angular 脚本后面添加对 forusers.js 的引用:
script(src='/javascripts/forusers.js')
再次强调,请确保此行与 head
中的其他脚本引用处于相同的缩进级别。此时的 layout.jade 应该是这样的:
doctype html
html
head
title= title
link(rel='stylesheet',href='/stylesheets/style.css')
script(src='//cdn.bootcss.com/angular.js/1.6.0/angular.min.js')
script(src='//cdn.bootcss.com/angular.js/1.6.0/angular-resource.min.js')
script(src='//cdn.bootcss.com/angular.js/1.6.0/angular-route.min.js')
script(src='/javascripts/forusers.js')
body
block content
现在我们已经拥有了必要的脚本,现在是时候添加 Angular 到我们的应用程序,添加Angular包括两个步骤:
首先,我们将
ng-app
属性添加到我们的 HTML 元素中。当 Angular 脚本被加载时,它会在 DOM 中寻找这个属性,如果找到它,它会 Hook 到你的应用程序中。接下来,我们为我们的应用程序创建一个 Angular 模块,Angular 应用由一个或多个模块组成。一个简单的应用程序,你只需要一个模块叫做 app,但随着应用程序的增长,您可能希望将各种功能划分为不同的模块,以便更好地组织和维护。
在 VSCode 中继续打开 layout.jade,将 ng-app
添加到顶部附近的 html 元素,就像这样:
doctype html
html(ng-app='ForUsers')
在 Jade 中,我们使用括号将属性添加到 HTML 标签。当这行由 Jade 模板引擎渲染时,我们将得到一个 HTML 元素,如下所示:
<html ng-app='ForUsers'>
我们为 ng-app
设置的值是应用程序模块的名称。现在我们需要创建这个模块。
打开 forusers.js 并写上这段代码:
var app = angular.module('ForUsers',[]);
Angular 对象可用于全局空间,所以它无处不在。模块方法可用于定义新模块或获取对现有模块的引用。第一个参数是模块的名称,这是我们使用 ng-app
上面的名称。第二个参数是一个依赖关系数组,如果提供此参数,模块方法将定义一个新模块并返回一个引用,如果将其排除,它将尝试获取对现有模块的引用。这里,我传递一个空数组,表示这个模块不依赖于任何其他模块。
这就是我们必须做的将 Angular 这货 Hook 到我们的应用程序。接下来,我们将使用 Angular 重新构建我们的主页,以显示数据库中的所有用户。
使用 Angular 重新构建首页
Express Generator 生成的默认应用程序使用Jade 作为视图引擎,这些 Jade 视图在服务器上进行解析和呈现,并将 HTML 标签返回给客户端,这就是很多 Web 框架是如何工作的。但在这个应用程序中,我们将使用不同的构建风格。我们将返回纯 JSON 对象,并让客户端(Angular)呈现视图,而不是将 HTML 标签返回到客户端。让我解释为什么。
早些时候,在“什么时候使用 Node”一节中,提到一个常见的情况,Node 擅长在文档数据库上构建 RESTful API,有了这种架构风格,我们没有任何数据转换的开销,我们在 Mongo 中存储 JSON 对象,通过 RESTful API 得到它们,并直接在客户端(Angular 中)展示。 JSON 是原生的 JavaScript 和 MongoDB 对象。因此,通过在整个堆栈中使用它,我们减少映射的开销或将其转换为其他类型。通过从我们的 API 返回 JSON 对象并在客户端上渲染视图,可以提高性能和可扩展性,因为服务器的 cpu 不会浪费在为大量并发用户呈现视图上。此外,我们可以重复使用相同的 API 来构建另一个客户端,例如 iPhone 或 Android 应用。
在这个步骤中,我们将不使用 Jade 视图构建的默认主页,而是使用 Angular 视图来取代。
在 public 下创建一个新文件夹 partials。我们将在这里存储我们的所有视图,在此先在文件夹下创建一个名为 home.html 的新文件,在此文件中,只需键入:
<h1>Home Page</h1>
现在,当用户访问主页时,我们需要告诉 Angular 渲染此视图。我们使用 Angular 路由实现这个功能。
在 forusers.js 中,更改应用程序模块的声明如下:
var app = angular.module('ForUsers',['ngRoute']);
我在依赖关系数组中添加了一个对 ngRoute
模块的引用。ngRoute
是用于配置路由的内置 Angular 模块之一。
在应用程序模块声明之后的 forusers.js 内编写以下代码:
app.config(['$routeProvider',function($routeProvider) {
$routeProvider
.when('/',{
templateUrl: 'partials/home.html'
})
.otherwise({
redirectTo: '/'
});
}]);
讲解一下,我们使用应用程序模块的配置方法为我们的应用程序提供配置。这个代码将在 Angular 检测到 ng-app
并尝试启动时立即运行,config 方法需要一个数组:
app.config([]);
此数组可以具有零个或多个依赖关系,以及一个用于实现配置的函数。这里,我们对 $routeProvider
有一个依赖,它是一个在 ngRoute
模块中定义的服务。这就是为什么我们改变了我们的应用程序模块声明来依赖于 ngRoute
的原因。配置函数接收 $routeProvider
作为参数。
app.config(['$routeProvider',function($routeProvider){
}]);
在我们的配置函数中,我们使用 $routeProvider
的 when
方法来配置路由。
$routeProvider
.when('/',{
templateUrl: 'partials/home.html'
})
第一个参数(’/’)是相对路径,第二个参数是一个对象,它指定视图的路径(通过 templateUrl
)。我们可以有多个对 when
方法的调用,每个都指定一个不同的路由。最后,我们使用其他方法来指示如果用户导航到任何其他 URL,则应将其重定向到根路径(’/’)。
几乎要完成啦,我们只需要对主页的 Jade 视图进行一些小的更改。所以,打开 views->index.jade,并更改此文件的内容如下:
extends layout
block content
div(ng-view)
我删除了这个视图中的现有内容(Welcome to Express),而是添加了一个带有属性 ng-view
的 div
。 此属性告诉 Angular 在哪里渲染视图。这样,当用户第一次点击主页时,我们的 Jade 视图将在服务器上呈现并返回给客户端。在现实应用中,此视图将具有网站的基本模板(例如,导航栏,logo 等)。它还将有一个内容区域(由 ng-view
指示),用于 Angular 渲染视图。当用户浏览应用程序时, Angular 将用不同的 Angular 视图替换内容区域。这可以防止整页重载,从而提高性能。这就是为什么我们称这些应用程序为单页应用程序:基本上一个单一的页面完全从服务器下载,任何后续页面只是内容区域的替换。
注意:再次,再强调一次,Jade 对空格字符非常敏感。 在同一视图中,不能混用空格和制表符缩进。Express Generator 生成的默认 Jade 视图使用两个空格字符缩进。所以,确保在 div(ng-view
)之前添加两个空格字符,否则可能会导致运行时解析错误。
让我们在实现最后一步之前做一个快速测试。返回浏览器,访问 http://127.0.0.1:3000,应该看到我们的新主页用 Angular 构建,并且注意 URL 变成了:http://127.0.0.1:3000/#!/。那么,何以见得我们是使用了 Angular?只需要开启控制台:
实现控制器
主页已正确 Hooked,现在我们需要从服务器获取用户并在主页上呈现。在 Angular 或任何其他 MVC 控制器中,这就是控制器的职责。视图纯粹负责呈现,而控制器负责获得视图的数据或处理从视图产生的事件(例如点击按钮等)。
让我们为我们的主视图创建一个控制器。打开 forusers.js 并更改应用程序模块的声明如下:
var app = angular.module('ForUsers',['ngResource','ngRoute']);
现在我们依赖于两个模块:ngResource
,用于调用 RESTful API 和 ngRoute
来管理路由。
app.controller('HomeCtrl',['$scope','$resource',function($scope,$resource) {}
]);
解释一下。
这里我们使用应用程序模块的控制器方法来定义一个新的控制器。
第一个参数是一个字符串,指定此控制器的名称。按照惯例,我们将 Ctrl 附加到我们的 Angular 控制器名。
第二个参数是数组。此数组可以包含零个或多个字符串,每个字符串表示此控制器的依赖关系。这里,我们指定了一个对 $scope
和 $resource
的依赖。这两个都是内置的 Angular 服务,这就是为什么他们前面加一个 $
符号。我们将使用 $scope
将数据传递给视图和 $resource
来使用 RESTful API。此依赖性数组中的最后一个对象是表示控制器的主体或实现的函数。在这个例子中,我们的函数获取两个参数:$scope
和 $resource
。 这是因为我们在声明这个函数之前引用了 $scope
和 $resource
。
app.controller('HomeCtrl',$resource) {
var Users = $resource('/api/users');
Users.query(function(users) {
$scope.users = users;
});
}
]);
这里,我们调用 $resource
方法来获取给定 API 端点(/api/users)的资源对象。此对象将提供与我们的 API 一起使用的方法。我们使用查询方法来获取所有用户。查询方法获得一个回调函数,当查询结果准备就绪时,它将被执行。此功能将接收从服务器检索的用户。最后,我们将这些用户存储在 $scope
中,以便我们可以在视图中访问它们进行渲染。记住,$scope
是视图和控制器之间的胶水。
现在,我们需要改变视图来渲染视图列表。打开 partials->home.html 并添加如下代码:
<ul> <li ng-repeat='user in users'>{{user.name}}</li> </ul>
我们使用 ul
和 li
来渲染用户列表。 我们的 li
有一个称为 ng-repeat
的 Angular-specific 属性。 Angular 中的这些属性称为指令,用于向 HTML 元素添加行为。ng-repeat
的值是类似于 JavaScript 中的 foreach
表达式。因此,users 本质上就是我们在控制器中早期在 $
范围中设置的属性。users 中的 user 在此阵列中表示一次一个用户。因此,这个 li
元素将针对 users 数组中的每个用户重复。 我们使用 {{}}
来写表达式。在这里,我们只需在我们的 li
中渲染每个用户的名字。
最后,我们需要将此控制器注册为路由的一部分。 回到 forusers.js 中,更改路由配置如下:
.when('/',{
templateUrl: 'partials/home.html',controller: 'HomeCtrl'
})
有了这个,我们告诉 Angular,当用户访问到网站的根路径时,显示 partials/home.html 并附加 HomeCtrl 控制器。
我们已经完成了这部分,返回浏览器并刷新主页,您应该会看到用户列表:
文中翻译和慕课那篇译文有差异。