MEAN 全栈 (Angular 6) CRUD Web Application Example

序言

我在《全栈开发之道》一书中,讲述了多个MEAN 全栈的应用实例,近期,不断有读者询问,书中讲述的 AngularJS 很容易理解, 那么如何创建基于 Angular 6 的 MEAN 全栈的增删改查呢?

当然,这里是有一定差别的,不过,只要有了之前的AngularJS基础,便可平滑过渡到 Angular 6。

本章仍然通过国外经典案例来学习,原文如下:
MEAN Stack (Angular 5) CRUD Web Application Example

工程源码下载地址: https://github.com/didinj/mean-stack-angular5-crud

代码与实例讲解

(1)创建一个 Angular 6 工程,并运行成功。 验证你的开发环境是OK的。 具体过程不再赘述。

ng new mean-angular5

(2) Replace Web Server with Express.js
创建 Express 工程,NG1.x 时代, 直接通过 Express generator 命令就可以创建,有了 NG5后,没有自动创建 MEAN 的命令了。 只有通过载入 Express 的方式完成。

在工程所在路径下,执行以下命令,把需要的模块加载进来:

npm install --save express body-parser morgan body-parser serve-favicon

在工程根目录下,创建bin 文件夹,并在bin下创建www 文件,如下

mkdir bin
touch bin/www

在bin/www 文件中,添加以下代码:

#!/usr/bin/env node

/**
 * Module dependencies.
 */

var app = require('../app');
var debug = require('debug')('mean-app:server');
var http = require('http');

/**
 * Get port from environment and store in Express.
 */

var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);

/**
 * Create HTTP server.
 */

var server = http.createServer(app);

/**
 * Listen on provided port, on all network interfaces.
 */

server.listen(port);
server.on('error', onError);
server.on('listening', onListening);

/**
 * Normalize a port into a number, string, or false.
 */

function normalizePort(val) {
  var port = parseInt(val, 10);

  if (isNaN(port)) {
    // named pipe
    return val;
  }

  if (port >= 0) {
    // port number
    return port;
  }

  return false;
}

/**
 * Event listener for HTTP server "error" event.
 */

function onError(error) {
  if (error.syscall !== 'listen') {
    throw error;
  }

  var bind = typeof port === 'string'
    ? 'Pipe ' + port
    : 'Port ' + port;

  // handle specific listen errors with friendly messages
  switch (error.code) {
    case 'EACCES':
      console.error(bind + ' requires elevated privileges');
      process.exit(1);
      break;
    case 'EADDRINUSE':
      console.error(bind + ' is already in use');
      process.exit(1);
      break;
    default:
      throw error;
  }
}

/**
 * Event listener for HTTP server "listening" event.
 */

function onListening() {
  var addr = server.address();
  var bind = typeof addr === 'string'
    ? 'pipe ' + addr
    : 'port ' + addr.port;
  debug('Listening on ' + bind);
}

在工程根目录下,创建一个新的文件 app.js

touch app.js

把以下代码添加到 app.js 文件中:

var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var bodyParser = require('body-parser');

var book = require('./routes/book');
var app = express();

app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({'extended':'false'}));
app.use(express.static(path.join(__dirname, 'dist')));
app.use('/books', express.static(path.join(__dirname, 'dist')));
app.use('/book', book);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;
创建路由文件

在根目录下,创建路由文件:

mkdir routes
touch routes/book.js

在 routes/book.js 文件中,添加以下代码:


var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
  res.send('Express RESTful API');
});

module.exports = router;

注意了, 一个完整的Express 工程已经形成了

我们不再运行 ng serve -o , 而是进入 npm start 时代。这就是典型的 MEAN 工程的节奏!

npm start

运行结果如下。与之前最大的差别是, 网络请求的地址已经变为: http://localhost:3000 端口号不再是 4200 了。

image.png

特别注意
当运行 ng serve -o 时, 在浏览器地址栏输入: htttp://localhost:4200 ,也同样可以出现之前默认的 Angular页面。

加入express 框架后, 解决了后端路由问题。


行文至此,有必要指出: Angular 自身带有路由, 而 Express 也是解决路由。 既然 Angular 自身有路由,那么,为什么还要用到 Express 呢?

你可以这样理解: Angular是前端框架,Angular 所携带的路由是为了解决前端的路由,所谓前端路由,就是页面之间的跳转,通过它,解决了单页面问题。 前端路由并不请求后台服务器,只是在页面之间来回跳转。

而 Express 路由则不然,它解决的是访问后台服务器的路由。

如果仅仅是学习Angular,永远是停留在前端上,它无法解决全栈的问题。

全栈 = Angular + express + node.js + MongoDB。

通过前面的代码,我们在引入 express的同时,也引入了 mongoDB,借助express,对数据库的访问,变得如此简单!

不信,看下路由就清楚了。

在 npm start 启动后, 浏览器地址栏输入: http://localhost:3000/book , 此时出现:

image.png
配置 mongoose

npm install --save mongoose bluebird

在 app.js 文件中添加以下代码:

var mongoose = require('mongoose');
mongoose.Promise = require('bluebird');
mongoose.connect('mongodb://localhost/mean-angular5', { useMongoClient: true, promiseLibrary: require('bluebird') })
  .then(() =>  console.log('connection successful'))
  .catch((err) => console.error(err));

单独开启一个终端窗口, 开启数据库:

sudo mongod

此时,在另一个窗口再次运行 npm start ,这时,会出现
connection successful

说明:

如果你使用内置的mongoose ,会出现以下信息:

(node:42758) DeprecationWarning: Mongoose: mpromise (mongoose's default promise library) is deprecated, plug in your own promise library instead: http://mongoosejs.com/docs/promises.html

这就是为什么添加 bluebird ,并将它注册为 mongoose promise library 的原因。

Create Mongoose.js Model

在工程根目录下,

mkdir models

创建一个 collection, 命名为 Book

touch models/Book.js

在 Book.js 文件中,添加以下代码:

var mongoose = require('mongoose');

var BookSchema = new mongoose.Schema({
  isbn: String,
  title: String,
  author: String,
  description: String,
  published_year: String,
  publisher: String,
  updated_date: { type: Date, default: Date.now },
});

module.exports = mongoose.model('Book', BookSchema);

注意: model/book.js 文件用来创建 mongodb 的collection。

而 routes/book.js 文件用来管理路由, 接下来,开始后台访问的路由配置。

在 routes/book.js ,添加代码如下:

var express = require('express');
var router = express.Router();
var mongoose = require('mongoose');
var Book = require('../models/Book.js');

/* GET ALL BOOKS */
router.get('/', function(req, res, next) {
  Book.find(function (err, products) {
    if (err) return next(err);
    res.json(products);
  });
});

/* GET SINGLE BOOK BY ID */
router.get('/:id', function(req, res, next) {
  Book.findById(req.params.id, function (err, post) {
    if (err) return next(err);
    res.json(post);
  });
});

/* SAVE BOOK */
router.post('/', function(req, res, next) {
  Book.create(req.body, function (err, post) {
    if (err) return next(err);
    res.json(post);
  });
});

/* UPDATE BOOK */
router.put('/:id', function(req, res, next) {
  Book.findByIdAndUpdate(req.params.id, req.body, function (err, post) {
    if (err) return next(err);
    res.json(post);
  });
});

/* DELETE BOOK */
router.delete('/:id', function(req, res, next) {
  Book.findByIdAndRemove(req.params.id, req.body, function (err, post) {
    if (err) return next(err);
    res.json(post);
  });
});

module.exports = router;

再来看下效果:

npm start

此时,在浏览器输入:

http://localhost:3000/book 时, 后台返回的数据是一个空数组: [ ] , 这说明,工作正常。毕竟还没有在数据库添加内容。

image.png

我们完全可以在终端窗口测试后台的响应,而不用切换到浏览器上。

具体来说,另起一个终端窗口

curl -i -H "Accept: application/json" localhost:3000/book

我们在测试 CRUD 的操作, 如果后台返回以下响应数据,说明REST API 工作正常。

HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 2
ETag: W/"2-l9Fw4VUO7kr8CvBlt4zaMCqXZ0w"
Date: Fri, 10 Nov 2017 23:53:52 GMT
Connection: keep-alive

说明,配置数据库的方式有两种:
(1)图形化操作数据库的工具: 比如: Robomongo
(2) 终端指令方式

这里以终端指令方式为例:

curl -i -X POST -H "Content-Type: application/json" -d '{ "isbn":"123442123, 97885654453443","title":"Learn how to build modern web application with MEAN stack","author": "Didin J.","description":"The comprehensive step by step tutorial on how to build MEAN (MongoDB, Express.js, Angular 5 and Node.js) stack web application from scratch","published_year":"2017","publisher":"Djamware.com" }' localhost:3000/book

正常情况下,后台返回以下数据:

HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 415
ETag: W/"19f-sb+GoLr+sWYpk964su4Cw9hiKhc"
Date: Sun, 15 Apr 2018 10:39:32 GMT
Connection: keep-alive

{"_id":"5ad32be4e2d8f11563dd70ad","isbn":"123442123, 97885654453443","title":"Learn how to build modern web application with MEAN stack","author":"Didin J.","description":"The comprehensive step by step tutorial on how to build MEAN (MongoDB, Express.js, Angular 5 and Node.js) stack web application from scratch","published_year":"2017","publisher":"Djamware.com","updated_date":"2018-04-15T10:39:32.822Z","__v":0}

Create Angular 5 Component for Displaying Book List

Create Angular 5 Routes to Book Component

运行结果:

image.png
Create Angular 5 Component for Displaying Book Detail
Create Angular 5 Component for Add New Book

运行结果:

image.png

add

image.png

update 、 delete、 edit 都好用, 如图

image.png

特别注意

NG6 实现了 前端与后台的分离,前端(Angular) 本身是一个应用服务, 而后台(node.js) 也是一个服务。 所以,在启动时,应该启动三个服务:

  • sudo mongod (启动数据库服务器)
  • npm start (启动 Angular 应用)

运行时,必须用 3000 端口,这是 app.js 确定的端口。 此时, Angular 默认的端口 4200 已经不再起作用了。


小结

这个案例,很好地诠释了“路由”的概念:前端路由和后台路由。 单独起一篇来写吧

运行工程遇到的问题

从 github 上下载一个 angular 工程,该怎么运行它呢?

前提: 先启动 mongoDB 数据库

sudo mongod

运行应用程序,如下:
第一步:

npm install

第二步:

npm start

这时候,出现报错很正常, 一个个解决呗。

Cannot find module '@angular-devkit/core'

module 找不见,怎么办? 安装呗。 那么,为什么会出现这种情况呢? 原因是: package.json 工程配置文件中没有这个文件,而编译时,需要这个文件。

npm install @angular-devkit/core --save-dev

这时候,再执行 npm start ,就可以了。

如果还是报错,就得更新 @angular/cli 版本了。如下:

Step1: Edit your package.json changing the line

@angular/cli": "1.6.4"
to

@angular/cli": "^1.6.4"
Step2:

npm update -g @angular/cli
Step3:

npm install --save-dev @angular/cli@latest

Angular APP 的运行

编译成功后, 在浏览器地址栏输入:

http://localhost:3000

运行结果如下:

image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容