MEAN CRUD - tdkehoe/blog GitHub Wiki
This app is deployed at https://jobsly-angular.firebaseapp.com/#/jobsly/
The finished code is at https://github.com/tdkehoe/jobsly
#Two Directories Make a root directory for the project:
mkdir swords
cd swords
ls
cd into the project root directory and set up two directories, one for Express and one for Angular. The two apps are separate and communicate via an API.
mkdir express-app
mkdir angular-app
ls
#GitHub, readme.md, npm init Set up a GitHub repository and get the URL. Follow the GitHub commands and make a readme.md file in the project root directory.
echo "# Swords-App" >> README.md
git init
git add README.md
git commit -m "First commit."
git remote add origin [email protected]:_username_/Swords-App.git
git push -u origin master
Test with:
grv
#Express App
In the Express directory:
cd express-app
npm init
Change "entry point" to app.js. Add the git repository.
Install Express. Double-check that you're in the express-app directory.
echo "node_modules" > .gitignore
npm install express --save
Set up the Express directory and files.
touch app.js
mkdir routes
cd routes
touch routes.js
cd ..
ls
You should see in the express directory:
├── app.js
├── node_modules
│ └── express
│ ├── History.md
│ ├── LICENSE
│ ├── Readme.md
│ ├── index.js
│ ├── lib
│ │ ├── application.js
│ │ ├── express.js
│ │ ├── middleware
│ │ │ ├── init.js
│ │ │ └── query.js
│ │ ├── request.js
│ │ ├── response.js
│ │ ├── router
│ │ │ ├── index.js
│ │ │ ├── layer.js
│ │ │ └── route.js
│ │ ├── utils.js
│ │ └── view.js
│ ├── node_modules
│ │ ├── accepts
│ │ │ ├── HISTORY.md
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── index.js
│ │ │ ├── node_modules
│ │ │ │ ├── mime-types
│ │ │ │ │ ├── HISTORY.md
│ │ │ │ │ ├── LICENSE
│ │ │ │ │ ├── README.md
│ │ │ │ │ ├── index.js
│ │ │ │ │ ├── node_modules
│ │ │ │ │ │ └── mime-db
│ │ │ │ │ │ ├── HISTORY.md
│ │ │ │ │ │ ├── LICENSE
│ │ │ │ │ │ ├── README.md
│ │ │ │ │ │ ├── db.json
│ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ └── package.json
│ │ │ │ │ └── package.json
│ │ │ │ └── negotiator
│ │ │ │ ├── HISTORY.md
│ │ │ │ ├── LICENSE
│ │ │ │ ├── README.md
│ │ │ │ ├── index.js
│ │ │ │ ├── lib
│ │ │ │ │ ├── charset.js
│ │ │ │ │ ├── encoding.js
│ │ │ │ │ ├── language.js
│ │ │ │ │ └── mediaType.js
│ │ │ │ └── package.json
│ │ │ └── package.json
│ │ ├── array-flatten
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── array-flatten.js
│ │ │ └── package.json
│ │ ├── content-disposition
│ │ │ ├── HISTORY.md
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── index.js
│ │ │ └── package.json
│ │ ├── content-type
│ │ │ ├── HISTORY.md
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── index.js
│ │ │ └── package.json
│ │ ├── cookie
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── index.js
│ │ │ └── package.json
│ │ ├── cookie-signature
│ │ │ ├── History.md
│ │ │ ├── Readme.md
│ │ │ ├── index.js
│ │ │ └── package.json
│ │ ├── debug
│ │ │ ├── History.md
│ │ │ ├── Makefile
│ │ │ ├── Readme.md
│ │ │ ├── bower.json
│ │ │ ├── browser.js
│ │ │ ├── component.json
│ │ │ ├── debug.js
│ │ │ ├── node.js
│ │ │ ├── node_modules
│ │ │ │ └── ms
│ │ │ │ ├── History.md
│ │ │ │ ├── LICENSE
│ │ │ │ ├── README.md
│ │ │ │ ├── index.js
│ │ │ │ └── package.json
│ │ │ └── package.json
│ │ ├── depd
│ │ │ ├── History.md
│ │ │ ├── LICENSE
│ │ │ ├── Readme.md
│ │ │ ├── index.js
│ │ │ ├── lib
│ │ │ │ └── compat
│ │ │ │ ├── buffer-concat.js
│ │ │ │ ├── callsite-tostring.js
│ │ │ │ └── index.js
│ │ │ └── package.json
│ │ ├── escape-html
│ │ │ ├── LICENSE
│ │ │ ├── Readme.md
│ │ │ ├── index.js
│ │ │ └── package.json
│ │ ├── etag
│ │ │ ├── HISTORY.md
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── index.js
│ │ │ └── package.json
│ │ ├── finalhandler
│ │ │ ├── HISTORY.md
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── index.js
│ │ │ ├── node_modules
│ │ │ │ └── unpipe
│ │ │ │ ├── HISTORY.md
│ │ │ │ ├── LICENSE
│ │ │ │ ├── README.md
│ │ │ │ ├── index.js
│ │ │ │ └── package.json
│ │ │ └── package.json
│ │ ├── fresh
│ │ │ ├── HISTORY.md
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── index.js
│ │ │ └── package.json
│ │ ├── merge-descriptors
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── index.js
│ │ │ └── package.json
│ │ ├── methods
│ │ │ ├── HISTORY.md
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── index.js
│ │ │ └── package.json
│ │ ├── on-finished
│ │ │ ├── HISTORY.md
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── index.js
│ │ │ ├── node_modules
│ │ │ │ └── ee-first
│ │ │ │ ├── LICENSE
│ │ │ │ ├── README.md
│ │ │ │ ├── index.js
│ │ │ │ └── package.json
│ │ │ └── package.json
│ │ ├── parseurl
│ │ │ ├── HISTORY.md
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── index.js
│ │ │ └── package.json
│ │ ├── path-to-regexp
│ │ │ ├── History.md
│ │ │ ├── LICENSE
│ │ │ ├── Readme.md
│ │ │ ├── index.js
│ │ │ └── package.json
│ │ ├── proxy-addr
│ │ │ ├── HISTORY.md
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── index.js
│ │ │ ├── node_modules
│ │ │ │ ├── forwarded
│ │ │ │ │ ├── HISTORY.md
│ │ │ │ │ ├── LICENSE
│ │ │ │ │ ├── README.md
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── package.json
│ │ │ │ └── ipaddr.js
│ │ │ │ ├── Cakefile
│ │ │ │ ├── LICENSE
│ │ │ │ ├── README.md
│ │ │ │ ├── bower.json
│ │ │ │ ├── ipaddr.min.js
│ │ │ │ ├── lib
│ │ │ │ │ └── ipaddr.js
│ │ │ │ ├── package.json
│ │ │ │ ├── src
│ │ │ │ │ └── ipaddr.coffee
│ │ │ │ └── test
│ │ │ │ └── ipaddr.test.coffee
│ │ │ └── package.json
│ │ ├── qs
│ │ │ ├── CHANGELOG.md
│ │ │ ├── CONTRIBUTING.md
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── bower.json
│ │ │ ├── lib
│ │ │ │ ├── index.js
│ │ │ │ ├── parse.js
│ │ │ │ ├── stringify.js
│ │ │ │ └── utils.js
│ │ │ ├── package.json
│ │ │ └── test
│ │ │ ├── parse.js
│ │ │ ├── stringify.js
│ │ │ └── utils.js
│ │ ├── range-parser
│ │ │ ├── HISTORY.md
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── index.js
│ │ │ └── package.json
│ │ ├── send
│ │ │ ├── HISTORY.md
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── index.js
│ │ │ ├── node_modules
│ │ │ │ ├── destroy
│ │ │ │ │ ├── README.md
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── package.json
│ │ │ │ ├── http-errors
│ │ │ │ │ ├── HISTORY.md
│ │ │ │ │ ├── LICENSE
│ │ │ │ │ ├── README.md
│ │ │ │ │ ├── index.js
│ │ │ │ │ ├── node_modules
│ │ │ │ │ │ └── inherits
│ │ │ │ │ │ ├── LICENSE
│ │ │ │ │ │ ├── README.md
│ │ │ │ │ │ ├── inherits.js
│ │ │ │ │ │ ├── inherits_browser.js
│ │ │ │ │ │ ├── package.json
│ │ │ │ │ │ └── test.js
│ │ │ │ │ └── package.json
│ │ │ │ ├── mime
│ │ │ │ │ ├── LICENSE
│ │ │ │ │ ├── README.md
│ │ │ │ │ ├── build
│ │ │ │ │ │ ├── build.js
│ │ │ │ │ │ └── test.js
│ │ │ │ │ ├── cli.js
│ │ │ │ │ ├── mime.js
│ │ │ │ │ ├── package.json
│ │ │ │ │ └── types.json
│ │ │ │ ├── ms
│ │ │ │ │ ├── History.md
│ │ │ │ │ ├── LICENSE
│ │ │ │ │ ├── README.md
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── package.json
│ │ │ │ └── statuses
│ │ │ │ ├── LICENSE
│ │ │ │ ├── README.md
│ │ │ │ ├── codes.json
│ │ │ │ ├── index.js
│ │ │ │ └── package.json
│ │ │ └── package.json
│ │ ├── serve-static
│ │ │ ├── HISTORY.md
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── index.js
│ │ │ └── package.json
│ │ ├── type-is
│ │ │ ├── HISTORY.md
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── index.js
│ │ │ ├── node_modules
│ │ │ │ ├── media-typer
│ │ │ │ │ ├── HISTORY.md
│ │ │ │ │ ├── LICENSE
│ │ │ │ │ ├── README.md
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── package.json
│ │ │ │ └── mime-types
│ │ │ │ ├── HISTORY.md
│ │ │ │ ├── LICENSE
│ │ │ │ ├── README.md
│ │ │ │ ├── index.js
│ │ │ │ ├── node_modules
│ │ │ │ │ └── mime-db
│ │ │ │ │ ├── HISTORY.md
│ │ │ │ │ ├── LICENSE
│ │ │ │ │ ├── README.md
│ │ │ │ │ ├── db.json
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── package.json
│ │ │ │ └── package.json
│ │ │ └── package.json
│ │ ├── utils-merge
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── index.js
│ │ │ └── package.json
│ │ └── vary
│ │ ├── HISTORY.md
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── index.js
│ │ └── package.json
│ └── package.json
├── package.json
└── routes
└── routes.js
##Express Modules
Install Express modules. Double-check that you're in the express-app directory.
npm install --save body-parser cors monk
If you didn't set this when installing express, in package.json change "main" from "index.js" to "app.js", and add the GitHub repository. Check that package.json looks like this:
{
"name": "express-app",
"version": "1.0.0",
"description": "Express server for swords app.",
"main": "app.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "[email protected]:tdkehoe/Swords-Assessment-Practice.git"
},
"keywords": [
"express",
"api",
"swords"
],
"author": "Thomas David Kehoe",
"license": "ISC",
"bugs": {
"url": "https://github.com/tdkehoe/Swords-Assessment-Practice/issues"
},
"homepage": "https://github.com/tdkehoe/Swords-Assessment-Practice",
"dependencies": {
"body-parser": "^1.13.3",
"cors": "^2.7.1",
"express": "^4.13.3",
"monk": "^1.0.1"
}
}Double-check that the modules (dependencies) are all there.
##Express App.js
Put this into app.js:
var express = require('express');
var bodyParser = require('body-parser');
var swords = require('./routes/routes') // matches the path javascript/routes/routes.js
var app = express();
var cors = require('cors');
app.use(cors());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use('/api/swords', swords); // the first argument is the name of the database, the second argument matches the routes variable set on line 3
app.listen(process.env.PORT || 8080);
console.log('Server started on localhost://8080');Line 3 must match the path to the routes file. E.g., if the routes are in
/express-app/routes/routes.js
then line 3 must include
'./routes/routes'The "express-app/" and ".js" are left off.
module.exports = app at the end of the file is optional.
##Express routes In the routes file start with:
var express = require('express');
var router = express.Router();
var db = require('monk')('localhost/swords'); // this is the name of the database, make it semantic
var Swords = db.get('swords'); // the name of the database
module.exports = router;You don't need to create the database in Mongo. Mongo will create the database.
Start the Express server from the express-app directory:
nodemon
You should get a console message and see "Cannot GET /" in the browser. Save to GitHub: "Express server starts."
###Seven CRUD routes
Add the seven CRUD routes to routes.js above module.exports = router;:
router.get('/', function(req, res) { // INDEX
Swords.find({}, function(err, swords) {
if (err) {
res.send(err);
}
res.status(200).json(swords); // OK
})
});
router.post('/', function(req, res) { // CREATE
Swords.insert(req.body, function(err, sword) {
if (err) {
res.send(err);
}
res.status(201).json(sword); // Created
});
})
router.get('/new', function(req, res){ // NEW
// goes to forms page for user to enter a new item
// this route isn't needed in Express because it
// doesn't get any data from the database
// just use a href anchor in Angular to go to the forms page
});
router.get('/:id', function(req, res) { //SHOW
Swords.findOne({_id: req.params.id}, function(err, sword) {
if (err) {
res.send(err);
}
res.status(200).json(sword); // OK
})
})
router.get('/:id/edit', function(req, res){ // EDIT (identical to SHOW route)
Swords.findOne({_id: req.params.id}, function(err, sword) {
if (err) {
res.send(err)
}
res.status(200).json(sword) // OK
})
});
router.put('/:id', function(req, res) { // UPDATE
Swords.findAndModify({_id: req.params.id}, req.body, function(err, sword) {
if (err) {
throw err;
}
res.json(req.body);
})
})
router.delete('/:id', function(req, res) { //DESTROY
Swords.remove({_id: req.params.id}, function(err, sword){
if (err) {
throw err;
}
res.status(204).json(sword); // No Content
});
});Note that the responses from the Express server are JSON objects, not webpages. I.e., these routes respond with res.json, not res.render. The Express and Angular apps are separate and communicate via an API.
###Test Routes with Postman
Start mongod. (Starting mongo isn't necessary.)
Check that the Express server is up.
From the Chrome browser open Postman. (Install Postman if you don't already have it.) Test each route.
INDEX:
GET http://localhost:8080/api/swords/Expected response:
[]CREATE:
Go to https://en.wikipedia.org/wiki/List_of_historical_swords and pick a sword.
POST http://localhost:8080/api/swordsClick Body, check x-www-form-urlencoded, and enter a key and a value. Expected response:
{
"swordName": "Legbiter",
"swordOwner": "Magnus Barelegs",
"_id": "54875u4tytuyt0thfo"
}SHOW
GET http://localhost:8080/api/swords/54875u4tytuyt0thfoExpected response:
{
"_id": "54875u4tytuyt0thfo",
"swordName": "Legbiter",
"swordOwner": "Magnus Barelegs"
}EDIT
GET http://localhost:8080/api/swords/54875u4tytuyt0thfo/editExpected response (identical to GET SHOW):
{
"_id": "54875u4tytuyt0thfo",
"swordName": "Legbiter",
"swordOwner": "Magnus Barelegs"
}UPDATE
PUT http://localhost:8080/api/swords/54875u4tytuyt0thfoClick Body, and check x-www-form-urlencoded. Change the swordName to "Assbiter" and the swordOwner to "Magnus Bareass". Expected response:
Expected response:
{
"swordName": "Assbiter",
"swordOwner": "Magnus Bareass"
}DESTROY
DELETE http://localhost:8080/api/swords/54875u4tytuyt0thfo
GET http://localhost:8080/api/swordsExpected response:
[]Commit to GitHib: "All Express routes working". Now you are finished with Express and shouldn't have to make any additional changes.
#Angular App
In the angular_app directory, make two files:
touch app.js
touch index.html
ls
##index.html
Check the latest Angular version at https://angularjs.org in the blue Download button.
<!DOCTYPE html>
<html lang="en" ng-app="SwordsApp">
<head>
<meta charset="UTF-8">
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.5/angular.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.5/angular-route.js"></script>
<title>Swords App</title>
</head>
<body>
<h1>Historical Swords</h1>
<script type="text/javascript" src="app.js"></script>
</body>
</html>##app.js
var app = angular.module("SwordsApp", ['ngRoute']);###Test Angular
From angular-app start the Angular server:
http-server -c-1 -o
In the browser you should see your index.html page. Save to GitHub: "Angular server started".
##Add the Stylesheet
Double-check that you're in the angular-app directory. Make this folder and file:
mkdir css
cd css
touch style.css
cd ..
Add a link to the stylesheet to index.html.
<link rel="stylesheet" href="css/style.css">This is link and href, not style and src.
In the stylesheet:
h1 {
color: red
}If the index page displays the header in red then the stylesheet is connected.
##Angular Directory Structure
Double-check that you're in the angular-app directory. Make these folders and files:
mkdir javascript
cd javascript
mkdir controllers
cd controllers
touch HomeController.js
touch NewController.js
touch ShowController.js
touch EditController.js
cd ..
mkdir routes
cd routes
touch routes.js
cd ..
mkdir templates
cd templates
touch home.html
touch new.html
touch show.html
touch edit.html
cd ..
cd ..
ls
You should see these folders and files in angular-app:
app.js
├── css
│ └── style.css
├── index.html
└── javascript
├── controllers
│ ├── EditController.js
│ ├── HomeController.js
│ ├── IndexController.js
│ ├── NewController.js
│ └── ShowController.js
├── routes
│ └── routes.js
├── services
│ └── services.js
└── templates
├── edit.html
├── home.html
├── new.html
└── show.html
##README.md
Include instructions for starting your app.
express-app
nodemon
mongod
angular-app
http-server -c-1 -o
test with Postman
GET http://localhost:8080/api/swords/Commit to GitHub: "Angular directory structure finished."
##Angular Routes
A CRUD app with four views needs four routes and four controllers.
The order of the routes matters. The SHOW route must be at the bottom, as it catches the other routes.
app.config(function($routeProvider) {
$routeProvider
.when('/', { // INDEX
templateUrl: 'javascript/templates/home.html',
controller: 'HomeController'
})
.when('/new', { // must be above '/:id' otherwise it'll think that the ID is 'new'
templateUrl: 'javascript/templates/new.html', // NEW
controller: 'NewController'
})
.when('/:id/edit', { // UPDATE
templateUrl: 'javascript/templates/edit.html',
controller: 'EditController'
})
.when('/:id', { // SHOW
templateUrl: 'javascript/templates/show.html',
controller: 'ShowController'
})
.otherwise({ redirectTo: '/' });
});Set up each controller with just (change the name of each controller):
app.controller('HomeController', ['$scope', function($scope) {
$scope.message = "Connected";
}]);Link to the router and controllers from index.html:
<script type="text/javascript" src="javascript/routes/routes.js"></script>
<script type="text/javascript" src="javascript/controllers/EditController.js"></script>
<script type="text/javascript" src="javascript/controllers/HomeController.js"></script>
<script type="text/javascript" src="javascript/controllers/NewController.js"></script>
<script type="text/javascript" src="javascript/controllers/ShowController.js"></script>Also put ng-view into the index.html body:
<ng-view>
or
<div ng-view></div>
and in each view set up:
<h1>Home/New/Show/Edit</h1>
{{message}}Test each route and you should see the header and "Connected!", except the EDIT route should show the SHOW page. Commit to GitHub: "Angular serving views."
##Connecting Angular to Express
Each CRUD route in Angular requires three components: a route, a controller, and a view.
###Angular CREATE Route
This route puts new data into the database.
Set up new.html with one form to send two pieces of data:
<form ng-submit="addSword()" name="newSword">
<label for="swordName">Sword: </label>
<input type="text" name="swordName" ng-model="sword.swordName" />
<label for="swordOwner">Owner: </label>
<input type="text" name="swordOwner" ng-model="sword.swordOwner" />
<input type="submit" value="Add Sword"></input>
</form>ng-submit and name must be different.
To send data to the controller you must use ng-submit, not ng-click.
Set up NewController.js to send the two pieces of data to Express:
app.controller('NewController', ["$scope", '$http', '$location', function($scope, $http, $location){
console.log("New controller");
$scope.addSword = function(sword){
var sword = {
swordName: $scope.sword.swordName,
swordOwner: $scope.sword.swordOwner
}
$http.post('http://localhost:8080/api/swords/', sword).then(function(response) { // NEW
console.log("Sword added.");
$location.path( "/" );
}, function(response) {
console.log("Invalid URL");
});
}
}]);Dependencies: $scope, $http, $location
Add a sword, then use Postman with a GET request to see if the data made it to MongoDB. Commit to GitHub: "Angular sending data to the database."
###Angular INDEX Route
This route displays the data in home.html.
Set up HomeController.js:
app.controller('HomeController', ['$scope', '$http', function($scope, $http){
console.log("Home controller.");
$http.get('http://localhost:8080/api/swords/').then(function(response) { // INDEX
$scope.swords = response.data;
}, function(response) {
console.log("Invalid URL");
});
}]);Dependencies: $scope, $http
Now display the data in home.html:
<div ng-repeat="sword in swords">
<span>{{sword.swordName}}</span>
<span>{{sword.swordOwner}}</span>
<br />
</div>If your data displays then Angular is working with Express and MongoDB. Commit to GitHub: "Database saving and serving data to Angular."
###Angular NEW Route
The NEW route takes the user to new.html. No Angular controller or Express route is needed because no data is fetched from the database. All you need is an anchor link in home.html:
<a href="/#/new"><button>Add a new sword</button></a>###Angular SHOW Route
The SHOW route shows one sword from the database.
In ShowController.js:
app.controller('ShowController', ['$scope', '$http', '$routeParams', '$location', function($scope, $http, $routeParams, $location) {
console.log("Show controller.");
$http.get('http://localhost:8080/api/swords/' + $routeParams.id).then(function(response) { // SHOW
$scope.sword = response.data;
console.log($scope.sword);
}, function(response) {
console.log("Invalid URL");
});
}]);Dependencies: $scope, $http, $routeParams
In show.html:
<table>
<tr><td style="font-style: italic">Sword Name:</td><td>{{sword.swordName}}</td></tr>
<tr><td style="font-style: italic">Sword Owner:</td><td>{{sword.swordOwner}}</td></tr>
<tr><td><a href="/#/swords/{{sword._id}}/edit"><button>Edit Sword</button></a></td></tr>
</table>In home.html add an anchor link so that the user can click a sword and go to the show page for that sword:
<div ng-repeat="sword in swords">
<span>{{sword.swordOwner}}</span>
<a ng-href="/#/{{sword._id}}"><span>{{sword.swordName}}</span></a>
<br />
</div>This will put the sword ID into the URL of the SHOW page, e.g.,
http://127.0.0.1:8081/#/55f47954dcdfb09a1c058883###Angular EDIT, UPDATE Routes
The EDIT route takes the user to edit.html. The UPDATE route sends the updated data to the database.
In EditController.js:
app.controller('EditController', ["$scope", '$http', '$routeParams', '$location', function($scope, $http, $routeParams, $location){
console.log("Edit controller");
$http.get('http://localhost:8080/api/swords/' + $routeParams.id + '/edit/').then(function(response) { // EDIT
$scope.sword = response.data;
console.log(response.data);
}, function(response) {
console.log("Invalid URL");
});
$scope.updateSword = function(sword) {
console.log("Updating sword.");
var sword = {
swordName: $scope.sword.swordName,
swordOwner: $scope.sword.swordOwner
};
console.log($routeParams.id);
$http.put('http://localhost:8080/api/swords/' + $routeParams.id, sword).then(function(response) { // UPDATE
$location.path( "/" );
console.log("Sword updated.");
}, function(response) {
console.log("Invalid URL");
});
}
}]);Dependencies: $scope, $http, $routeParams, $location
edit.html displays the data and forms to edit the data:
<form ng-submit="updateSword()">
<label for="editSword">Edit Sword: </label>
<input type="text" name="editSword" ng-model="sword.swordName"></label><br>
<label for="editOwner">Edit Owner: </label>
<input type="text" name="editOwner" ng-model="sword.swordOwner"></label><br>
<input type="submit" value="Update Sword"></input>
</form>###Angular DESTROY Route
In the view this is usually a button:
<button ng-click="deleteSword(sword)">Delete Sword</button>The button usually goes in the ng-repeat in home.html.
This method goes into a controller such as HomeController.js:
$scope.deleteSword = function(sword) { // DESTROY
console.log("Deleting sword.");
$http.delete('http://localhost:8080/api/swords/' + sword._id).then(function(response){
console.log("Sword deleted.");
$route.reload();
}, function(response) {
console.log("Failed to reload page.");
});
};Dependencies: $scope, $http, $route
_ $route.reload();_ reloads the page for the updated data to appear. Commit to GitHub: "All Angular CRUD methods working."
##Nested Comments
Comments are associated with (a part of) a record in a database. A new comment doesn't use the NEW route. It uses the UPDATE route. The full record is updated whenever a comment is added or deleted. I.e., adding a comment doesn't create a new sword, it updates an existing sword.
###Adding a New Comment
In a view, such as in the ng-repeat in home.html:
<form ng-submit="newComment(sword)">
<input type="text" name="commentText" ng-model="sword.newComment.commentText">
<input type="text" name="commentAuthor" ng-model="sword.newComment.commentAuthor">
<input type="submit" value="Submit Comment"></input>
</form>In a controller:
$scope.newComment = function(sword) { // full record is passed from the view
var comment = {
commentAuthor: sword.newComment.commentAuthor,
commentText: sword.newComment.commentText,
commentTimestamp: Date.now(),
};
var comments = sword.comments || [];
comments.push(comment); // push comment to local $scope
sword.newComment.commentAuthor = null; // needed to prevent autofilling fields
sword.newComment.commentText = null; // needed to prevent autofilling fields
sword.comments = comments; // saves new comment locally
$http.put('http://localhost:8080/api/swords/' + sword._id, sword).then(function(response) { // UPDATE
console.log("Comment added.");
}, function(response) {
console.log("Invalid URL");
});
};Dependencies: $scope, $http
Add a comment and use Postman to see if it goes into the database as part of its associated sword.
###Displaying Comments
<div ng-repeat="comment in sword.comments">
<span>{{comment.commentText}}</span>
<span>--{{comment.commentAuthor}}</span>
<br />
</div>###Deleting Comments
In a view:
<form ng-submit="deleteComment(sword, comment)">
<input type="submit" value="Delete Comment" />
</form>In a controller:
$scope.deleteComment = function(sword, comment) {
console.log("Deleting comment.")
var index = sword.comments.indexOf(comment); // find the index of the comment in the array of comments
sword.comments.splice(index, 1); // removes the comment from the array
$http.put('http://localhost:8080/api/swords/' + sword._id, sword).then(function(response) { // UPDATE
console.log("Comment deleted.");
}, function(response) {
console.log("Invalid URL");
});
};Dependencies: $scope, $http
Again this is the UPDATE route, not the DESTROY route. The user isn't deleting a record, just updating a part of the record.
Don't do this:
sword.comments = sword.comments.splice(index, 1);That will return the deleted element, i.e., delete all comments except the selected comment!
###Show or Hide Comments
In a view:
<span ng-click="showComments = !showComments">
<ng-pluralize count="sword.comments.length"
when="{'0': '',
'one': '1 comment',
'other': '{} comments',
'NaN': ''}">
</ng-pluralize><br>
</span>##Likes
To display likes in a view:
<span>Likes: {{sword.likes}}</span>
<form ng-submit="upLike(sword)">
<input type="submit" value="Up"<</input>
</form>
<form ng-submit="downLike(sword)">
<input type="submit" value="Down"<</input>
</form>In a controller, first add likes to the addSword method:
$scope.addSword = function(sword){
var sword = {
swordName: $scope.sword.swordName,
swordOwner: $scope.sword.swordOwner,
likes: 0
}then add methods for liking and disliking:
$scope.upLike = function(sword) {
console.log("Liked!");
var likes = sword.likes || 0;
sword.likes += 1;
$http.put('http://localhost:8080/api/swords/' + sword._id, sword).then(function(response) { // UPDATE
console.log("Upliked.");
}, function(response) {
console.log("Invalid URL");
});
}
$scope.downLike = function(sword) {
console.log("Disliked!");
var likes = sword.likes || 0;
sword.likes -= 1;
$http.put('http://localhost:8080/api/swords/' + sword._id, sword).then(function(response) { // UPDATE
console.log("Downliked.");
}, function(response) {
console.log("Invalid URL");
});
}Dependencies: $scope, $http
Again this uses the UPDATE Express route.
##Finish Views
###Homepage Link
Add a link back to index.html.
<a ng-href="/#/"><p style="font-style: italic">Return to index route</p></a>
It's possible to put this link into index.html with ng-show set to a variable that connects to a controller method that checks
###Images
Use ng-src for images:
<img class="audrey" ng-src="http://blog.syracuse.com/shelflife/2008/09/anna.jpg" alt="Audrey Hepburn in 'War and Peace' (1955)" />
###Google Fonts
Google has many attractive fonts at https://www.google.com/fonts. Load them in the header of index.html:
<link rel="stylesheet" type="text/css" href="http://fonts.googleapis.com/css?family=Tangerine">
<link rel="stylesheet" type="text/css" href="http://fonts.googleapis.com/css?family=Cinzel+Decorative">
<link rel="stylesheet" type="text/css" href="http://fonts.googleapis.com/css?family=Expletus+Sans">
Note that spaces in the font name are replaced with a +.
In style.css pick a default font:
body {
font-family: Cinzel Decorative, sans-serif;
}
###Sword Box
To make a light gray box around each sword, make a
<div ng-repeat="sword in swords">
<div class="swordBox">
<div>
<a ng-href="/#/{{sword._id}}"><span>{{sword.swordName}}:</span></a>In style.css give it some styling:
.swordBox {
border: 1px solid #d3d3d3;
border-radius: 5px;
width: 640px;
margin: 2px;
padding: 10px
}Make the comments smaller and italicize the comment authors:
.comment {
font-size: smaller;
font-family: Expletus Sans, sans-serif;
}
.commentAuthor {
font-style: italic;
}###Style Buttons
.button {
/*background-color: #ADD8E6;*/
-webkit-border-radius: 10;
-moz-border-radius: 10;
border-radius: 10px;
color: black;
font-size: 20px;
padding: 10px 20px 10px 20px;
text-decoration: none;
}
.button:hover {
background: #3cb0fd;
text-decoration: none;
}
###Style H1
h1 {
text-shadow: 4px 4px 4px #aaa;
}