AKA Itu satu tumpukan BERARTI

Melakukan lompatan dari menulis 'hello world' di konsol Anda ke membuat aplikasi satu halaman seringkali tampak menakutkan. Meskipun jalurnya berangin dengan jalan memutar, jebakan pasir, dan sumber informasi yang sepertinya tak ada habisnya — jalur ini intuitif. Setelah Anda memahami interaksi klien-server-database, yang Anda perlukan hanyalah mengimplementasikan pekerjaan Anda. Dalam seri Aplikasi Satu Halaman ini, kita akan membuat aplikasi yang sama menggunakan berbagai kerangka JavaScript. Dengan melakukan hal ini, saya berharap dapat memperjelas pemahaman Anda tentang kerangka kerja MVC, serta memberi Anda dasar untuk membantu mengembangkan aplikasi Anda. Sebagai rangkuman singkat, kerangka kerja MVC memberi pemrogram cara untuk “memisahkan masalah” untuk mengelompokkan komponen dan fungsinya dengan jelas. Dengan begitu, kita bisa membuat sebuah aplikasi yang jelas penerapan dan fungsinya. Untuk memulainya, kita akan membahas cara membuat aplikasi Angular satu halaman dasar menggunakan tumpukan MEAN (MongoDB untuk Database kita, NodeJS/Express untuk server, dan AngularJS untuk kerangka front-end kita). Postingan ini adalah yang pertama dari rangkaian postingan tentang cara membuat daftar tugas menggunakan berbagai framework front end dan back end. Untuk melihat cara membuat aplikasi full stack menggunakan React, klik di sini.

Alat dan Dependensi
Untuk memulai, Anda perlu menginstal dependensi berikut:
- Node
- Express
- MongoDB< br /> - AngularJS

Anda dapat melakukannya dengan menjalankan perintah berikut di terminal Anda:

npm install angular express body-parser ejs mongojs 
npm install bootstrap mongodb --save
npm install -g nodemon

Perintah ini akan menginstal dan menyimpan dependensi ke repo Anda. (Dalam hal ini, ejs digunakan sebagai sistem templating bagi server untuk merender komponen front end). Nodemon adalah ekstensi rapi yang akan memuat ulang server Anda setiap kali ada perubahan pada file, jadi Anda tidak perlu terus-menerus me-restart server Anda.

Struktur Aplikasi
Di akhir tutorial ini, struktur dasar aplikasi kita akan terlihat seperti ini:

Jika Anda ingin mereferensikan produk jadi kapan saja, silakan mengkloning Template Sudut di sini.

Langkah 1 — Buat Server

AH, kanvas kosong. Sejauh ini, ini adalah bagian proses yang paling menarik. Kami akan memulai aplikasi satu halaman kami dengan membuat file server.js dasar. Karena kita akan menggunakan Node dengan Express untuk server kita, pertama-tama kita perlu mengimpor semua dependensi yang relevan.

// 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

Setelah kita membuat instance server Node/Express, kita perlu mengatur jalur file ke direktori klien dan menggunakan bodyParser untuk mengkodekan permintaan dan tanggapan kita dengan benar. Kami juga ingin menyiapkan mesin tampilan kami untuk menentukan rute mana yang harus dirender setelah menerima permintaan API tertentu.

// 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);
});

Langkah 2 — Perutean Dasar

Kami telah membuat server kami, tetapi itu tidak berarti apa-apa jika kami tidak memiliki file dan pengaturan rute juga. Mari buat folder baru bernama routes dengan file index.js dan file tasks.js. File-file ini akan terlihat sangat mirip untuk saat ini:

// 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;

Untuk merender informasi spesifik dan menghubungkan klien kita ke server, kita juga perlu membuat folder views. Folder ini akan berisi file index.html utama.

//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>

Sekarang kita perlu membuat file di folder client/components dengan masing-masing file html yang sesuai di folder client/templates.

Langkah 3 —Buat Front End

Kami akan memulai dengan file app.js kami. Sesuai konvensi Angular, file app.js ini menjadi komponen induk dari semua komponen lainnya. Hal pertama yang perlu kita lakukan adalah mengatur modul sudut kita ke ng-app yang kita deklarasikan di file index.html kita. Menetapkan modul sudut di bagian atas setiap komponen akan memungkinkan kita mengakses dan menghubungkan informasi spesifik komponen di mana pun kita memerlukannya. Setelah kita melakukan ini, kita dapat mulai mengatur masing-masing komponen. Kita tahu bahwa setiap file komponen akan memiliki templateUrl yang terkait dengannya. Kita juga mengetahui bahwa setiap komponen memiliki pengontrol uniknya sendiri, dan setiap komponen turunan akan mewarisi informasi dari komponen induk melalui metode pengikatan. Karena kita akan membuat daftar tugas, kita tahu bahwa kita memerlukan cara untuk menyimpan informasi kita. Dengan mengingat hal ini, app.js kita akan terlihat seperti ini:

//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>

Templat ini memiliki informasi yang belum terlalu relevan, namun akan menjadi informasi yang relevan setelah kita mulai menggunakan komponen turunan kita. Perhatikan bagaimana kita meneruskan array tugas yang kita atur di file app.js sehingga komponen anak akan dapat mengakses dan memodifikasi array bila diperlukan. Jika kami memulai server kami dan membuka localhost:3000, kami tidak akan lagi melihat teks dari file index.html kami. Sebaliknya, kita akan melihat dua pesan baru yang kita miliki di file app.html kita. Selain itu, jika kami membuka konsol pengembang, kami akan melihat pesan dari permintaan dapatkan yang menyatakan this is our API.

Besar! Sekarang setelah kita menyiapkan dasar-dasar aplikasi, kita dapat mulai mendalami lebih dalam. Mari kita mulai dengan membuat file addTask.js di folder komponen, dan file addTask.html yang sesuai di folder templates. File ini akan memiliki banyak karakteristik yang sama dengan file app.js kita, namun kita perlu memastikan bahwa kita mengatur binding kita dengan benar sehingga kita dapat membuat perubahan langsung pada daftar kita. File ini terutama akan menangani penambahan tugas ke daftar kami. Oleh karena itu, kita perlu menambahkan fungsi spesifik ke dalam pengontrol kita. Kami juga ingin memastikan bahwa tugas kosong tidak dapat diserahkan, dan tugas tidak terulang kembali. Untuk melakukannya, kita perlu membuat fungsi pembantu kecil untuk melakukan pemeriksaan dasar berikut:

//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>

Di sini, di file html kami, kami mendeklarasikan model input kami sehingga kami kemudian dapat mengakses nilai input dalam fungsi $ctrl.addTask yang kami deklarasikan di komponen kami. Untuk informasi lebih lanjut tentang komponen, binding, dll… lihat postingan luar biasa ini!

Meskipun kami membuat metode postingan, Anda akan melihat bahwa informasinya tidak ditampilkan. Itu karena kami belum memberikan postingan kami cara untuk merendernya ke halaman. Kami juga belum menyiapkan aplikasi kami untuk menyimpan informasi yang diposting ke database, jadi permintaan $http.post kami tidak akan kemana-mana. Pertama-tama kami akan berupaya menyelesaikan masalah front-end. Setelah kita selesai melakukannya, kita akan beralih ke bagian belakang kita.

Kembali ke folder komponen, kita perlu membuat file taskList.js di folder komponen, serta file taskList.html yang sesuai di folder template. File-file ini cukup mendasar, karena mereka hanya menyediakan cara mudah bagi kita untuk meneruskan informasi ke setiap entri daftar tugas:

//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>

Seperti yang Anda lihat, file-file ini berfungsi untuk mentransfer informasi spesifik ke masing-masing item tugas. Dengan demikian, sekarang kita dapat membuat komponen terakhir — file taskListEntry.js dan file taskListEntry.html. Di sinilah kita akan menentukan bagaimana kita ingin merender item daftar tugas kita, serta properti apa yang kita inginkan untuk dimiliki setiap item. Kami tahu bahwa kami ingin dapat memodifikasi item tugas yang ada, dan kami juga ingin dapat menghapus item apa pun yang telah kami selesaikan. Di sinilah properti task.editing kita akan berperan:

//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
  }
});

Langkah 4 — Buat Basis Data

Sekarang setelah kita menyiapkan front end, kita dapat melanjutkan dan menghubungkan permintaan GET, POST, PUT, dan DELETE tersebut. Hal pertama yang perlu kita lakukan adalah menyinkronkan database kita sehingga permintaan ini ditangani dengan benar. Setelah kita selesai melakukannya, kita dapat melanjutkan dan menuliskan fungsi penangan permintaan spesifik kita:

// 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);
  });
});

Ini tidak jauh berbeda dari apa yang kami miliki sebelumnya, tapi seperti yang Anda lihat, alih-alih mengirim pesan sebagai tanggapan, kami mengembalikan semua tugas yang disimpan ke database tugas. Sekarang setelah kita menetapkan permintaan GET, kita dapat memulai dengan 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);
    });
  }
});

Berbeda dengan permintaan GET, permintaan POST tidak mengharuskan kami memeriksa database. Satu-satunya hal yang akan kami periksa adalah entri yang valid. Setelah kami berhasil mengimplementasikan permintaan POST kami, kami dapat memulai permintaan DELETE dan PUT kami.

// 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;

Jika Anda bertanya-tanya dari mana id spesifik itu berasal, penting untuk dicatat bahwa setiap item yang ditambahkan ke database kami diberikan id unik. Dengan demikian, kita dapat mencari tugas kita untuk id tertentu yang cocok dengan id apa pun yang kita lewati ke rute kita. Dalam kasus permintaan penghapusan, kami hanya menghapus item mana pun di database kami yang memiliki id yang cocok. Namun, memperbarui entri mengharuskan kita meneruskan objek yang kita inginkan agar objek asli kita diperbarui. Untuk informasi lebih lanjut tentang metode mongojs, lihat dokumentasi di sini.

Dan itu dia! Anda dapat bermain-main dan membuat gaya Anda sendiri, atau Anda dapat menggunakan lembar gaya ini:

@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;
}

Terima kasih telah memeriksa postingan ini. Jika menurut Anda itu berguna, silakan lihat postingan berikutnya tentang cara menyiapkan aplikasi React full stack