使用Node和Angular创建单页Todo应用程序

来自菜鸟教程
跳转至:导航、​搜索

注意: 编辑 #1:删除 ng-init


介绍

今天我们将使用 MEAN(Mongo、Express、Angular、Node)堆栈创建一个非常简单的 Todo 应用程序。 我们将创建:

  • 用于创建和完成待办事项的单页应用程序
  • 使用 Mongoose 在 MongoDB 中存储待办事项
  • 使用 Express 框架
  • 创建 RESTful 节点 API
  • 使用 Angular 作为前端并访问 API

本文已针对 ExpressJS 4.0 进行了更新]。

虽然应用程序很简单,并且本身就是 初级到中级 级别,但这里的概念可以适用于更高级的应用程序。 我们应该关注的最重要的事情是使用 Node 作为 API 和 Angular 作为前端。 让它们一起工作可能会有点混乱,所以本教程应该有助于缓解一些混乱。 系好安全带; 这可能很长。

基本设置

文件结构

我们将保持文件结构非常简单,并将我们的 Node 应用程序的大部分代码放入 server.js 文件中。 在较大的应用程序中,这应该进一步分解为单独的职责。 Mean.io 是一个很好的样板,可以查看最佳实践以及如何分离文件结构。 让我们继续创建我们更简单的文件结构并在我们进行的过程中编辑文件。

        - public            <!-- holds all our files for our frontend angular application -->
        ----- core.js       <!-- all angular code for our app -->
        ----- index.html    <!-- main view -->
        - package.json      <!-- npm configuration to install dependencies/modules -->
        - server.js         <!-- Node configuration -->

安装模块

在 Node 中,package.json 文件保存了我们应用程序的配置。 Node 的包管理器 (npm) 将使用它来安装我们将要使用的任何依赖项或模块。 在我们的例子中,我们将使用 Express(流行的 Node 框架)和 Mongoose(MongoDB 的对象建模)。

    {
      "name"         : "node-todo",
      "version"      : "0.0.0",
      "description"  : "Simple todo application.",
      "main"         : "server.js",
      "author"       : "Scotch",
      "dependencies" : {
        "express"    : "~4.7.2",
        "mongoose"   : "~3.6.2",
        "morgan"     : "~1.2.2",
        "body-parser": "~1.5.2",
        "method-override": "~2.1.2"
        }
    }

现在如果我们运行 npm install,npm 将查看这个文件并安装 Express 和 Mongoose。

节点配置

在我们的 package.json 文件中,我们告诉它我们的主文件是 server.js。 这是我们的 Node 应用程序的主文件,我们将在其中配置整个应用程序。

这是我们将在其中的文件:

  • 配置我们的应用程序
  • 连接到我们的数据库
  • 创建我们的 Mongoose 模型
  • 为我们的 RESTful API 定义路由
  • 为我们的前端 Angular 应用程序定义路由
  • 将应用程序设置为侦听端口,以便我们可以在浏览器中查看它

现在,我们只需为 Express 配置应用程序、我们的 MongoDB 数据库并监听端口。

    // server.js

        // set up ========================
        var express  = require('express');
        var app      = express();                               // create our app w/ express
        var mongoose = require('mongoose');                     // mongoose for mongodb
        var morgan = require('morgan');             // log requests to the console (express4)
        var bodyParser = require('body-parser');    // pull information from HTML POST (express4)
        var methodOverride = require('method-override'); // simulate DELETE and PUT (express4)

        // configuration =================

        mongoose.connect('mongodb://user:password@mongo.onmodulus.net:port/database');     // connect to mongoDB database on modulus.io

        app.use(express.static(__dirname + '/public'));                 // set the static files location /public/img will be /img for users
        app.use(morgan('dev'));                                         // log every request to the console
        app.use(bodyParser.urlencoded({'extended':'true'}));            // parse application/x-www-form-urlencoded
        app.use(bodyParser.json());                                     // parse application/json
        app.use(bodyParser.json({ type: 'application/vnd.api+json' })); // parse application/vnd.api+json as json
        app.use(methodOverride());

        // listen (start app with node server.js) ======================================
        app.listen(8080);
        console.log("App listening on port 8080");

仅仅使用那一点代码,我们现在就有了一个由 Node.js 提供的 HTTP 服务器。 我们还使用 Express 创建了一个应用程序,现在可以使用它的许多好处。 在我们的 app.configure 部分中,我们使用 express 模块向我们的应用程序添加更多功能。

数据库设置

我们将使用托管在 Modulus.io 上的远程数据库。 他们提供优质的服务,并为您提供 15 美元的预付费用,供您根据需要使用。 这对于动态测试和创建数据库非常有用。

Modulus 将提供您需要的数据库 URL,您可以使用 mongoose.connect 连接到它。 就是这样。

启动您的应用程序!

现在我们已经启动了 package.jsonserver.js,我们可以启动我们的服务器并查看发生了什么。 只需进入您的控制台并使用以下命令:

node server.js 现在你有一个监听 8080 端口的服务器。 您在浏览器中的 http://localhost:8080 上看不到任何内容,因为我们没有将应用程序配置为输出任何内容。 但这是一个开始!

文件更改时自动重启服务器:默认情况下,服务器启动后节点不会监控文件更改。 这意味着每次更改文件时都必须关闭并启动服务器。 这可以通过 nodemon 修复。 要使用:全局安装 nodemon npm install -g nodemon。 现在用 nodemon server.js 启动你的服务器。 从此一帆风顺。

申请流程

现在简要概述我们所有活动部件如何协同工作。 此应用程序涉及许多不同的想法和技术,很容易将它们混在一起。

Angular 在前端是独立的。 它通过 Node API 访问它需要的所有数据。 Node 命中数据库并根据 RESTful 路由向 Angular 返回 JSON 信息。

这样,您可以将前端应用程序与实际 API 分开。 如果您想扩展 API,您始终可以在其中构建更多路由和函数,而不会影响前端 Angular 应用程序。 通过这种方式,您最终可以在不同的平台上构建不同的应用程序,因为您只需要使用 API。

创建我们的节点 API

在我们进入前端应用程序之前,我们需要创建我们的 RESTful API。 这将允许我们有一个 api 将 获取所有待办事项创建待办事项完成并删除待办事项 。 它将以 JSON 格式返回所有这些信息。

待办事项模型

我们必须为我们的 Todos 定义我们的模型。 我们将保持简单。 在 配置部分listen 部分之前,我们将添加我们的模型。

        // define model =================
        var Todo = mongoose.model('Todo', {
            text : String
        });

这就是我们想要的。 只是待办事项的文本。 MongoDB 将自动为我们创建的每个待办事项生成一个 _id

RESTful API 路由

让我们生成我们的 Express 路由来处理我们的 API 调用。

    // server.js
    ...

    // routes ======================================================================

        // api ---------------------------------------------------------------------
        // get all todos
        app.get('/api/todos', function(req, res) {

            // use mongoose to get all todos in the database
            Todo.find(function(err, todos) {

                // if there is an error retrieving, send the error. nothing after res.send(err) will execute
                if (err)
                    res.send(err)

                res.json(todos); // return all todos in JSON format
            });
        });

        // create todo and send back all todos after creation
        app.post('/api/todos', function(req, res) {

            // create a todo, information comes from AJAX request from Angular
            Todo.create({
                text : req.body.text,
                done : false
            }, function(err, todo) {
                if (err)
                    res.send(err);

                // get and return all the todos after you create another
                Todo.find(function(err, todos) {
                    if (err)
                        res.send(err)
                    res.json(todos);
                });
            });

        });

        // delete a todo
        app.delete('/api/todos/:todo_id', function(req, res) {
            Todo.remove({
                _id : req.params.todo_id
            }, function(err, todo) {
                if (err)
                    res.send(err);

                // get and return all the todos after you create another
                Todo.find(function(err, todos) {
                    if (err)
                        res.send(err)
                    res.json(todos);
                });
            });
        });

    ...

基于这些路由,我们构建了一个表格来解释前端应用程序应该如何从 API 请求数据。

HTTP 动词 网址 描述
得到 /api/todos 获取所有待办事项
邮政 /api/todos 创建一个待办事项
删除 /api/todos/:todo_id 删除单个待办事项

在我们的每个 API 路由中,我们使用 Mongoose 操作来帮助我们与数据库进行交互。 我们之前使用 var Todo = mongoose.model 创建了模型,现在我们可以使用它来 findcreateremove。 您可以做的事情还有很多,我建议您查看官方的 文档 以了解更多信息。

我们的 API 完成了! 麾! 如果你启动你的应用程序,你可以在 localhost:8080/api/todos 与它进行交互以获取所有待办事项。 由于您没有添加任何内容,因此目前不会有任何内容。

使用 Angular 的前端应用程序

我们创建了一个节点应用程序配置了我们的数据库生成了我们的API路由启动了一个服务器。 已经做了这么多,还有一点点要走!

到目前为止,我们所做的工作可以作为一个应用程序独立存在。 它可以是我们使用的 API,让应用程序和用户连接到我们的内容。

我们希望成为第一个使用我们刚刚创建的全新 API 的人。 这是我上个月学到的最喜欢的术语之一:We will be dogfooding。 我们可以这样对待,因为我们是第一个使用我们新 API 的客户。 我们将保持这个简单,所以我们将只使用我们的 index.htmlcore.js 来定义我们的前端。

定义前端路由

我们已经定义了我们的 API 路由。 我们的应用程序的 API 可以从 /api/todos 访问,但是我们的前端呢? 我们如何在我们的主页显示 index.html 文件?

我们将为前端应用程序添加一条路由到我们的 server.js 文件。 这就是我们需要做的所有事情,因为 Angular 将制作一个单页应用程序并处理路由。

在我们的 API 路由之后,在 app.listen 之前,添加这个路由:

    // server.js
    ...
        // application -------------------------------------------------------------
        app.get('*', function(req, res) {
            res.sendfile('./public/index.html'); // load the single view file (angular will handle the page changes on the front-end)
        });
    ...

当我们点击 localhost:8080 时,这将加载我们的单个 index.html 文件。

设置 Angular core.js

让我们先来看看我们的 Angular 设置。 我们必须创建一个模块创建一个控制器,并且定义处理todos的函数。 然后我们可以申请查看

    // public/core.js
    var scotchTodo = angular.module('scotchTodo', []);

    function mainController($scope, $http) {
        $scope.formData = {};

        // when landing on the page, get all todos and show them
        $http.get('/api/todos')
            .success(function(data) {
                $scope.todos = data;
                console.log(data);
            })
            .error(function(data) {
                console.log('Error: ' + data);
            });

        // when submitting the add form, send the text to the node API
        $scope.createTodo = function() {
            $http.post('/api/todos', $scope.formData)
                .success(function(data) {
                    $scope.formData = {}; // clear the form so our user is ready to enter another
                    $scope.todos = data;
                    console.log(data);
                })
                .error(function(data) {
                    console.log('Error: ' + data);
                });
        };

        // delete a todo after checking it
        $scope.deleteTodo = function(id) {
            $http.delete('/api/todos/' + id)
                .success(function(data) {
                    $scope.todos = data;
                    console.log(data);
                })
                .error(function(data) {
                    console.log('Error: ' + data);
                });
        };

    }

我们创建了 Angular 模块(scotchApp)和控制器(mainController)。

我们还创建了我们的函数来获取所有待办事项创建一个待办事项删除一个待办事项。 所有这些都将触及我们刚刚创建的 API。 在页面加载时,我们将 GET /api/todos 并将我们从 API 接收到的 JSON 绑定到 $scope.todos。 然后,我们将在我们的视图中循环这些以制作我们的待办事项。

对于 创建和删除 ,我们将遵循类似的模式。 运行我们的操作,重新制作我们的待办事项列表。

前端视图 index.html

在这里,我们将保持简单。 这是与 Angular 交互所需的 HTML。 我们会:

  • 分配 Angular 模块和控制器
  • 通过获取所有待办事项来初始化页面
  • 循环处理待办事项
  • 有一个表格来创建待办事项
  • 检查时删除待办事项
    <!-- index.html -->
    <!doctype html>

    <!-- ASSIGN OUR ANGULAR MODULE -->
    <html ng-app="scotchTodo">
    <head>
        <!-- META -->
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1"><!-- Optimize mobile viewport -->

        <title>Node/Angular Todo App</title>

        <!-- SCROLLS -->
        <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css"><!-- load bootstrap -->
        <style>
            html                    { overflow-y:scroll; }
            body                    { padding-top:50px; }
            #todo-list              { margin-bottom:30px; }
        </style>

        <!-- SPELLS -->
        <script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script><!-- load jquery -->
        <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.min.js"></script><!-- load angular -->
        <script src="core.js"></script>

    </head>
    <!-- SET THE CONTROLLER AND GET ALL TODOS -->
    <body ng-controller="mainController">
        <div class="container">

            <!-- HEADER AND TODO COUNT -->
            <div class="jumbotron text-center">
                <h1>I'm a Todo-aholic <span class="label label-info">{{ todos.length }}</span></h1>
            </div>

            <!-- TODO LIST -->
            <div id="todo-list" class="row">
                <div class="col-sm-4 col-sm-offset-4">

                    <!-- LOOP OVER THE TODOS IN $scope.todos -->
                    <div class="checkbox" ng-repeat="todo in todos">
                        <label>
                            <input type="checkbox" ng-click="deleteTodo(todo._id)"> {{ todo.text }}
                        </label>
                    </div>

                </div>
            </div>

            <!-- FORM TO CREATE TODOS -->
            <div id="todo-form" class="row">
                <div class="col-sm-8 col-sm-offset-2 text-center">
                    <form>
                        <div class="form-group">

                            <!-- BIND THIS VALUE TO formData.text IN ANGULAR -->
                            <input type="text" class="form-control input-lg text-center" placeholder="I want to buy a puppy that will love me forever" ng-model="formData.text">
                        </div>

                        <!-- createToDo() WILL CREATE NEW TODOS -->
                        <button type="submit" class="btn btn-primary btn-lg" ng-click="createTodo()">Add</button>
                    </form>
                </div>
            </div>

        </div>

    </body>
    </html>

看看我们有什么。

结论

现在我们有一个完整的应用程序,它将通过 API(我们构建的!)显示、创建和删除所有待办事项。 那是相当充实的一天。 我们做了这么多。 只是我们已经完成的概述:

  • 使用 Express 的 RESTful Node API
  • 使用猫鼬进行MongoDB交互
  • Angular AJAX $http 调用
  • 没有刷新的单页应用程序
  • Dogfooding(对不起,我真的很喜欢这个词)

测试应用程序

继续在 GitHub 上下载代码并对其进行调整或测试。 要让它全部启动并运行:

  1. 确保已安装 Node 和 npm
  2. 克隆仓库:git clone git@github.com:scotch-io/node-todo
  3. 安装应用程序:npm install
  4. 启动服务器:node server.js
  5. 在浏览器中查看 http://localhost:8080

我希望这对如何让许多移动部件一起工作有深刻的见解。 将来,我们将考虑分离我们的 server.js 文件,因为这有点疯狂。

进一步阅读 如果您对更多 MEAN 堆栈应用程序感兴趣,我们编写了一份指南,帮助您开始构建自己的 MEAN 堆栈基础。

设置 MEAN Stack 单页应用程序

本文是我们的 Node 和 Angular To-Do App 系列的一部分。

  1. 使用 Node 和 Angular 创建单页待办事项应用程序
  2. 节点应用组织结构
  3. Angular 模块:控制器和服务