使用 MEAN 技术栈和 WebMatrix 的 Node.js 简介






4.64/5 (6投票s)
介绍如何使用 Node.js 和 Angular.js 构建 Web 应用程序
引言
本文将指导你开始使用 MEAN 技术栈构建一个网站。我还会尝试帮助你搭建开发应用程序所需的基本工具/基础设施,例如设置 Node.js、MongoDB 等。我假设你对 Node.js 和 JavaScript 以及 HTML 等有基本的了解。然而,即使你是 Node.js 或其他相关技术的新手,也不用担心,因为本文涉及多种不同的技术,我只会做一些浅尝辄止的介绍。
MEAN 这个缩写代表什么?
- M - MongoDB (NoSQL 文档存储数据库)
- E - Express (用于 Node.js 的 Web 框架)
- A - Angular.js (客户端 JavaScript MVC 框架)
- N - Node.js
使用 MEAN 技术栈的优势在于,所有组件都非常健壮和流行,并且 JavaScript 作为通用语言同时在客户端和服务器端使用。此外,Node.js 和 MongoDB 能够很好地协同工作。
我将按类别定义下面常用的技术栈。我可能不会详细使用下面列出的所有技术,但了解整个技术栈有助于知道各个部分的作用。
技术栈分类
客户端
- HTML5/CSS3
- Angular.js 作为 MVC 框架
- Javascript/Jquery
- Bootstrap 用于响应式设计
服务器
- Node.js
数据访问和 ORM
- Mongoose
数据库
- MongoDB
请记住,虽然我们在数据访问中使用了 ORM 这个术语,但这些 NoSQL 数据库不定义任何模式(schema),所以 Mongoose 可能与 NHibernate 或 Entity Framework 等其他对象关系映射器(ORM)有些不同。
开始吧
安装 Nodejs
你可以从官方网站 http://www.nodejs.org/ 安装 Node.js。请确保在安装向导的“自定义设置”中选择了所有组件。
我将使用 Microsoft Web Matrix 作为 IDE,它是免费的,你可以从以下链接安装。(你也可以选择自己的 IDE,甚至使用记事本。)
安装后,打开 WebMatrix,你会看到如下界面
点击右下角的选项图标,我们将在那里设置项目的路径。
将默认站点位置更改为你选择的路径,然后点击保存。你使用 WebMatrix 创建的项目将存放在该路径下。
现在在启动屏幕上,点击新建并选择模板库。在左侧菜单栏选择 Node.js 标签,并选择“空站点”作为模板。同时为你的项目输入一个站点名称。
点击下一步,WebMatrix 将为我们下载并安装模板。现在你应该能看到为我们创建的 Node.js 项目,如下图所示
在这里,我们看到一些文件,如 server.js、package.json、web.config 等。我们稍后会详细研究它们。
首先,打开 server.js 文件,我们看到一些模板代码已经写好了,如上图所示。我高亮显示了输出“Hello, world NodeJS
”的代码片段。只需点击左上角的运行,浏览器就会弹出一个显示Hello World文本的窗口。这样,我们的第一个 Node.js 程序就成功运行起来了,从而验证了 Node.js 和 WebMatrix 都已正确安装。
好了,现在我们来试试不使用 WebMatrix,只用我们熟悉的好伙伴——命令提示符来运行我们的程序。
打开 cmd
(以管理员模式运行命令提示符——右键点击并选择以管理员身份运行),然后进入项目文件所在的路径。输入“node server.js
”,意思是用 node.exe 运行 server.js 文件。Node.exe 是运行 Node.js 的主可执行文件。
这时,我们得到了一个错误,因为我们之前已经用 WebMatrix 运行了程序,所以端口 8080 已经被占用了。我在运行时遇到了这个错误,所以分享给你,以防你也遇到。
我们只需在 server.js 文件的最后一行将端口号更改为 8081,然后再次运行程序。
现在打开浏览器并访问 https://:8081/,我们应该会再次看到 hello world。所以现在我们看到,仅使用命令提示符,我们就能够运行我们的 Node.js 程序。我们稍后会回到 server.js 文件的细节,但在此之前,让我们先理清一些 Node.js 的概念。
package.json
package.json 文件包含了我们项目的信息,如项目名称、版本、作者等。它还包含一个 dependencies
属性,我们稍后会探讨。
模块 (使用 require())
为了遵循良好的编程实践并编写模块化代码,我们会将 JavaScript 代码写在不同的文件中,每个文件代表一个特定的模块。
Node.js 使用一个概念,即在一个文件中“require”(引用)其他文件(这些文件实际上代表模块),这样我们就可以使用通过“require
”包含进来的文件中的方法。听起来有点绕,我们来看个例子。
我们创建一个文件,命名为 employee.js。
每个 node.js 文件都有一个叫做 module.exports
的东西,它有点像面向对象编程(OOP)中的对象。所以我们可以使用“module.exports
”关键字来创建属性。
employee.js
module.exports.firstname = "Rajiv";
module.exports.lastname = "Gogoi";
module.exports.department = "Computer Science";
module.exports.address = {
city : "Mumbai",
country : "India"
};
所以这个 employee.js 文件或模块包含四个属性,三个简单属性如 firstname
、lastname
、department
,以及一个名为 address
的复杂属性。
现在我们创建另一个名为 main.js 的文件,它将使用 require
功能来引用这个 employee.js 文件。
main.js
var employee = require("./employee.js");
console.log(employee.firstname + " " + employee.lastname);
console.log(employee.address.city + ", " + employee.address.country);
var employee = require("./employee.js");
上面这行代码使用了 node.js 的 require
方法,并传入了 employee.js 文件的路径(./
表示同一目录)。该方法返回一个对象,我们将其赋给变量 employee
。之后,我们使用 employee
对象将属性值打印到控制台。
注意:请注意,这里的 employee.js 是项目根目录下的一个文件,但我们完全可以创建一个文件夹,将我们的 .js 文件放在其中,并使用相对路径。在大型应用中,我们通常会创建文件夹来存放相关的 js 文件,以便更好地管理和维护。
让我们打开 cmd
命令提示符,进入项目路径,然后尝试运行这个 main.js 文件。
好的,我们已经了解了如何使用 require()
方法创建模块以及获取/设置属性。现在让我们尝试创建另一个模块,它实际上代表一个类(让我们更接近面向对象编程)。
我们创建一个名为 customer.js 的文件,其中包含以下代码。这段代码将返回一个函数,我们将把它模拟成一个类
。对于了解 JavaScript 构造函数的人来说,下面的用法是相同的。http://www.javascriptkit.com/javatutors/oopjs2.shtml
module.exports = function () {
this.customername = "TopBank",
this.location = "Florida",
this.employeestrength = 10000
};
现在让我们回到 main.js 文件,在之前写好的代码下面添加以下代码片段。
我们将像之前一样使用 require
,但这一次它会返回一个函数,这个函数代表 Customer
类。之后,我们将使用 new
关键字创建一个 Customer
类的对象。
var employee = require("./employee.js");
console.log(employee.firstname + " " + employee.lastname);
console.log(employee.address.city + ", " + employee.address.country);
//Let's require customer.js which in this case will represent class customer.
var Customer = require("./customer.js");
//Now let's create a new object of Customer class.
var objCustomer = new Customer();
console.log(objCustomer.customername);
console.log(objCustomer.location);
console.log(objCustomer.employeestrength);
Node 包管理器 (NPM)
NPM 是一种功能,通过它我们可以从互联网上安装 node.js 包。对于来自 .NET 背景的人来说,它类似于 Nuget。Java 也有类似的包管理器,例如 jpm4j。
https://npmjs.net.cn/ 列出了可供我们使用的 node.js 库。
现在,让我们使用 npm
和以下命令来安装 Node.js 的 Express 库,也就是 MEAN 缩写中的 E。
npm install express --save
这里的 --save
是可选的,它实际上是说,请将 Express 库保存为当前项目的依赖库。这样做的效果是,在 package.json 文件(主项目文件夹中)中,会有一个 dependencies
属性,其值为 express。好处是当我们使用源代码控制时,我们不必将这些 npm
模块也检入。每当一个新用户检出 node.js 项目时,package.json 文件会看到 npm
的依赖项并自动安装它们。
安装后,我们会看到项目文件夹中多了一个名为 node_modules 的新文件夹,其中包含了 Express 库的文件。
npm
的优点在于它也管理子库的依赖关系,例如,我们的 express 模块可能也依赖于其他一些模块。它的工作方式是,在 node_modules 文件夹下的 express 文件夹里,也会有一个 package.json 文件,其中会再次定义其依赖项。
server.js
好了,不久前,我们看到了 server.js 文件向浏览器返回了一个 "Hello World
" 的响应。现在让我们深入了解这个文件的细节,尝试做一些修改,并解释每一行的作用。
//Let's require the http module which will take care of listening to HTTP requests.
//http is a built
//in node.js library
var http = require('http');
//Let's create our Web Server passing a callback function with request/response as parameters.
var server = http.createServer(function (req, res) {
//Write out response headers of 200 successful and content type.
res.writeHead(200, { 'Content-Type': 'text/html' });
//Write out a piece of our everlasting Hello World string
// Notice that we are using the request object to write our the host header.
res.write("Hello Node.js, are you running fine on host " + req.headers.host + "\n");
//End the response else the request will keep hanging.
res.end();
})
// Make sure server is listening on port 8081.
server.listen(process.env.PORT || 8081);
开始吧
既然我们已经设置好了 Node.js,掌握了一些基本概念,并运行了一些程序,那么让我们如约开始使用 MEAN 技术栈来构建一些东西吧。
我们已经通过 npm
安装了 "Express
" 库。这个 "Express
" 库将帮助我们以一种相对简单的方式构建我们的 Web 服务器,因为它将作为我们的 Web 框架。因此,我们将拥有库函数来完成大部分工作,比如处理 HTTP GET
/POST
请求、向客户端发送数据等,而不必处理像 res.end()
这样手动结束响应的事情。相反,Express 库将充当一个抽象层,这样我们就可以专注于我们的应用逻辑,而不必处理底层的职责。那么,让我们修改 server.js 文件来使用 Express 库吧。
//Let's require the http module which will take care of listening to HTTP.
var http = require('http');
//Let's require the express module which will return a function that we will call
var express = require('express');
//Let's call express as a function which will return an application object
var application = express();
//Let's use the application object to do something on an HTTP GET request.
application.get("/", function (req, res) {
// Set the response header content type
res.set("Content-Type", "text/html");
// Send some HTML back as response.
res.send("Hello using express get");
})
//Let's create our Web Server passing the application object.
var server = http.createServer(application)
// Make sure server is listening on port 8081.
server.listen(process.env.PORT || 8081);
现在让我们尝试运行我们的程序,你可以点击 Web Matrix 左上角的运行,或者使用命令提示符运行 node server.js 文件。无论哪种方式,你都会看到浏览器显示从服务器返回的 Hello 文本。
视图引擎
到目前为止,我们都是直接使用 write()
或 send()
函数从服务器发送文本/HTML。但在实际应用中,我们希望使用一种视图引擎,在其中编写我们的 HTML。Node.js 有多种视图引擎,如 Jade、EJS 或 Vash,选择哪一种完全取决于个人喜欢的语法风格。在本文中,我将使用 Vash 视图引擎。
让我们使用以下命令来安装 vash
库。
npm install vash --save
现在 vash
已经安装好了,让我们重写 server.js 文件,使用 vash
视图引擎,并设置响应来渲染一个视图。
server.js
//Let's require the http module which will take care of listening to HTTP requests.
var http = require('http');
//Let's require the express module which will return a function that we will call
var express = require('express');
//Let's call express as a function which will return an application object.
var application = express();
// Setting the View Engine to Vash. "view engine" is the key.
application.set("view engine", "vash");
//Let's use the application object to do something on an HTTP GET request.
application.get("/", function (req, res) {
// We'll use the render method of the response object passing "index"
// as the filename(1st para meter)
// and a JavaScript object literal as the 2nd parameter.
// This object literal with firstname and lastname properties
// will be passed to the view to be
// used there.
res.render("index", {firstname: "Robert", lastname: "Brosnan"});
})
//Let's create our Web Server passing the application object.
var server = http.createServer(application)
// Make sure server is listening on port 8081.
server.listen(process.env.PORT || 8081);
之后,让我们在项目根目录下创建一个 views 文件夹。这个 views 文件夹将包含视图文件。让我们添加一个 index.vash 文件,并添加以下代码片段。
index.vash
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
@model.firstname @model.lastname
</body>
</html>
注意,我们通过在 @model
后面附加属性名来使用 firstname
和 lastname
属性。是的,视图将这些传递给它的属性视为模型属性。
<meta charset="utf-8" /> <title></title>
现在让我们再次运行程序,我们应该会看到 Robert Brosnan 的输出。
<meta charset="utf-8" /> <title></title>
添加 CSS 资源
让我们为项目添加一个 site.css 文件来进行样式设计。我们已经在主项目目录中有一个 public 文件夹。让我们在该目录中创建 site.css 文件。这个 public 文件夹是所有客户端静态资源应该存放的地方,比如 CSS、客户端 .js 文件、图片等。
site.css
body {
background-color: #b6ff00;
}
在 index.vash 视图中链接 site.css 文件。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title></title>
<link href="site.css" rel="stylesheet" />
</head>
<body>
@model.firstname @model.lastname
</body>
</html>
现在,如果我们再次使用 WebMatrix 中的运行按钮来运行我们的网站,我们应该会看到一个背景色,这意味着 site.css 文件正在被使用。然而,如果我们尝试使用命令提示符来运行我们的 node 服务器,即进入项目路径并执行 "node server.js
",然后从浏览器运行网站,我们会发现看不到背景色。如果我们在浏览器中使用开发者工具(F12),可以看到 site.css 文件未找到。
那么,为什么当我们直接从 WebMatrix 运行网站和从命令提示符运行 node 服务器时会有差异呢?为什么从 WebMatrix 运行时,它能够找到 public 文件夹内的 site.css 文件?
原因在于,当我们从 WebMatrix 运行网站时,它使用 IIS Express 来托管 Node 服务器,而 IIS Express 使用一个名为 web.config 的配置文件,你可能已经在项目目录中注意到了这个文件。
请注意,这个 web.config 文件对于运行 node.js 并非必需,它只是 WebMatrix IDE 为了在 Microsoft IIS Web 服务器(在我们的例子中是 IISExpress)中托管项目而创建的。当我们使用命令提示符运行 node.js 服务器时,它不会读取 web.config 文件。
如果我们查看 web.config 文件,可以看到那里设置了一条规则,该规则会检查传入的 URL 是否与 public 文件夹中的物理文件匹配。因此,这条规则有效地帮助我们的请求在 public 文件夹内找到了 site.css 文件。
但这是否意味着当我们使用命令提示符运行 node 服务器时,就无法使用 CSS 文件了呢?显然不是。为了在使用命令提示符运行时能够识别 public 文件夹,我们需要将 public 文件夹设置为静态资源文件夹。要做到这一点,打开 server.js 文件,在设置视图引擎的代码行之后,添加以下代码。我还使用了 console.log
来打印 __dirname
,这样你就可以看到它指向我们 node.js 项目的物理目录。
// Setting the View Engine to Vash. "view engine" is the key.
application.set("view engine", "vash");
// Let's set the public folder as static resources folder.
// __dirname points to the node.js physical project directory.
console.log(__dirname);
application.use(express.static(__dirname + "/public"));
所以现在当我们运行项目时,应该能在浏览器中看到我们美丽的背景了。:)
使用 Bower 进行前端包管理
就像我们使用 npm
进行服务器端包管理一样,我们将使用一个名为 “Bower
” 的客户端包管理器来安装客户端资源,如 jquery、bootstrap、knockout、angular 等。
那么,首先让我们用下面的 npm
命令来安装 Bower。
npm install bower --save-dev
注意这个新的可选标志 --save-dev
的使用。这表示这只是一个开发依赖,我们在生产服务器上不需要这个包。当然,我们为什么会在生产服务器上需要一个客户端包管理器呢。
请看下面快照中的 package.json 文件。
要使用 bower,我们需要一个名为 .bowerrc 的配置文件,在其中我们将放置一个 json,指明客户端资源的保存位置。那么,让我们在项目目录下创建一个新文件,命名为 .bowerrc,并粘贴以下 json 片段。
这段 json 告诉 bower 将客户端资源安装到 public 目录下的 lib 文件夹中。
现在我们已经配置好了 bower,让我们用它来安装 jquery。
我们会注意到,jquery 被下载到了 public/lib 目录下。
提示与技巧
到目前为止,每次我们修改服务器端代码后,都必须手动重启我们的 node.js 应用程序,才能让更改生效。然而,有一个名为 "nodemon
" 的 node.js 开发工具,它可以监控 node 应用程序的变化并自动重启服务器。很酷,不是吗?这样我们就节省了每次修改后重启服务器的时间。
要安装 nodemon
,让我们使用以下命令
npm install nodemon -g
这次我们引入了另一个开关 -g
,意思是全局安装,这样 nodemon
将对这台机器上所有的 node.js 项目可用,而不仅仅是我们当前的项目。
现在,让我们运行 server.js 文件,但这次使用 nodemon
。
nodemon server.js
现在试着对服务器端代码做任何修改,比如更改 firstname
属性等,然后刷新浏览器,你会惊喜地发现更改立即生效了。:)
REST 服务
现在我们已经运行了几个 node.js 程序,使用了 vash
作为视图引擎,并用 CSS 进行了装饰,接下来让我们构建一个 REST 服务,以便稍后由客户端库 Angular.js 来消费。
首先,在我们的 node 项目中创建一个 controllers 文件夹。我们称这个文件夹为 controllers,因为它将控制请求,获取一些模型(数据)并返回给视图,是的,就是 MVC 设计模式中的 C。
在 controllers 文件夹内,让我们创建一个名为 employeeController.js 的 JavaScript 文件。这个文件将是我们应用程序的主文件,我们将用它来显示员工
和插入员工
。是的,这就是我们将使用 MEAN 技术栈构建的功能。我们将使用一个 JavaScript 自执行匿名函数来保持代码的可读性和模块化。要了解更多关于自执行匿名函数的信息,你可以访问 http://esbueno.noahstokes.com/post/77292606977/self-executing-anonymous-functions-or-how-to-write。
在我们的 REST 服务开始返回 JSON 等数据之前,让我们先尝试做一些更简单的事情,就像我们之前做的那样,即通过使用 employeeController
来返回 index.vash 视图。
(function (employeeController) {
//By using employeeController as a parameter in the anonymous function
// we are replacing it with module.exports so that we can start adding functions to it.
// So we have a function named init which accepts an application parameter.
// This application parameter will be passed from the server.js file, yes
// the application object that we created using express library.
employeeController.init = function(application){
//Here we are doing the very same thing that we did in server.js
// routing a GET request to the root of the site "/" and returning the index.vash view
application.get("/", function(req, res){
res.render("index", {firstname: "John", lastname: "Brosnan"});
})
}
})(module.exports);
我们还需要修改 server.js 文件,使其调用 employeeController
的 init
函数。当然,我们不再需要 server.js 文件中对 application.get()
的调用了,因为我们已经将那段代码移到了 employeeController.js 的 init
方法中。
var employeeController = require("./controllers/employeeController");
employeeController.init(application);
现在如果我们尝试运行我们的应用程序,我们应该会在浏览器中看到相同的输出,但这次我们是使用了一个控制器来处理 GET
请求并向视图返回响应。
现在让我们修改 employeeController.js 文件,以公开一个 API 端点,该端点接受 GET
请求并返回一些 JSON 作为数据。这其实很简单,我们要做的是在 application.get()
函数中,使用 send
函数返回 JSON,而不是使用实际上渲染视图的 render()
函数。
(function (employeeController) {
//By using employeeController as a parameter in the anonymous function
// we are replacing it with module.exports so that we can start adding functions to it.
// So we have a function named init which accepts an application parameter.
// This application parameter will be passed from the server.js file, yes
// the application object that we created using express library.
employeeController.init = function (application) {
//Here we are doing the very same thing that we did in server.js
// routing a GET request to the root of the site "/" and returning the index.vash view
application.get("/", function (req, res) {
// Set response headers, content type to json
res.set("Content-Type", "application/json");
// Send a json object back.
res.send({employeeName: "Tommy Jones"});
})
}
})(module.exports);
现在尝试运行应用程序,我们会看到返回了 JSON 数据。
让我们来用 Angular
AngularJs 是一个客户端数据绑定库,如果你用过的话,它和 KnockoutJs 类似。我不会在这里深入讲解 AngularJs 的细节,因为网上有很多教程。我将要做的是展示一个如何将 AngularJs 与 Node.js 结合使用的示例。
那么,首先让我们使用 bower
工具通过以下命令安装 Angularjs。
bower install angular-bootstrap
这将同时安装 angular 和 bootstrap 库(用于响应式 UX 设计)。
现在,让我们在 index.vash 文件中引入 angular 和 bootstrap 的脚本。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
@model.firstname @model.lastname
<script src="/lib/jquery/dist/jquery.js"></script>
<script src="/lib/angular/angular.js"></script>
<script src="/lib/angular-bootstrap/ui-bootstrap.js"></script>
</body>
</html>
让我们在 public 文件夹下创建一个名为 scripts 的文件夹,它将包含我们的客户端 JavaScript 文件。
然后我们添加一个名为 empViewdata.js 的文件。该文件包含一个 JavaScript 自执行匿名函数,是的,与我们用于服务器端脚本的模式相同。它包含了我们命名为 "empViewdata
" 的 angular 模块和名为 "empController
" 的 angular 控制器。我们向自执行函数传递了 window.angular
对象,以便我们可以开始使用 angular 库函数。angular 模块是一段模块化的代码,它定义了模块的依赖关系并包含了控制器。
(function (angular) {
// Let's create a module named empViewdata and the 2nd parameter is an empty array where
// we can define any dependencies that this module needs. Empty means our module has
// no dependencies.
// 1st parameter is the module name which the view will use as ng-app attribute.
var module = angular.module("empViewdata", []);
// Create the controller, 1st parameter is the controller name which
// the view will use and the
// 2nd parameter will be an array.
// This array will have a variable named $scope, which is nothing but the viewmodel.
// the viewmodel $scope will have the properties that the view needs.
// $scope will have one property named employees which will contain an array of objects,
// each object will have one property named "name".
module.controller("empController",["$scope",
function($scope){
$scope.employees = [{name: "Roger Peter"},
{name: "Lovely Planet"},
{name: "Roudie Moore"}]
}
])
})(window.angular);
$scope
是 Angular 提供的一个对象,我们可以用它来向视图返回数据。稍后,我们还会看到另一个类似的 angular 对象 $http
的用法。
好了,我们的 Angular 模块和控制器已经准备好通过 $scope
对象返回视图模型
数据了,现在让我们编写视图来使用 Angular 对象,以便视图能够使用 Angular 进行数据绑定。
index.vash
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title></title>
<link href="site.css" rel="stylesheet" />
</head>
<body>
<div ng-app="empViewdata">
<div ng-controller="empController">
<div ng-repeat="e in employees">
{{ e.name }}
</div>
</div>
</div>
<script src="/lib/jquery/dist/jquery.js"></script>
<script src="/lib/angular/angular.js"></script>
<script src="/lib/angular-bootstrap/ui-bootstrap.js"></script>
<script src="/scripts/empViewdata.js"></script>
</body>
</html>
<script src="/lib/jquery/dist/jquery.js"></script><script src="/lib/angular/angular.js"></script><script src="/lib/angular-bootstrap/ui-bootstrap.js"></script><script src="/scripts/empViewdata.js"></script>
视图中 angular 属性的解释
ng-app
--> 这个 angular 属性的值将是我们之前编写的 angular 模块的名称,在我们的例子中是 "empViewdata
"。实际上,这个div
将成为我们 angular 对象所在的容器。我们可以在一个页面上有多个ng-app
属性,从而通过分配给不同的 angular 模块来让页面的不同部分承担不同的任务。ng-controller
--> 这个 angular 属性将负责控制视图的动作,比如为视图提供数据、处理点击、下拉菜单变化等事件。在我们的例子中,控制器名称是 "empController
",它负责向视图发送视图模型
。就像ng-app
一样,一个视图中可以有多个控制器执行不同的动作。请注意,这是供 angular 使用的客户端控制器。我们之前创建的服务器端控制器 "employeeController
" 是 node.js 控制器,它处理来自浏览器的 HTTPGET
/POST
请求。ng-repeat
--> 这个属性指定这个<div>
将根据集合中项目的数量重复多次。它类似于许多语言(如 C#、JavaScript)中的foreach
循环。表达式 "e in employees
" 意味着e
是一个局部变量,它将在循环中每次执行,而employees
是一个集合。我们已经看到$scope
中的employees
包含一个由员工
姓名组成的数组。所以我们实际上是在尝试遍历employees
集合。{{ e.name }}
--> 双大括号语法表示我们希望在其中绑定一些数据。在这里,我们使用局部变量e
来显示员工
的姓名 (e.name
)。
视图部分就到此为止了。好了,现在,我们只需将服务器端的 node.js 控制器 employeeController
修改为渲染我们初始的 index.vash 视图,而不是返回一个 JSON 响应。
res.render("index");
现在尝试用浏览器运行网站,你应该能看到员工
姓名列表了。
好的,我们使用 Angular 返回了一个硬编码的员工
姓名集合,并将其显示在视图中。但在实际应用中,数据会来自某个数据源,并由某个服务提供。所以,让我们回到我们之前创建的 REST 服务的工作上。
我们将在 employeeController.js 文件中添加另一个 GET
事件处理器,如下面的代码片段所示。这个 get
处理器将路由到 URL 请求 "/api/employee",并返回一个员工
姓名的 JSON 集合。
(function (employeeController) {
//By using employeeController as a parameter in the anonymous function
// we are replacing it with module.exports so that we can start adding functions to it.
// So we have a function named init which accepts an application parameter.
// This application parameter will be passed from the server.js file, yes
// the application object that we created using express library.
employeeController.init = function (application) {
//Here we are doing the very same thing that we did in server.js
// routing a GET request to the root of the site "/" and returning the index.vash view
application.get("/", function (req, res) {
res.render("index");
})
application.get("/api/employee", function (req, res) {
// Set response headers, content type to json
res.set("Content-Type", "application/json");
// Send a json collection object back.
res.send([{ name: "Roger Peters" },
{ name: "Lovelys Planet" },
{ name: "Roudie Moores"}]);
})
}
})(module.exports);
然后回到我们的 angular 模块,我们将在那里对 "/api/employee" 发起一个 ajax HTTP GET
调用,它将返回一个 JSON 集合,然后我们将像之前一样愉快地使用 angular 进行绑定,但这次不是用硬编码的集合,而是用从 REST 服务返回的数据。为了实现这一点,我们将使用 $http
angular 对象,并对 URL "/api/employee
" 发起一个类似于 jquery get 请求的 get()
请求。$http
使用了一种叫做 JavaScript promise 的模式,这有点像我们在 then()
方法上定义的回调。then()
方法接受 2 个函数作为参数,一个用于成功,第二个用于失败。
了解更多关于 JavaScript promises 的信息 https://www.promisejs.org/
(function (angular) {
// Let's create a module named empViewdata and the 2nd parameter is an empty array where
// we can define any dependencies that this module needs. Empty means our module has
// no dependencies.
var module = angular.module("empViewdata", []);
// Create the controller and the 2nd parameter will be an array.
// This array will have a variable named $scope, which is nothing but the viewmodel.
// the viewmodel $scope will have the properties that the view needs.
module.controller("empController", ["$scope", "$http",
function ($scope, $http) {
$http.get("/api/employee")
.then(function (result) {
$scope.employees = result.data;
},
function (error) {
// Do something with the error
alert("error");
});
}
])
})(window.angular);
现在,让我们再次尝试运行我们的网站,我们应该能看到由 node.js REST 服务返回并由 angular 进行数据绑定的员工
姓名。
好了,MEAN 技术栈中我们只剩下 "M (MongoDB)" 了。我们已经使用了 E (Express)、A (Angular)、N (Node.js),那么,让我们开始使用 MongoDB 吧 :)
MongoDb
让我们根据我们的操作系统从 https://mongodb.ac.cn/downloads 下载 mongodb。从 zip 文件解压后,让我们将 bin 目录下的文件复制到我们项目要使用的一个目录中。
我们还要在这个目录下创建一个文件夹,并命名为 database。这个文件夹将是数据的容器。
现在让我们从 cmd 运行一个命令来启动我们的 mongodb 服务器。
现在我们的 mongodb 服务器已经启动并运行了。让我们尝试用浏览器访问我们的 mongodb 服务器,是的,mongo 的 web 控制台运行在 28017 端口。
我们将使用 mongoose 作为 node.js 和 mongodb 之间的数据连接。简而言之,mongoose 有点像 nhibernate/entity framework 这样的 ORM,我们可以用它来为我们的模型定义模式(schema)。需要知道的是,MongoDB 作为一个 NoSQL 数据库,不强制执行模式,但这又是另一个话题了,我们不在这里讨论。
让我们使用以下命令来安装 mongoose
。
npm install mongoose.
现在让我们在 employeeController.js 中编写代码,使用 mongoose
连接到我们的 mongodb
数据库,创建一个员工
,并检索员工
列表。
employeeController.js
我们向该文件添加了 post 事件处理器,以处理创建员工
的请求。
(function (employeeController) {
//By using employeeController as a parameter in the anonymous function
// we are replacing it with module.exports so that we can start adding functions to it.
// So we have a function named init which accepts an application parameter.
// This application parameter will be passed from the server.js file, yes
// the application object that we created using express library.
employeeController.init = function (application) {
// Mongoose connection - Start
// Add mongoose library
var mongoose = require('mongoose');
// Connect to empdb database, if not exists, mongodb will create.
mongoose.connect('mongodb:///empdb');
// define connection
var db = mongoose.connection;
db.on('error', console.error);
db.once('open', function callback() {
console.log('mongodb connection opened.');
});
// define schema and model
var empSchema = mongoose.Schema({ name: String });
var Employee = mongoose.model('Employee', empSchema);
// Mongoose connection - End
//Here we are doing the very same thing that we did in server.js
// routing a GET request to the root of the site "/" and returning the index.vash view
application.get("/", function (req, res) {
res.render("index");
})
application.get("/api/employee", function (req, res) {
// Set response headers, content type to json
res.set("Content-Type", "application/json");
// Retrieve employees
Employee.find(function (err, employees) {
if (err) return console.error(err);
// Send a json collection object back.
res.send(employees);
});
});
// Handle POST request to create employee
application.post("/api/employee", function (req, res) {
// Create an employee
// Get the employee name from the HTTP request body using req.body.empName
var emp = new Employee({ name: req.body.empName });
emp.save(function (err, emp) {
if (err) return console.error(err);
console.log(emp);
});
// Set response headers, content type to json
res.set("Content-Type", "application/json");
// Send the inserted employee name back to angular for
// databind to the collection employees.
res.send({ name: req.body.empName });
});
}
})(module.exports);
empViewdata.js
(function (angular) {
// Let's create a module named empViewdata and the 2nd parameter is an empty array where
// we can define any dependencies that this module needs. Empty means our module has
// no dependencies.
var module = angular.module("empViewdata", []);
// Create the controller and the 2nd parameter will be an array.
// This array will have a variable named $scope, which is nothing but the viewmodel.
// the viewmodel $scope will have the properties that the view needs.
module.controller("empController", ["$scope", "$http",
function ($scope, $http) {
$scope.newEmployee = { empName: "" };
$http.get("/api/employee")
.then(function (result) {
$scope.employees = result.data;
},
function (error) {
// Do something with the error
alert("error");
});
// Call REST api to create employee
// $scope.newEmployee is the model object, this model object contains properties
// attributed with ng-model in the view elements.
// In our case, we have 1 property newEmployee.empName
// in the model properties collection.
$scope.create = function () {
$http.post("/api/employee", $scope.newEmployee)
.then(function (result) {
// Push the added employee to the collection of employees.
$scope.employees.push(result.data);
})
};
}
])
})(window.angular);
修改 index.vash 以包含一个文本框
和一个提交
按钮来添加员工
。
ng-submit
--> 它接受一个create
方法,该方法已在我们的 empViewdata.js 中定义,它会向 node.js REST 服务 API 发起一个$http.post
调用。ng-model
--> 它定义了一个模型对象,作为 HTTPPOST
请求的主体传递。它包含在 HTML 元素上定义的属性,在我们的例子中只有一个,就是文本框。
<div ng-app="empViewdata">
<div ng-controller="empController">
<form class="form-horizontal" name="empForm" ng-submit="create()">
<div class="form-group">
<input type="text" id="empName" ng-model="newEmployee.empName" />
<input type="submit" class="btn btn-primary" value="Create" />
</div>
</form>
<div ng-repeat="e in employees">
{{ e.name }}
</div>
</div>
</div>
最后一件事,修改 server.js 以包含 node.js 库 body-parser
,这样它就能处理 POST
请求了。
var express = require('express'),
bodyParser = require('body-parser');
// Setting application to use body parser.
application.use(bodyParser());
现在确保 mongodb 和 node.js 服务器都在运行,然后浏览网站。你应该能够看到员工列表,并且也能够创建员工。
摘要
所以在这篇文章中,我们简要地探讨了如何使用 MEAN 技术栈来构建以 Node.js 为服务器端的网站。希望我能够分享我的知识并且对你有所帮助。请随时提出任何问题,我会尽力回答。谢谢,并保持学习的热情。:)
历史
- 2014年9月7日:初始版本