美文网首页药物设计我爱编程
MEAN 全栈 (Angular 6) CRUD Web App

MEAN 全栈 (Angular 6) CRUD Web App

作者: 全栈开发之道 | 来源:发表于2018-04-15 19:19 被阅读0次

    序言

    我在《全栈开发之道》一书中,讲述了多个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

    相关文章

      网友评论

        本文标题:MEAN 全栈 (Angular 6) CRUD Web App

        本文链接:https://www.haomeiwen.com/subject/umqrkftx.html