这一篇文章会大大降低学习mean开发的难度,翻译的还是不错的,不过有些地方代码错了,可能是编辑文章的时候操作错误导致,我在这里转载过来并且把代码错误的地方改了过来,方便查看。感谢原文作者Moshfegh Hamedani和译者亚里士朱德!
译文地址:http://yalishizhude.github.io/2015/10/13/mean-1/
原文地址:https://blog.udemy.com/node-js-tutorial/
Node是一个开源,跨平台的用来执行javascript的运行环境。它建立在google的v8引擎上,而v8引擎正是google chrome浏览器的执行引擎,能够快速的将javascript代码转为原生的机器码。
在Node诞生之前,javascript只能在浏览器执行。在2009年,Ryan Dahl使用开源的google v8 javascript引擎来构建node,使它成为一个独立于浏览器之外的javascript运行环境。这使得javascript开发者能够在服务端使用javascript来构建大部分的web apis。
题外话:我觉得有两个技术的普及让web前端的有了较大的发展。一是混合应用的出现,包括移动端的混合应用如phone gap、ionic等框架和PC端的混合应用如hex、酷狗音乐等,这一技术的诞生让前端工作从简单的类似制作ppt一般地展现页面效果的工作升级成了开发应用程序的级别;二是node.js的出现,让js可以运用于服务端开发,让前端工程师能低门槛地转成js全栈,通过node.js开发后端又可以与数据库连接,让js开发人员可以参与到web的前端、后端、数据库整个系统,从而为js工程师提供了一条晋升到web架构师的有利通道,为js工程师插上了一双翅膀。
近年node非常地受欢迎并且很多大公司(例如IBM,微软,雅虎,LinkedIn,PayPal),已经开始采用它了。
正如你在下面来自于indeed.com的就业趋势图中所见,市场对于node.js开发者的需求正在快速增长。
虽然在数量方面,node开发者的市场需求量不如Ruby on Rails以及很多其它框架那样,但是我认为这种情况很快会改变。
2013年,从一些官方博客中得知,PayPal从Java转向了node.js,LinkedIn也从Rails转向了node。
我很早就采用了ASP.NET MVC框架,并且使用它进行了数年的web应用开发。关于ASP.NET MVC有一件事让我很不爽的就是,在语言的编程风格和习惯上,从服务端到客户端会有较大的转变。然而C#和javascript都是类似C语言风格的编程语言,当然用C#来编程还是和javascript有很大的不同。这就是为什么很多ASP.NETweb开发者通常只擅长两种语言中的一种而不是全部擅长,所以他们会将自己分类成“后端”或“前端”开发者。同样的情况发生在Ruby on Rails,PHP,Python 等开发者身上。
采用node的话,你可以在服务端和客户端同时使用javascript。这意味着更简洁和更一致的代码库以及更少的转换和映射。当然一个好的javascript开发者能同时编写服务端和客户端的代码将是也是非常牛叉的。
node不是一个银弹。它是专为I/O密集型操作和快速构建可扩展性的实时网络应用而设计的。比如说一些在线游戏,协作工具,聊天系统等。通过node,你可以用最少的系统资源来服务大量的客户端,这就是为什么它为高可扩展性而设计。
对于搭建在类似于MongoDB的文档数据库的API服务器,node也是一个非常不错的选择。你将文档数据以json对象的格式存储在mongo中,然后通过RESTful API的来操作它们。当从数据库读写数据时,并不需要将JSON和其它类型进行转换。在本教程中,我们将构建一个node 应用通过以上提到的这种方式。
根结node的结构特点,它应该避免用于CPU密集型操作。本教程中我将从实例开始介绍node而不是从架构深入讲解node以及为什么它不适用与CPU密集型应用。这些深入讲解的部分我将很快发布在Udemy上。如果你有兴趣,请提交到我的邮箱。
构建node应用有很多选择,但mean全栈式框架最近变得非常流行。mean代表的是:
在我们开始之前,为了开发MEAN应用你需要提前安装一些开发工具。安装这些工具需要15-20分钟。安装完成后,你就有了一个对于MEAN应用的开发环境,并且今后不需要再次安装。
所以请先安装我列举的这些工具。如果你已经安装了其中某些工具的话,可以跳过相关部分。
Sublime Text 是一个轻量级而且功能强大的代码编辑器。你可以使用任何编辑器来构建node应用,但是如果你之前没有使用过sublime的话,我建议你尝试一下sublime。你可以在http://sublimetext.com网站上进行下载。
登陆http://mongodb.org 然后点击 Download MongoDB。按照官网上提供的文档进行安装。安装过程很简单,不像SQL Server那样需要半个多小时,只需要一两分钟即可。
安装完Mongo,你需要按照下面的操作来运行它。你需要创建一个目录用来存储Mongo的数据库文件。这个目录对于当前用于必须有写权限。然后你需要启动MongoD(Mongo Daemon),这是一个用来处理数据请求的后台进程。
默认情况下,MongoD将会把数据存储在系统盘的 /data/db目录下(如果提示错误不存在,请自行创建)。在本教程中我建议你保持默认设置。如果你希望修改这个路径,请参考官网上的指令进行修改。
接下来让我们在默认配置下启动Mongo
对于windows用户
用管理员权限打开命令行窗口
> md \data\db
> cd “C:\Program Files\MongoDB\Server\3.0\bin” (你的MongoDB安装目录)
> mongod
注意你可以将MongoD设置为windows服务,这样的话你就不用每次从命令行来启动了。具体设置方法可以参考MongDB的官网。
然后你可以看到一个弹出窗口,显示 MongoD listening for network connections. Give access to MongoD.
对于Mac用户
打开终端
$ sudo mkdir -p /data/db
$ whoami
moshfeghhamedani
$ sudo chown moshfeghhamedani /data/db
$ mongod
你将看见MongoD在命令行弹出窗口或者终端中执行正在等待连接。
如果你在启动MongoD时发现了任何问题,最好查看MongoDB的官网然后进行完整安装。
登陆https://nodejs.org并且点击安装。不管是使用的是windows还是Mac,它将提供合适的装程序。
安装Node的时候会自动安装NPM(Node Package Manager)。NPM对于Node来说有点像Ruby Gems对于Ruby和NuGet对于.NET。我们通过NPM来下载和安装开源可复用的包/模块到应用中。
Express Generator是一个用来搭建应用的Node模块。为了安装Express Generator,我们需要打开另一个Mac的终端或者windows的命令提示窗口,
然后执行:
npm install -g express-generator
-g
参数代表全局安装。
所有的工具都已经安装完毕。现在让我用Express Generator搭建一个框架。我们将开发一个适用于录像租赁店的叫Vidzy的应用。打开终端窗口,选择一个合适的目录来创建项目:
express Vidzy
Express Generator 将在Vidzy目录下搭建一个应用。
现在在你喜欢的代码编辑器中打开Vidzy文件夹。如果你使用的是Sublime,你可以把这个文件夹拖入Sublime。
下面是目录结构
bin
www
public
images
javascripts
stylesheets
routes
index.js
users.js
views
error.jade
index.jade
layout.jade
app.js
package.son
public 我们将存储一些公共资源到这个目录下,例如javascript文件、样式文件、图片等。
routes 包括一系列的javascript文件,每一个文件都为该应用的给定模块定义了一些路由以及业务逻辑。
views 包括了应用中的视图文件。Express支持很多常见的模板引擎例如:Jade,Haml,EJS,Handlebars等。Jade是默认的模板引擎。
app.js程序的主入口。包括一些程序配置和声明。
package.json每个Node应用都有一个这样的文件。这个文件用来描述当前应用的信息和依赖的插件。
我们打开pakcage.json,可能看到如下信息:
{
"name": "Vidzy",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www"
},
"dependencies": {
"body-parser": "~1.13.2",
"cookie-parser": "~1.3.5",
"debug": "~2.2.0",
"express": "~4.13.1",
"jade": "~1.11.0",
"morgan": "~1.6.1",
"serve-favicon": "~2.3.0"
}
}
在文件中,我们定义了程序的名称和版本以及依赖模块。所有这些依赖模块都是用Node编写的模块。
当你通过Express Generator生成一个应用时,这些依赖模块并没有被安装。它们只是在package.json文件中进行了配置。你需要单独安装这些依赖。
来安装这些依赖,首先返回到控制台并且输入命令:
cd Vidzy
npm install
安装需要花费一些时间。NPM将参照 package.json 文件中定义的依赖。然后它将从NPM仓库中下载这些依赖到一个叫做 node_modules 的目录中。让我们看看这些模块吧。
在 Vidzy 文件夹中,进入 node_modules/express,注意这个目录下有另一个package.json用来定义Express.js的依赖模块。所以,这里也有另一个 node_modules目录来存储依赖模块。这就是一个Node应用的常见目录结构。每一个模块都有一个 package.json文件和一个 node_modules 文件夹。
当你启动Node应用的时候,一个用来接收请求的最基本的web服务器就在3000端口上启动了。如果你修改了代码,这些改变将不会生效直到你重启了服务器。频繁的重启服务器来使代码生效是一件蛋疼的事情。为了解决这个问题,我们使用 Nodemon,这个模块在检测到源文件修改时自动重启web服务器。
安装Nodemon:
npm install nodemon -g
Monk是一个用来读写MongoDB的Node模块。
安装monk:
npm install monk --save
--save
参数告诉NPM将这个依赖加入到package.json文件中。这样做的好处就是你将代码提交到版本库,别人将代码签出,所有的依赖模块都可以从就package.json中读取出来。(也就是说我们不需要保存这些第三方模块,任何时候我们搭建项目只需要读取这个package.json文件即可安装,减少了项目的维护代码)。然后只需要简单的执行 npm install,然后自动安装这些引用模块。这就是我们搭建应用框架的时候需要做的事情。
太棒了!我们已经安装了所有需要的工具。现在是时候来运行应用了。从控制台进入Vidzy目录输入以下命令:
nodemon
Nodemon将在3000端口上启动你的web服务器。你可能会看到一个弹出窗口显示Node正在侦听连接。
现在启动你的浏览器跳转到
http://localhost:3000
这就是你的第一个express应用。
接下来的几个章节,我们将为这个录像租赁店应用开发各种功能。
首先,我们来实现一个简单的功能:在首页上展示数据库中所有的视频。有几种方式来实现这个功能。我们可以从前端到后端开发,也可以反过来。这并没有什么对错,不过在本教程中,处于教学原因我推荐从后端开始开发。
我们将通过以下几个步骤来实现:
我们怎么通过数据文件来构建MongoDB数据库?MongoDB有一个可以通过控制台访问的脚本,然而通过脚本访问并不是很友好,所以为了开发变得简单,我们将用到一个免费的工具——RboMongo。登陆到 http://robomongo.org 然后下载适合你操作系统的安装程序。
启动RoboMongo。你将看见一个用来连接MongoDB服务器的对话框
点击顶部的 Create按钮。
将连接名称改为 localhost。注意连接地址指向的是localhost:27017。默认情况下MongoDB将在27017端口上启动。
如果你点击 Test按钮,你可能看到一个类似这样的错误“Authorization skipped by you”。不用担心,忽略这个错误连接你本地的MongoDB。
保存配置的连接。回到 Connect对话框。连接 localhost。
在 View菜单中,勾选 Explorer选项。现在你的RoboMongo看起来应该是这样:
在 Explorer面板,右键 localhost选择 Create Database。命名数据库为 vidzy。展开 vidzy,右键 Collections然后点击 Create Collection。在MongoDB中,一个集合就类似于关系型数据库中的一张表。将这个集合命名为 videos。然后这个集合就显示在了列表中。
接下来右键 videos 集合然后选择 Insert Document**。1个文档再MongoDB中类似关系型数据库中的1条记录。而MongoDB文档与之不同的是可以包含其它的文档。在Mongo中,我们使用JSON格式来展现文档。复制粘贴下面的代码到对话框中来新增一个视频文档:
{
"title" : "Terminator Genisys",
"genre" : "SciFi",
"description" : "When John Connor, leader of the human resistance, sends Sgt. Kyle Reese back to 1984 to protect Sarah Connor and safeguard the future, an unexpected turn of events creates a fractured timeline."
}
注意:在粘贴这些代码之前请确保对话框中的内容已经清除,不然可能会得到一个非JSON对象。
重复上一步继续添加两个文档到videos集合:
【校正:原译文这里两段代码是相连的,应该是分开的,否则全部复制了去添加到数据库会出错。当然懂json的人知道怎么做,主要是新手可能在这里迷糊了。】
{
"title" : "The Lord of the Rings",
"genre" : "Fantasy",
"description" : "A meek hobbit of the Shire and eight companions set out on a journey to Mount Doom to destroy the One Ring and the dark lord Sauron."
}
{
"title" : "Apollo 13",
"genre" : "Drama",
"description" : "NASA must devise a strategy to return Apollo 13 to Earth safely after the spacecraft undergoes massive internal damage putting the lives of the three astronauts on board in jeopardy."
}
现在右键videos集合并选择 View Documents。你将在 videos集合中看见3个文档。
注意到每个文档都有一个由MongoDB自动生成的ID。
大功告成!我们的数据库准备就绪。现在,让我们用Express创建一个API来获取这些视频文档。
在这个步骤中,你将学习到关于Node模块系统,Express路由和用Monk从MongoDB中获取数据。
用你最喜欢的代码编辑器在项目根目录中打开app.js。文件中第1部分包含了几个 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');
第2部分引入了我们的路由模块。1个路由模块定义了1个或多个关联的端点以及对应的处理器。在这个由Express Generator生成的示例应用中,我们有两个路由模块: index 和 users:
var routes = require('./routes/index');
var users = require('./routes/users');
让我们看一看其中的一个路由模块,打开 rutes>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个方法或者对象。在这个例子中,这个 express变量是1个对象。它提供了一个叫做 Router的方法,我们在第2行就调用了这个方法。用来访问Express中的路由对象。我们用1个路由来定义我们应用中的端点。我们在这些端点中接收请求。每个端点将会被关联到1个路由处理器,处理器负责处理在端点中收到的请求。
现在看下一行中路由配置的示例。
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
我们使用路由中定义的 get方法来定义1个路由和它的处理器。第1个参数是这个端点;在这里,’/‘代表网站的根路径或者主页。第2个参数就是路由的处理器。
在Express中,所有的路由处理器都有一个同样的签名。第1个参数是请求对象,第2个参数是响应对象,第3个参数是当前链中的下一个处理器。Express使用的中间件函数采用链式调用的方式。当Express使用中间件时,有时候你可能需要在当前链中调用下一个中间件。你可以通过 next 变量实现。但是当我们在处理路由的时候,我们几乎不需要这个操作,所以这里你可以安全地删除 next变量。
现在看一下这个函数体。 res 变量代表了响应对象。这个响应对象自带了一些有用的函数。
在这里,我们渲染index视图,这个视图在 views>index.jade中已经定义了。
这就是一个路由的基础结构。我们现在需要为我们的视频创建一个RESTful API。我们将在1个类似/api/videos的端点中展示我们的视频。
在 routes 目录下创建1个新的路由模块叫做 videos.js ,然后在文件输入以下代码,之后我会对这段代码进行逐行讲解。
var express = require('express');
var router = express.Router();
var monk = require('monk');
var db = monk('localhost:27017/vidzy');
router.get('/', function(req, res) {
var collection = db.get('videos');
collection.find({}, function(err, videos){
if (err) throw err;
res.json(videos);
});
});
module.exports = router;
头两行和之前的一样,我们引入了Express然后获取路由对象。
然后我们引入了Monk,一个用来持久化MongoDB数据的模块。另一个叫做 Mongoose 的模块也可以达到这个效果。但是在本教程中,我更倾向用Monk。
之前曾经提过, require 方法会根据模块的实现方式来返回一个对象或者方法。当我们引入Monk的时候得到的是一个方法而不是对象。所以 monk 变量是一个我们通过调用来访问数据库的方法。
var db = monk('localhost:27017/vidzy');
现在来实现我们路由处理器的逻辑。
function(req, res) {
var collection = db.get('videos');
collection.find({}, function(err, videos){
if (err) throw err;
res.json(videos);
});
}
首先我们调用 db 对象的 get 方法,传入集合的名称(video)。它将返回一个集合对象。这个集合对象提供了一个数字和一些方法来操作集合上的文档。
这里我们使用 find 方法来获取集合中的所有视频。这个方法的第1个参数是一个用来过滤的对象。由于我们需要查询所有视频,我们传入一个空对象。第2个参数是一个回调方法,从数据库返回结果之后调用。这个方法遵循Node回调函数的标准协议模式——“错误优先”。在这个模式中,回调函数的第1个参数是一个错误对象,第2个参数才是返回结果。当你开发更多的Node应用的时候,你将发现更多的这种回调模式。
在这种回调中,我们首先检查 err 对象是否被设置。如果在查询视频文档中没有出现错误, err 的值是 null;否则它将被设置。我们抛出 err 来中断程序的执行并且告知用户。如果没有错误,就通过调用 res.json 简单地返回一个JSON对象。
看最后一行
module.exports = router;
这一行定义了一个对象返回值,当别的模块引用这个模块时我们将返回这个对象。在这种情况下我们返回路由对象给Express。这个模块的主要功能就是获取路由并注册一些路由配置并返回。
现在还剩下一个小步骤。虽然写了一个模块来为我们的新API配置路由信息,但是我们并没有调用它。再次打开 app.js 然后靠近顶部查找以下代码:
var routes = require('./routes/index');
var users = require('./routes/users');
通过以下代码将我们的路由模块引入到应用程序模块。添加下面这行代码。
var videos = require('./routes/videos');
将新模块复制给 videos变量供之后使用。将 app.js 往下滚动一点然后找到下面代码:
app.use('/', routes);
app.use('/users', users);
添加一行代码:
app.use('/api/videos', videos);
这一行代码的作用就是让 videos 模块给任何以 /api/vides开头的路由使用。
现在来测试一下我们的API。打开浏览器输入地址 http://localhost:3000/api/videos 。你将看到以下JSON对象
我使用了 JSONView Chrome插件来高亮显示JSON对象。
接下来几步我们将使用Angular来构建前端代码展示这些视频。
在这一步中,你将学习到Angular的基础知识。如果你已经熟悉Angular,可以跳过这些描述,但是请拷贝这些代码到项目中。
Angular是一个用来构建单页应用(SPA)的非常流行的前端框架。它提供了路由,依赖注入,测试和MVC的结构实现的代码解耦。如果这些听起来太极客了,也不用担心。这一章将带你了解这些特性。
首先,需要添加Angular脚本到应用中。打开 views>layout.jad 添加以下3个脚本文件引用在head的末尾(一般考虑性能会放在body标签的末端)。
【校正:使用国内的cdn服务更稳定】
script(src='https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.5/angular.js')
script(src='https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.5/angular-resource.js')
script(src='https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.5/angular-route.js')
script(src='//cdn.bootcss.com/angular.js/1.4.5/angular.js')
script(src='//cdn.bootcss.com/angular.js/1.4.5/angular-resource.js')
script(src='//cdn.bootcss.com/angular.js/1.4.5/angular-route.js')
确保他们的缩进空格数相等,因为jad对缩进空格非常敏感。Express Generator生成Jade视图的时候将会把两个空格当做缩进。所以你需要遵循同样的缩进并且不能省略。否则将会报错。
这些是什么脚本?第1个是Angular框架的主脚本,第2个是用来调用RESTful APIs,第3个是用来管理路由的。通过路由来定义应用导航对应展示的页面。
下一步,在 public>javascripts 路径下创建一个名为 vidzy.js的文件,在里面编写javascript代码。
在Angular脚本之后,添加 vidzy.js 引用
script(src='/javascripts/vidzy.js')
请再次确保缩进空格数一致。
现在主要脚本已经引入,可以添加Angular到我们的应用了。添加Angular包括两步:
打开 layout.jade 添加 ng-app 到html元素。
doctype html
html(ng-app='Vidzy')
在Jade中,我们使用括号来为HTML标签添加属性。当该行被Jade模板引擎渲染时,我们将获取以下HTML元素
<html ng-app=’Vidzy’>
我们给ng-app设置的值就是应用的名称。现在我们需要创建这个模块。
打开 vidzy.js输入以下代码:
var app = angular.module('Vidzy', []);
Angular现在是一个全局可用的对象, module 方法可以用来定义一个新的模块或者获取已有模块的引用。第1个参数和我们之前在 ng-app 中定义的值一致。第2个参数是一个依赖数组。这里传入一个空数组来声明当前模块不依赖任何其他模块。
我们做这些就是为了将Angular挂载到我们的应用上。接下来我们将重构首页,使用Angular来展示数据库中所有的视频。
Express Generator生成的默认项目使用了Jade做为视图引擎。这些Jade视图在服务端被解析和渲染成HTML然后返回给客户端。这就是很多网页框架如何工作的。但是在本应用中,我们将使用一种不同的构建风格。我们将返回JSON给客户端(Angular)来渲染视图来替代返回HTML。下面说一说这样做的原因。
开始的时候,在本章关于“什么时候使用Node”中,我提到一种通用的场景:Node擅长在文档数据库上构建基于RESTful APIs的应用。通过这种架构,我们不必把时间花在数据转换上。我们存储JSON对象在Mongo中,通过RESTful API导出它们并且直接在客户端进行展示(通过Angular)。JSON就是Javascript和MongoDB的原生对像。所以用它来贯穿整个技术栈,我们就减少了匹配和转换数据的工作。通过从API返回JSON对象然后在客户端渲染视图,来提高性能和可扩展性。因为服务器的CPU将不会被浪费在为大量并发用户渲染视图上。另外,我们可以重用同样的API去构建另一个客户端,例如Iphone和Android app。
在这一步中,我们将用Angular视图来取代首页上默认的Jade视图。
在 public 下创建一个新的叫做 partials 的文件夹用来存储视图文件。在这个文件夹下创建一个新的文件 home.html,在文件中输入
<h1>Home Pageh1>
现在,我们需要告诉Angular当跳转到这个首页时渲染这个视图。我们通过Angular路由来实现这个功能。
在 vidzy.js 中,改变 app 模块的声明如下:
var app = angular.module('Vidzy', ['ngRoute']);
在依赖数组中我添加了一个 ngRoute 的引用。 ngRoute 是构建Angular模块中用来配置路由的。
在 app 模块声明中写下如下代码:
app.config(['$routeProvider', function($routeProvider){
$routeProvider
.when('/', {
templateUrl: 'partials/home.html'
})
.otherwise({
redirectTo: '/'
});
}]);
让我来为你讲解一下。我们使用 app 模块的 config 方法来为我们的应用提供配置。这个代码将在Angular检测到 ng-app 并且视图启动的时候执行。 config 方法的参数是一个数组:
app.config([]);
这个数组可以有0个或更多的依赖以及一个函数来实现配置逻辑。这里我们有一个依赖$routeProvider,这是一个在 ngRoute 中国定义的模块。这就是我们修改app模块声明来依赖ngRoute 的原因。配置函数接收 $routeProvider 作为一个参数
app.config(['$routeProvider', function($routeProvider){
}]);
在我们的配置函数中,我们使用 $routeProvider 的 when 方法来配置路由。
$routeProvider
.when('/', {
templateUrl: 'partials/home.html'
})
第1个参数(‘/‘)是相对路径。第2个参数是一个对象,定义了路径对应的视图(通过templateUrl)。我们可以多次调用 when 方法,每次定义个不同的路由。最后,我们使用otherwise 方法来声明如果用户浏览其他URLs,将被重定向到根路径(‘/‘)。
现在还差一点点。我们只需要做一些小小的改动来时使Jade视图映射到首页。打开views>index.jade 然后改变文件的内容:
extends layout
block content
div(ng-view)
我溢出了视图中之前的内容(Welcom to Express)添加了一个带有 ng-view 的 div。这个属性告诉Angular在当前dom下渲染视图。通过这个设置,用户首次进入主页时,Jade视图将会在服务端渲染并且返回给客户端。在实际项目中,站点视图将有基础的模板()例如导航条,logo等)。它也有一个内容区(通过 ng-view 声明)由Angular来渲染视图。当用户通过应用来访问页面时,Angular将用不同的Angular视图来替换内容区域。这样避免了整个页面的刷新从而带来了更好的效果。这就是我们称这些应用为单页应用的原因:从本质上说只有一个页面被完整地从服务器下载,然后其他子页面只是简单的用来替换内容区。
注意:我必须再一次强调Jade对空格符非常敏感。在同一个视图中你不能混淆空格和tab键的缩进。Express Genrator默认生成的Jade视图用两个空格键来缩进。确保添加两个空格在 div(ng-view) 前面,否则你运行时将报错。
在最后一步之前让我们快速测试一下。回到你的浏览器,输入http://localhost:3000.你将看见我们通过Angular构建的新首页
首页已经被正确地挂载了。现在需要从服务端获取视频并渲染到首页。在Angular或其他MVC中,这是控制器的主要职责。视图只负责展现响应的数据,控制器负责为视图获取数据或者处理视图中发生的事件。
现在为我们的首页视图创建一个控制器。打开 vidzy.js 并且改变 app 模块的声明:
var app = angular.module('Vidzy', ['ngResource', 'ngRoute']);
现在我们依赖两个模块:ngResource,用来调用RESTful APIs和ngRoute来管理路由。
接下来在文件末尾输入以下代码来创建一个控制器:
app.controller('HomeCtrl', ['$scope', '$resource',
function($scope, $resource){
}]);
这里我们使用 app 模块提供的 controller 方法来定义一个新的控制器。
第1个参数是1个字符串,用来定义控制器的名称。按照管理,我们通常在Angular控制器名末尾添加 Ctrl。
第2个参数是数组。这个数组可以引用0个或更多的字符串,每个代表了1个控制器的依赖。这里定义了依赖 $scope 和 resource。这些都是Angular的内部服务,所以她们都有一个前缀”$”。$scope用来传递数据给视图,$resource用来调用RESTful API。数组中最后一个对象是函数,这个函数就是控制器的主体。在这个例子中,我们的函数获取了两个参数 $scope 和$resource 。因为我们依赖了 $scope 和 $resource 在函数声明之前。
让我们实现控制器的主体部分。在控制器函数内部,输入以下代码
app.controller('HomeCtrl', ['$scope', '$resource',
function($scope, $resource){
var Videos = $resource('/api/videos');
Videos.query(function(videos){
$scope.videos = videos;
});
}]);
这里我们调用 $resouce 方法通过给定的API端点(/api/videos)来获取一个资源对象。这个对象将提供一些方法来访问我们的 API。我们用 query 方法来获取所有视频。 query 方法在查询结果就绪后将得到一个回调函数。这个函数将获得我们从服务端获取的视频。最后我们将视频存储到$scope对象中然后渲染到视图中。记住 $scope 就是视图和控制器之间的胶水。
现在我们需要改变视图来渲染视图中的列表。打开 partials>home.html 然后输入代码:
【校正】
<ul>
<li ng-repeat='video in videos' ng-bind="video.title">li>
ul>
<ul>
<li ng-repeat='video in videos'>{{video.title}}li>
ul>
我们用ul和li来渲染视频列表。li标签有一个Angular定义的属性叫做 ng-repeat。这些属性在Angular中叫做指令,用来为HTML元素添加一些行为。ng-repeat属性的值是一个类似js中foreach的表达式。videos变量是我们之前在 $scope中定义的属性。video in videos表示一次从数组中获取一个video。所以li元素将会被数组中的video对象重复渲染。我们用双花括号来编写表达式。这里我们简单的渲染video对象中的title属性在li标签中。
最后我们需要将这个控制器注册到路由上。回到 vidzy.js,改变路由配置
.when('/', {
templateUrl: 'partials/home.html',
controller: 'HomeCtrl'
})
完成了这些步骤,当用户进入网站根路径时,Angular将展示 partials/home.html 并且为它添加HomeCtrl 控制器。
回到浏览器并刷新首页。你将看到视频列表。
如果你是一个Angular新手并且还有些困惑,没关系,在接下来的章节中我们将继续使用更多的Angular控制器,视图和路由。
总结下,本章节中,为我们的应用添加了第一个功能。我们开始用RoboMongo来连接MongoDB。我们创建了一个数据库然后填充了一些视频文档。然后我们创建了一个API用Express来导出视频列表。最后,我们添加Angular到应用中来调用API渲染视频列表。
在下一节中,我们将添加另一个功能到应用中。
这一部分的内容很多,翻译花费了很多时间。原文作者写教程的时候确实是很用心的,这也是我答应作者翻译这篇教程的一个原因。如果想看更多教程可以去作者的官网,如果觉得这篇教程不错,还请点个赞~3q~
在这一部分中,你将学习更多关于在Express中创建API端点,利用Angular构建表单,用Monk在Mongo中存储文档。
类似于上一节,我们将在随后几步中将从前端到后端实现这一功能。首先,我们将创建一个添加视频的API。我们将使用Express路由创建此端点并用Monk存储视频文件在Mongo中。然后,我们将创建一个新的页面来添加一个视频并用Angular来构建这个页面。
让我们开始吧。
打开 routes>videos.js 然后在文件后面和 module.exports 前面新增路由(记住,module.exports 应该是模块中的最后一行):
router.post('/', function(req, res){
var collection = db.get('videos');
collection.insert({
title: req.body.title,
description: req.body.description
}, function(err, video){
if (err) throw err;
res.json(video);
});
});
这些代码和之前有些类似。让我回顾一下重要的部分。首先,注意router.post方法的用法。在最后一节,我们使用router.get方法来处理一个HTTP GET请求。这里,我们使用REST约定中用来创建对象的 HTTP POST请求。
在路由处理器中,首先我们获得了一个 videos 集合的引用,然后使用 insert方法在Mongo中添加一个新的文档。
这个方法的第1个参数是一个JSON对象,它有两个属性: title 和 description。我们用req.body 从这些属性中读取值。它代表数据将被提交到请求的body中。
最后,在回调方法中新增一个文档,如果我们没有获取任何错误,我们使用response(res)的json 方法来返回一个新增的用JSON表示的视频文档。
现在API已经准备好了。我们需要一个表单来新增一个视频。
在 public > partials 目录下创建一个新的视图 video-form.html 。在文件中输入以下代码:
<h1>Add a Videoh1>
<form>
<div>
<label>Titlelabel>
<input>input>
div>
<div>
<label>Descriptionlabel>
<textarea>textarea>
div>
<input type="button" value="Save">input>
form>
现在当用户跳转到 /add-video 时我们需要告诉Angular来展示这个视图。所以需要一个新的路由。
打开 vidzy.js 并且更新路由配置如下:
app.config(['$routeProvider', function($routeProvider){
$routeProvider
.when('/', {
templateUrl: 'partials/home.html',
controller: 'HomeCtrl'
})
.when('/add-video', {
templateUrl: 'partials/video-form.html'
})
.otherwise({
redirectTo: '/'
});
}]);
注意这里我们还没有配置controller,因为我们还没有写controller,我们在下一步中设置。
视图和路由已经准备好了。最后,我们添加一个链接 /add-video 在首页中。打开partials>home.html 在 UL 标签前面添加1个新的链接:
<p>
<a href="/#/add-video">Add a Videoa>
p>
注意在Angular应用中需要在链接前添加 /#。这是为了兼容老的不支持单页应用的浏览器。
让我们预览一下目前为止我们开发的页面。回到浏览器并刷新首页。你将看到添加一个视频的链接。点击链接可以看到添加视频的页面。
不得不说这个表单看起来真的很丑并且和真正应用的表单看起来差别很大。让我们给它一个漂亮、现代化的外观。
我们将使用Bootstrap来为表单添加一些样式。如果你不熟悉Bootstrap,简单解释一下,它是一个用来构建现代和响应式web应用的前端CSS框架。在这个步骤中,我们将引用Bootstrap CSS文件,通过Bootstrap类来装饰我们的表单元素。
打开 views > layout.jade
在 head 标签的最后一行添加
link(rel='stylesheet', href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css')
确认它和上一行有同样的缩进空格数
现在回到 partials > video-form.html。添加下面的类到HTML元素:
<h1>Add a Videoh1>
<form>
<div class="form-group">
<label>Titlelabel>
<input class="form-control">input>
div>
<div class="form-group">
<label>Descriptionlabel>
<textarea class="form-control">textarea>
div>
<input type="button" class="btn btn-primary" value="Save">input>
form>
这些都是标准的Bootstrap用来创建表单的类。更多关于怎样用Bootstrap创建现代表单的例子,请查看Bootstrap 文档
回到浏览器并刷新页面。
现在看起来好多了。
这个表单还没有行为。如果你点击保存按钮的话什么也不会发生,这就是我们下一步将要添加的功能。
正如我之前所说,在MVC框架中,一个控制器主要处理视图中的事件。我们将创建一个Angular控制器来处理保存按钮的点击事件。
打开 vidzy.js 并在文件末尾输入下列代码
app.controller('AddVideoCtrl', ['$scope', '$resource', '$location',
function($scope, $resource, $location){
$scope.save = function(){
var Videos = $resource('/api/videos');
Videos.save($scope.video, function(){
$location.path('/');
});
};
}]);
这个控制器有3个依赖:$scope是控制器和视图之间的胶水,$resource用来调用RESTful API,$location用来改变浏览器地址栏的URL。所有这些已经在Angular服务中构建。
在控制器中,我们在 $scope 上定义 save 方法。这个方法将在用户点击保存按钮时被调用。稍后将它挂载在视图上,先来看看方法中的逻辑。
首先,我们调用 $resource 方法来传递API中的地址(/api/videos),返回一个对象来与API交互。在最后一节中,我们使用 query 方法来获取所有的视频。这里,我们使用 save 方法来提交一个视频给API。
videos.save 方法需要两个参数:用来提交的对象和回调函数(当异步调用执行完成时调用)。在回调函数中,我们使用 $location 服务来修改浏览器地址到网站的根路径。Angular知道根URL是绑定到home视图的。它将展示home页面给用户。
打开 partials > videos-form.html 如下修改input域
<div class="form-group">
<label>Titlelabel>
<input class="form-control" ng-model="video.title">input>
div>
<div class="form-group">
<label>Descriptionlabel>
<textarea class="form-control" ng-model="video.description">textarea>
div>
ng-model属性是另一个用来绑定数据的指令。通过它,我们告诉Angular如果用户点击这个按钮,它将自动更新引用$scope的属性。在第一个例子中,当文本框的值改变时,Angular将自动修改 $scope.video.title。
接下来修改按钮的声明如下:
type="button" class="btn btn-primary" value="Save" ng-click="save()">
ng-click属性也是Angular的另一个用来处理HTML元素点击事件的指令。通过这个指令,我们告诉Angular如果用户点击这个按钮,它将执行 $scope上的 save方法。
最后在路由中注册一个新的控制器
.when('/add-video', {
templateUrl: 'partials/video-form.html',
controller: 'AddVideoCtrl'
})
现在已经完成了,测试一下这个应用。回到浏览器,填写并提交表单。你将在列表中看到一个新的视频。
快速总结一下这一节中学到的知识。我们用Express创建了一个新的API端点然后使用Monk来存储一个视频文档到Mongo。然后,我们创建了一个Angular视图并通过表单添加一个视频。我们通过使用Bootstrap来美化了表单。最后,我们创建控制器来处理视图中的点击事件。在处理点击事件中,我们使用 $resource服务来提交数据给服务端。
在下一节中,我们将添加一个编辑功能。
如果觉得阅读这篇文章有收获,不妨点个赞吧^_^
在这一节中,你将看见另一个API端点,Angular视图,控制器和路由。
我们将以类似的方式从这一部分到最后一个部分。首先,我们将建立两个API端点:一个用于通过ID获取视频,另一个用于更新视频。然后,我们将添加一个链接到主页中的每个视频。当用户点击这个链接,他们将被重定向到一个由视频详情填充的表单。当他们点击保存,更改将被保存然后返回到主页。
下面这一步很熟悉,创建两个新的路由:
GET /api/videos/{id}
PUT /api/videos/{id}
然后打开 routes>videos.js 添加以下路由信息:
router.get('/:id', function(req, res) {
var collection = db.get('videos');
collection.findOne({ _id: req.params.id }, function(err, video){
if (err) throw err;
res.json(video);
});
});
注意这里有一个路由参数,在一个冒号后面声明(:id)。你可以访问这个参数值通过req.params.id。
除此之外,其余的这条路由配置是类似于你以前见过的。唯一的区别是,我们使用集合中的findOne方法来返回一个对象。对该方法的第一个参数是标准对象。因此,我们通过_id等于req.params.id来寻找一个文档。
创建另一个路由:
router.put('/:id', function(req, res){
var collection = db.get('videos');
collection.update({
_id: req.params.id
},
{
title: req.body.title,
description: req.body.description
}, function(err, video){
if (err) throw err;
res.json(video);
});
});
注意我们通过 router.put 来定义这个路由。当这个端点上有HTTP PUT请求时这个处理器将会被调用。
通过集合中的 update 方法来更新一个文档。第一个参数是一个标准对象。只更新 _id 和req.params.id
匹配的文档。第二个参数代表了需要更新的值。
这种rest接口其实非常简单。
首先我们需要为主页上的每个视频添加一个链接。打开 partials>home.html 如下改变LI标签。
【校正】
<li ng-repeat='video in videos'>
<a href="/#/video/">
a>
li>
<li ng-repeat='video in videos'>
<a href="/#/video/{{video._id}}">
{{video.title}}
a>
li>
Angular绑定表达式通过视频ID来渲染动态URL。注意这个URL带有前缀/#
来兼容老的浏览器。
现在我们需要通过一个带有表单的视图来编辑一个视频。现在我们已经有了一个带有表单的视图。所以我们我考虑重用它。
在 vidzy.js 中添加一个新的路由:
.when('/video/:id', {
templateUrl: 'partials/video-form.html'
})
由于我们还没有创建控制器,所以在路由中还不能设置控制器。让我们来回顾一下我们的工作。
回到浏览器主页然后刷新页面。每个视频现在都由一个超链接替代。点击一个视频。你将看见一个空的表单。
下一步我们为表单添加行为:填充表单数据和处理点击保存按钮事件。
现在我们有了2个选择。我们可以创建一个新的控制器,如editvideoctrl,或使用现有的控制器(addvideoctrl)。你认为最好的解决办法是什么?答案是:没有最好的解决办法。需要看情况而定。如果2个案例(添加和编辑)有很多相似之处,则创建一个控制器来处理这两种情况。另一方面,如果两个场景有很大的不同,你会写很多丑陋的条件语句在一个控制器中。这种情况下,最好把它们分为2个不同的控制器。
在写这一步的教程之前,我开始重复使用相同的控制器但是我对最终结果感到不满意。所以,我决定把它们拆分成2个不同的控制器。
在 vidzy.js中,如下创建一个新的控制器:
app.controller('EditVideoCtrl', ['$scope', '$resource', '$location', '$routeParams',
function($scope, $resource, $location, $routeParams){
var Videos = $resource('/api/videos/:id', { id: '@_id' }, {
update: { method: 'PUT' }
});
Videos.get({ id: $routeParams.id }, function(video){
$scope.video = video;
});
$scope.save = function(){
Videos.update($scope.video, function(){
$location.path('/');
});
};
}]);
让我解释一下这里发生了什么。
首先,这个控制器,并不像我们的 AddVideoCtrl 有4个依赖。这里我们有一个额外的依赖:$routeParams 用来访问路由参数。这种情况下,编辑中使用的视频ID将成为路由参数。
在这个控制器内,首先我们使用 $resource 服务来获取一个对象通过和API端点交互。但这一次,我们通过另一种方式来使用 $resource。
var Videos = $resource('/api/videos/:id', { id: '@_id' }, {
update: { method: 'PUT' }
});
传输给端点的第一个参数是URL。这里我们有一个冒号声明的参数(:id)。因为我们在这一节中之前创建的端点都是带有路由参数的,所以我们使用参数化的路由。
GET /api/videos/:id
PUT /api/videos/:id
这个方法的第2个参数是1个对象,为路由参数 :id 提供默认值。
{ id: '@_id' }
这里 ‘@_id’ 告诉Angular在请求对象中查找一个叫做 _id 的属性。所以当我们发送一个PUT请求 /api/videos/:id,Angular将使用视频对象的_id属性来设置路由中的:id参数。
$resource 方法第3个参数用来扩展 $resource 服务。
{
update: { method: 'PUT' }
}
只有Angular的开发者才知道,在某些情况下,默认你不能用$resource服务发送HTTP PUT请求,你需要扩展它通过一个使用HTTP UPT的 update 方法。
下面我们通过给定的ID使用 Videos.get 来获取这个视频。
Videos.get({ id: $routeParams.id }, function(video){
$scope.video = video;
});
现在实现在页面加载的时候填充表单。Videos.get 方法的第一个参数为路由提供了第一个参数:id。我们使用 $routeParams.id 来获取浏览器地址栏中的这个参数。还记得我们为编辑页面定义的路由吗?
.when('/video/:id', {
templateUrl: 'partials/video-form.html',
})
这里我们使用一个路由参数(:id)。然后我们通过 $routeParams 来访问它。
在 Videos.get 的回调方法中,我们从服务端获得返回的视频然后将它存储在 $scope 中。同时通过Angular的双向数据绑定在后台运行,这个表格将会被我们的视屏对象自动填充。还记得 ng-model 吗?我们将输入域绑定到$scope对象的属性上。任何在输入域上发生的改变将会反射到$scope,反之亦然。
最后在控制器中,我们定义一个 save 方法,当保存按钮被点击时调用。
$scope.save = function(){
Videos.update($scope.video, function(){
$location.path('/');
});
}
注意这里,我们用 Videos.update 来替代 Videos.save。这是我们早在扩展 $resource 服务的时候就定义的新方法。这里将发送一个HTTP PUT请求给我们的API端点。
到这里差不多完成了。新控制器已经写好了。我们只需要添加引用到路由配置中。如下改变路由配置:
.when('/video/:id', {
templateUrl: 'partials/video-form.html',
controller: 'EditVideoCtrl'
})
让我们测试这个新功能。返回到浏览器,刷新主页。选择一个视频。做一些修改,点击保存按钮。一切都应该工作。
在下一节中,我们将添加一个功能,来使我们的视频租赁商店的应用程序具有完整的增删改查功能。
如果觉得阅读这篇文章有收获,不妨点个赞吧^_^
在本节中,你将学习如何通过Monk删除文档。你也会再一次回顾你所学到的关于Express和Angular的知识。到这一节的结尾,所有的知识点都会像拼图一样放在一起。
我们将在后面几步中实现这一功能。从服务器开始,我们将建立一个用于删除视频文档的接口。然后,我们将在列表中为每个视频前面添加一个删除链接。当用户点击这个链接,他们将被重定向到一个页面,在那里他们可以看到视频的细节。我们将在该页上有一个确认删除按钮。一旦他们点击这个按钮,页面将调用API端点,跳转带回的视频列表。
但在我们实现功能之前,我有一个建议。我认为你所学的到目前为止,应该能够独自实现这一功能。我想让你花10~15分钟来实现这个功能,作为一个练习,以检验你所学到的。然后,你可以回顾你的解决方案与我的对比,看看是否有任何改进的空间。在你开始之前,请注意:
打开 routes>videos.js 然后添加一个新的路由:
router.delete('/:id', function(req, res){
var collection = db.get('videos');
collection.remove({ _id: req.params.id }, function(err, video){
if (err) throw err;
res.json(video);
});
});
这些代码和之前的差不多,唯一的区别是我们使用了 router.delete 来为HTTP DELETE请求注册一个路由处理器。
同时注意我们这里使用了视频文档对象的 remove 方法。第1个参数你可能已经猜到了,正是一个标准对象。
回到 partials > home.html,在每一个视频链接前添加一个删除链接:
【校正】
<li ng-repeat='video in videos'>
<a href="/#/video/">
a>
<a href="/#/video/delete/">
<i class="glyphicon glyphicon-remove">i>
a>
li>
<li ng-repeat='video in videos'>
<a href="/#/video/{{video._id}}">
{{video.title}}
a>
<a href="/#/video/delete/{{video._id}}">
<i class="glyphicon glyphicon-remove">i>
a>
li>
在这里用来渲染一个图标。glyphicon和glyphicon-remove都是Bootstrap用来绘制图标的类。Bootstrap包括几十个用来构建应用的现代图标。为了看到所有图标的列表,可以去这里。绘制一个图标,你需要使用两个CSS类。一是glyphicon(所有图标的基础类),另一个就是你使用的类(glyphicon-remove)。在Bootstrap文档中,你可以在每个图标下面看到这个CSS类。
点击图标页面将跳转到【校正】 /#/video/delete/{{video._id}}。让我们创建一个试图并为它注册一个路由。
在 partials 文件夹中添加一个叫做 video-delete.html 的新文件。文件中写下如下代码:
<h1>Delete Videoh1>
<p>
Are you sure you want to delete this video?
p>
<ul>
<li>Title: li>
<li>Description: li>
ul>
<input type="button" value="Yes, Delete" class="btn btn-danger" ng-click="delete()" />
<a class="btn btn-default" href="/#/">No, Go Backa>
这里并没有什么特别的。用UL和LI来展示视频的不同属性。在真实的应用中,你可能需要一个更加复杂的标签来实现。
这里注意一下,我为删除按钮添加了一个 btn-danger 类来让它变成红色,为返回按钮添加了btn-default 让它变成白色。
当我们完成这些步骤之后,页面看起来是这个样子的:
接下来,我们需要为视图编写一个控制器。在控制器中,我们将调用API来获取视频的详细信息并加载到页面上。当用户点击删除按钮我们将调用API来删除这个视频。
打开 Vidzy.js 并创建这个控制器:
app.controller('DeleteVideoCtrl', ['$scope', '$resource', '$location', '$routeParams',
function($scope, $resource, $location, $routeParams){
var Videos = $resource('/api/videos/:id');
Videos.get({ id: $routeParams.id }, function(video){
$scope.video = video;
})
$scope.delete = function(){
Videos.delete({ id: $routeParams.id }, function(video){
$location.path('/');
});
}
}]);
最后我们创建一个路由来注册视图和控制器。添加路由到应用的配置中:
.when('/video/delete/:id', {
templateUrl: 'partials/video-delete.html',
controller: 'DeleteVideoCtrl'
})
现在已经完成了。测试一下这个删除功能。回到浏览器并且刷新首页。点击删除图标。删除一个视频。这个视频将被删除然后调回首页。
如果你按照教程做到这一步了,你已经证明了你对学习新事物充满热情。这非常不错~我希望你喜欢这个教程并且能学习到Node,Express,Angular和MongoDB的基础知识。
下一步是什么?Node不仅仅是你目前所学的这些,这就是为什么我计划创建一个全面的课程,来教你使用Node做许多令人惊奇的事情。我将重点教会你:
如果你很喜欢我的教学风格并且想从我这里学习更多,欢迎订阅我的时事通讯。一旦我的课程准备好了将发送通知给你。
教程中提到的代码已上传到github:
https://github.com/yalishizhude/Vidzy
如果觉得阅读这篇文章有收获,不妨点个赞吧^_^