หรือที่เรียกกันว่า MEAN Stack หนึ่ง
การก้าวกระโดดจากการเขียน "สวัสดีชาวโลก" ในคอนโซลของคุณไปเป็นการสร้างแอปพลิเคชันหน้าเดียวมักจะดูน่ากังวล แม้ว่าเส้นทางนี้จะเป็นเส้นทางที่มีลมแรงและมีทางเบี่ยง กับดักทราย และแหล่งข้อมูลที่ดูเหมือนไม่มีที่สิ้นสุด แต่ก็เป็นเส้นทางที่เข้าใจง่าย เมื่อคุณมีความเข้าใจเกี่ยวกับการโต้ตอบระหว่างไคลเอ็นต์-เซิร์ฟเวอร์-ฐานข้อมูลแล้ว สิ่งที่คุณต้องทำก็แค่นำงานของคุณไปใช้ ในซีรีส์แอปหน้าเดียวนี้ เราจะสร้างแอปพลิเคชันเดียวกันโดยใช้เฟรมเวิร์ก JavaScript ที่หลากหลาย ในการทำเช่นนั้น ฉันหวังว่าจะชี้แจงความเข้าใจของคุณเกี่ยวกับ "กรอบงาน MVC" รวมทั้งให้พื้นฐานแก่คุณในการช่วยพัฒนาแอปพลิเคชันของคุณ โดยสรุปสั้นๆ กรอบงาน MVC ช่วยให้โปรแกรมเมอร์มีวิธี “แยกข้อกังวล” เพื่อแบ่งส่วนส่วนประกอบและฟังก์ชันต่างๆ อย่างชัดเจน ในการทำเช่นนั้น เราสามารถสร้างแอปที่ชัดเจนในแอปพลิเคชันและฟังก์ชันได้ ขั้นแรก เราจะอธิบายวิธีสร้างแอปพลิเคชัน Angular หน้าเดียวพื้นฐานโดยใช้สแต็ก MEAN (MongoDB สำหรับฐานข้อมูลของเรา NodeJS/Express สำหรับเซิร์ฟเวอร์ และ AngularJS สำหรับเฟรมเวิร์กส่วนหน้าของเรา) โพสต์นี้เป็นโพสต์แรกในชุดโพสต์เกี่ยวกับวิธีสร้างรายการสิ่งที่ต้องทำโดยใช้เฟรมเวิร์กส่วนหน้าและส่วนหลังที่หลากหลาย หากต้องการดูวิธีสร้างแอปแบบเต็มสแต็กโดยใช้ React คลิกที่นี่
เครื่องมือและการขึ้นต่อกัน
ในการเริ่มต้น คุณจะต้องติดตั้งการขึ้นต่อกันต่อไปนี้:
- Node
- Express
- MongoDB< br /> - AngularJS
คุณสามารถทำได้โดยการรันคำสั่งต่อไปนี้ในเทอร์มินัลของคุณ:
npm install angular express body-parser ejs mongojs npm install bootstrap mongodb --save npm install -g nodemon
คำสั่งเหล่านี้จะติดตั้งและบันทึกการขึ้นต่อกันลงใน repo ของคุณ (ในกรณีนี้ ejs จะถูกใช้เป็นระบบเทมเพลตของเราสำหรับเซิร์ฟเวอร์ในการแสดงผลส่วนประกอบส่วนหน้า) Nodemon เป็นส่วนขยายเรียบร้อยที่จะโหลดเซิร์ฟเวอร์ของคุณทุกครั้งที่มีการเปลี่ยนแปลงไฟล์ ดังนั้นคุณจึงไม่จำเป็นต้องรีสตาร์ทเซิร์ฟเวอร์ตลอดเวลา
โครงสร้างแอปพลิเคชัน
เมื่อสิ้นสุดบทแนะนำนี้ โครงสร้างแอปพื้นฐานของเราควรมีลักษณะดังนี้:
หากคุณต้องการอ้างอิงผลิตภัณฑ์สำเร็จรูป ณ เวลาใดก็ตาม คุณสามารถคัดลอก "เทมเพลต Angular ที่นี่" ได้ตามใจชอบ
ขั้นตอนที่ 1 — สร้างเซิร์ฟเวอร์
อ่า ผืนผ้าใบว่างเปล่า นี่เป็นส่วนที่น่าตื่นเต้นที่สุดของกระบวนการนี้ เราจะเริ่มต้นแอปพลิเคชันหน้าเดียวโดยการสร้างไฟล์ server.js
พื้นฐาน เนื่องจากเราจะใช้ Node กับ Express สำหรับเซิร์ฟเวอร์ของเรา เราจะต้องนำเข้าการอ้างอิงที่เกี่ยวข้องทั้งหมดก่อน
// server.js const express = require('express'); const bodyParser = require('body-parser'); const path = require('path'); const index = require('./routes/index'); const tasks = require('./routes/tasks'); const app = express(); let port = process.env.PORT || 3000 // sets a relative port
เมื่อเราสร้างอินสแตนซ์เซิร์ฟเวอร์ Node/Express ของเราแล้ว เราจะต้องตั้งค่าพาธของไฟล์ไปยังไดเร็กทอรีไคลเอนต์ของเรา และใช้ bodyParser เพื่อเข้ารหัสคำขอและการตอบกลับของเราอย่างถูกต้อง นอกจากนี้เรายังต้องการตั้งค่าเอ็นจิ้นการดูของเราเพื่อระบุเส้นทางที่ควรแสดงผลเมื่อได้รับคำขอ API เฉพาะ
// server.js // Setup View Engine app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'ejs'); //specifies the engine we want to use app.engine('html', require('ejs').renderFile); //renders files with html extension // Set Static Folder app.use(express.static(__dirname, 'client')); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({extended: false})); app.use('/', index); //sets our home page route app.use('/api', tasks); //sets our api call routes //Starts our server app.listen(port, function() { console.log('We have successfully connected to port: ', port); });
ขั้นตอนที่ 2 — การกำหนดเส้นทางพื้นฐาน
เราได้สร้างเซิร์ฟเวอร์ของเราแล้ว แต่จะมีความหมายน้อยมากหากเราไม่มีการตั้งค่าไฟล์และเส้นทางด้วย มาสร้างโฟลเดอร์ใหม่ชื่อ routes
พร้อมไฟล์ index.js
และไฟล์ tasks.js
ไฟล์เหล่านี้จะดูค่อนข้างคล้ายกันในตอนนี้:
// index.js const express = require('express'); const router = express.Router(); // Set Route for Home router.get('/', function(req, res, next) { res.render('index.html'); }); module.exports = router; // tasks.js const express = require('express'); const router = express.Router(); // Set Route for Tasks router.get('/tasks', function(req, res, next) { res.send('this is our tasks API'); }); module.exports = router;
ในการเรนเดอร์ข้อมูลเฉพาะและเชื่อมต่อไคลเอนต์ของเรากับเซิร์ฟเวอร์ เราจะต้องสร้างโฟลเดอร์ views
ของเราด้วย โฟลเดอร์นี้จะมีไฟล์ index.html
หลัก
//index.html <!DOCTYPE html> <html lang="en"> <head> <title>My AngularJS App</title> <link rel="stylesheet" href="./node_modules/bootstrap/dist/css/bootstrap.min.css"> <link rel="stylesheet" href="./styles/styles.css"> <script src="./node_modules/angular/angular.js"></script> </head> <body> <app ng-app="angular-app"> <h5><br/><br/>this message will disappear when <em>app</em> component is correctly rendered<br/><br/><br/></h5> </app> <script src="./client/components/app.js"></script> <script src="./client/components/addTask.js"></script> <script src="./client/components/taskList.js"></script> <script src="./client/components/taskListEntry.js"></script> </body> </html>
ตอนนี้เราจะต้องสร้างไฟล์ในโฟลเดอร์ client/components
โดยมีไฟล์ html ที่เกี่ยวข้องแต่ละไฟล์ใน client/templates
ขั้นตอนที่ 3 —สร้างส่วนหน้า
เราจะเริ่มต้นด้วยไฟล์ app.js
ของเรา ตามแบบแผนเชิงมุม ไฟล์ app.js
นี้เป็นองค์ประกอบหลักของส่วนประกอบอื่นๆ ทั้งหมด สิ่งแรกที่เราต้องทำคือตั้งค่าโมดูลเชิงมุมของเราเป็น ng-app ที่เราประกาศในไฟล์ index.html
ของเรา การตั้งค่า "โมดูลเชิงมุม" ที่ด้านบนของส่วนประกอบแต่ละส่วนจะทำให้เราสามารถเข้าถึงและเชื่อมโยงข้อมูลเฉพาะส่วนประกอบได้ทุกที่ที่เราต้องการ เมื่อเราทำสิ่งนี้เสร็จแล้ว เราก็สามารถเริ่มตั้งค่าส่วนประกอบแต่ละส่วนของเราได้ เรารู้ว่าแต่ละไฟล์ส่วนประกอบจะมี templateUrl เชื่อมโยงอยู่ด้วย นอกจากนี้เรายังทราบด้วยว่าแต่ละส่วนประกอบมีตัวควบคุมเฉพาะของตัวเอง และส่วนประกอบย่อยแต่ละส่วนจะสืบทอดข้อมูลจากส่วนประกอบหลักผ่านวิธีการรวม เนื่องจากเราจะสร้างรายการสิ่งที่ต้องทำ เราจึงรู้ว่าเราจำเป็นต้องมีวิธีจัดเก็บข้อมูลของเรา ด้วยเหตุนี้ app.js
ของเราจึงควรมีลักษณะดังนี้:
//app.js angular.module('angular-app', []) .component('app', { templateUrl: 'client/templates/app.html', controller: function($http) { //ensures that the this scope isn't lost let $ctrl = this; $ctrl.tasks = []; $http.get('/api/tasks').then(function(res) { res.data.forEach((obj) => { $ctrl.tasks.push(obj); }) }) } }); //app.html <div id="app container" > <nav class="navbar"> <div class="col-md-6 col-md-offset-3"> <add-task tasks="$ctrl.tasks"><h5><em>addTask</em> component goes here</h5></add-task> </div> </nav> <div class="row"> <div class="col-md-7"> <task-list tasks="$ctrl.tasks"><h5><em>taskList</em> component goes here</h5></task-list> </div> <div> </div>
เทมเพลตนี้มีข้อมูลที่ยังไม่เกี่ยวข้องมากนัก แต่จะเป็นเมื่อเราเริ่มต้นกับส่วนประกอบย่อยของเรา สังเกตว่าเราส่งผ่านอาเรย์งานที่เราตั้งค่าไว้ในไฟล์ app.js
ของเราอย่างไร เพื่อให้คอมโพเนนต์ลูกสามารถเข้าถึงและแก้ไขอาเรย์ได้เมื่อจำเป็น หากเราต้องเริ่มต้นเซิร์ฟเวอร์และเปิด localhost:3000 เราจะไม่เห็นข้อความจากไฟล์ index.html
ของเราอีกต่อไป แต่เราจะเห็นข้อความใหม่สองข้อความที่เรามีในไฟล์ app.html
ของเราแทน นอกจากนี้ หากเราเปิดคอนโซลนักพัฒนาซอฟต์แวร์ เราจะเห็นข้อความจากคำขอรับของเราระบุว่า this is our API
ยอดเยี่ยม! ตอนนี้เราได้ตั้งค่าพื้นฐานของแอปแล้ว เราก็สามารถเริ่มเจาะลึกลงไปอีกหน่อยได้ เริ่มต้นด้วยการสร้างไฟล์ addTask.js
ในโฟลเดอร์ส่วนประกอบของเรา และไฟล์ addTask.html
ที่เกี่ยวข้องในโฟลเดอร์เทมเพลต ไฟล์นี้จะแชร์คุณลักษณะต่างๆ มากมายเช่นเดียวกับไฟล์ app.js
ของเรา แต่เราจะต้องตรวจสอบให้แน่ใจว่าได้ตั้งค่าการเชื่อมโยงอย่างถูกต้อง เพื่อที่เราจะได้สามารถทำการเปลี่ยนแปลงในรายการของเราได้โดยตรง ไฟล์นี้จะจัดการกับการเพิ่มงานในรายการของเราเป็นหลัก ด้วยเหตุนี้ เราจะต้องเพิ่มฟังก์ชันเฉพาะลงในคอนโทรลเลอร์ของเรา เรายังต้องการให้แน่ใจว่าไม่สามารถส่งงานที่ว่างเปล่าได้ และงานนั้นไม่ควรทำซ้ำ ในการทำเช่นนั้น เราจะต้องสร้างฟังก์ชันตัวช่วยเล็กๆ น้อยๆ เพื่อทำการตรวจสอบขั้นพื้นฐานเหล่านี้:
//addTask.js angular.module('angular-app') .component('addTask', { templateUrl: 'client/templates/addTask.html', controller: function($http) { let $ctrl = this; let dup = false; // checks our list for duplicates let checkList = function(tasks, task) { tasks.forEach((item) => { if (item.task === task) { dup = true; } }); } $ctrl.addTask = (task) => { // checks if the task is blank if (!task) { return alert('please enter a task'); } checkList($ctrl.tasks, task); // will return a true dup if the input already exists if (dup) { dup = false; return alert('that task already exists'); } // sets our task to an object that can be posted task = { task: task, editing: false //we will use this to edit tasks later }; $http.post('/api/tasks', task) .then((res) => { $ctrl.tasks.push(res.data); //adds a task to the task list $ctrl.task = ''; //sets the input field blank again }) } }, bindings: { tasks: '<' } }); //addTask.html <div class="search-bar form-inline"> <input ngclass="form-control" placeholder="Add a task here!" ng-model="$ctrl.task" type="text" /> <button class="btn" ng-click="$ctrl.addTask($ctrl.task)"> <span class="glyphicon glyphicon-plus-sign"></span> add task </button> </div>
ในไฟล์ html ของเรา เรากำลังประกาศโมเดลอินพุตของเราเพื่อที่เราจะได้เข้าถึงค่าอินพุตในฟังก์ชัน $ctrl.addTask ที่เราประกาศในคอมโพเนนต์ของเรา สำหรับข้อมูลเพิ่มเติมเกี่ยวกับส่วนประกอบ การผูก ฯลฯ... ลองดูที่ "โพสต์ที่ยอดเยี่ยม" นี้!
แม้ว่าเราจะสร้างวิธีการโพสต์ของเราแล้ว คุณจะสังเกตเห็นว่าข้อมูลไม่ได้ถูกแสดงผล นั่นเป็นเพราะว่าเราไม่ได้ให้โพสต์ของเราแสดงผลบนเพจได้ เรายังไม่ได้ตั้งค่าให้แอปของเราคงข้อมูลที่โพสต์ไว้บนฐานข้อมูล ดังนั้นคำขอ $http.post ของเราจะไม่ไปไหน ก่อนอื่นเราจะแก้ไขปัญหาส่วนหน้า เมื่อเราทำเสร็จแล้วเราจะไปยังส่วนหลังของเรา
ย้อนกลับไปในโฟลเดอร์ส่วนประกอบของเรา เราจะต้องสร้างไฟล์ taskList.js
ของเราในโฟลเดอร์ส่วนประกอบ รวมถึงไฟล์ taskList.html
ที่เกี่ยวข้องในโฟลเดอร์เทมเพลต ไฟล์เหล่านี้จะค่อนข้างพื้นฐาน เนื่องจากเป็นเพียงวิธีง่ายๆ สำหรับเราในการส่งข้อมูลไปยังแต่ละรายการงาน:
//taskList.js angular.module('angular-app') .component('taskList', { templateUrl: 'client/templates/taskList.html', controller: function() {}, bindings: { tasks: '<' } }); <ul class="task-list"> <task-list-entry ng-repeat="task in $ctrl.tasks track by task._id" tasks="$ctrl.tasks" task="task"></task-list-entry> </ul>
อย่างที่คุณเห็น ไฟล์เหล่านี้ทำหน้าที่ถ่ายโอนข้อมูลเฉพาะไปยังรายการสิ่งที่ต้องทำแต่ละรายการ ด้วยเหตุนี้ เราจึงสามารถสร้างองค์ประกอบสุดท้ายของเราได้ นั่นคือไฟล์ taskListEntry.js
และไฟล์ taskListEntry.html
ในที่นี้เราจะระบุว่าเราต้องการเรนเดอร์รายการสิ่งที่ต้องทำของเราอย่างไร รวมถึงคุณสมบัติที่เราต้องการให้แต่ละรายการมี เรารู้ว่าเราต้องการแก้ไขรายการสิ่งที่ต้องทำที่มีอยู่ และเรายังต้องการลบรายการใดๆ ที่เราได้ทำเสร็จแล้วด้วย นี่คือจุดที่คุณสมบัติการแก้ไขงานของเราจะเข้ามามีบทบาท:
//taskListEntry.js angular.module('angular-app') .component('taskListEntry', { templateUrl: 'client/templates/taskListEntry.html', controller: function($http) { $ctrl = this; $ctrl.toggleEdit = function(task) { //sets the edit value to the opposite of what it currently is task.editing = !task.editing; } $ctrl.updateTask = function(task) { // checks for empty edit values if (!task.task) { return alert('please enter a task'); } task.editing = false; task = { task: task } // send our update request $http.put('/api/tasks/' + task.task._id, task).then((res) => { $ctrl.toggleEdit(task); }) } // deletes specific tasks $ctrl.deleteTask = function(task) { $http.delete('/api/tasks/' + task._id) .then((res) => { let i = $ctrl.tasks.indexOf(task); // removes the task from the task list $ctrl.tasks.splice(i, 1); }) } }, bindings: { task: '<', //binding the individual task tasks: '<' //binding the entire task list } });
ขั้นตอนที่ 4 — สร้างฐานข้อมูล
ตอนนี้เราได้ตั้งค่าส่วนหน้าของเราแล้ว เราสามารถดำเนินการต่อและขอคำขอ GET
, POST
, PUT
และ DELETE
เหล่านั้นได้ สิ่งแรกที่เราต้องทำคือซิงค์ฐานข้อมูลของเราเพื่อให้คำขอเหล่านี้ได้รับการจัดการอย่างถูกต้อง เมื่อเราทำเสร็จแล้ว เราก็สามารถเขียนฟังก์ชันตัวจัดการคำขอเฉพาะของเราได้:
// tasks.js const express = require('express'); const router = express.Router(); const mongojs = require('mongojs'); const db = mongojs('tasks', ['tasks']); // Get All Tasks router.get('/tasks', function(req, res, next) { db.tasks.find(function(err, tasks) { if (err) { res.status(404); res.send(err); } res.json(tasks); }); });
นี่ไม่ได้แตกต่างจากที่เราเคยมีมาก่อน แต่อย่างที่คุณเห็น แทนที่จะส่งข้อความตอบกลับ เราจะส่งคืนงานทั้งหมดที่บันทึกไว้ในฐานข้อมูลงาน ตอนนี้เราได้สร้างคำขอ GET
ของเราแล้ว เราก็สามารถเริ่มต้นใน POST
ของเราได้:
// Save Tasks router.post('/tasks', function(req, res, next) { let task = req.body; if (!task) { res.status(404); res.json({ error: 'information is invalid' }); } else { db.tasks.save(task, function(err, task) { if (err) { res.status(404); res.send(err); } res.json(task); }); } });
ต่างจากคำขอ GET
ของเรา คำขอ POST
ไม่ต้องการให้เราตรวจสอบฐานข้อมูล สิ่งเดียวที่เราจะตรวจสอบคือรายการที่ถูกต้อง เมื่อเราดำเนินการตามคำขอ POST
สำเร็จแล้ว เราก็สามารถเริ่มต้นคำขอ DELETE
และ PUT
ของเราได้
// Delete Task router.delete('/tasks/:id', function(req, res, next) { db.tasks.remove({_id: mongojs.ObjectId(req.params.id)}, function(err, task) { if (err) { res.status(404); res.send(err); } res.json(task); }); }); // Update Task router.put('/tasks/:id', function(req, res, next) { let task = req.body.task; let updatedTask = {}; if (task) { updatedTask = task; //this is necessary to prevent overwriting errors delete updatedTask._id; } db.tasks.update({_id: mongojs.ObjectId(req.params.id)}, updatedTask, {}, function(err, task) { if (err) { res.status(404); res.send(err); } res.json(task); }); }); module.exports = router;
หากคุณสงสัยว่ารหัสเฉพาะนั้นมาจากไหน สิ่งสำคัญคือต้องทราบว่าแต่ละรายการที่เพิ่มลงในฐานข้อมูลของเราจะได้รับรหัสที่ไม่ซ้ำกัน ด้วยเหตุนี้ เราจึงสามารถค้นหางานของเราเพื่อหารหัสเฉพาะที่ตรงกับรหัสใดก็ตามที่เราส่งผ่านไปยังเส้นทางของเรา ในกรณีของคำขอลบ เราจะลบรายการใดก็ตามในฐานข้อมูลของเราที่มีรหัสที่ตรงกัน อย่างไรก็ตาม การอัปเดตรายการต้องการให้เราส่งผ่านออบเจ็กต์ที่เราต้องการให้ออบเจ็กต์ดั้งเดิมของเราอัปเดตไป สำหรับข้อมูลเพิ่มเติมเกี่ยวกับวิธีการ mongojs โปรดดูที่ เอกสารประกอบที่นี่
และคุณก็ได้แล้ว! คุณสามารถปรับแต่งและสร้างสไตล์ของคุณเอง หรือคุณสามารถใช้สไตล์ชีตนี้:
@import url("http://netdna.bootstrapcdn.com/bootstrap/3.0.0-rc2/css/bootstrap-glyphicons.css"); body { background: #f1f1f1; } .navbar { background: #fff; margin-bottom: 20px; } .navbar img { height: 40px; width: 50px; } .search-bar { border-radius: 1px; background: #fff; } .search-bar .form-control { border-radius: 2px; width: 80% } .search-bar .btn { border-radius: 2px; border-color: #d3d3d3; background: #f8f8f8; width: 20%; margin-left: -1px; } .search-bar .btn:hover { border-color: #c6c6c6; background: #f0f0f0; box-shadow: 0 1px 0 rgba(0,0,0,0.10); } .task-player { margin-left: 20px; margin-right: 20px; } .task-player-details { background: #fff; padding: 8px; border: 0; box-shadow: 0 1px 2px rgba(0,0,0,.1); margin-top: 10px; } .task-list { background: #fff; padding: 8px; border: 0; box-shadow: 0 1px 2px rgba(0,0,0,.1); } .task-list-entry { font-size: 16px; padding: 8px; } .task-list-entry-title { font-weight: bold; } .task-list-entry-title:hover { color: #e62117; cursor: pointer; } .task-list-entry-detail { font-size: 12px; color: #999; } .loading { padding-left: 20px; } .media-body { padding-left: 20px; } input { border-radius: 5px; } h5 { border: 2px solid red; text-align: center; padding: 10px; }
ขอบคุณสำหรับการตรวจสอบโพสต์นี้ หากคุณพบว่ามีประโยชน์ โปรดอ่านโพสต์ถัดไปเกี่ยวกับวิธีตั้งค่าแอป React แบบเต็มสแต็ก