MEAN CRUD - tdkehoe/blog GitHub Wiki

View Finished App

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

Click 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/54875u4tytuyt0thfo

Expected response:

{
  "_id": "54875u4tytuyt0thfo", 
  "swordName": "Legbiter",
  "swordOwner": "Magnus Barelegs"
}

EDIT

GET http://localhost:8080/api/swords/54875u4tytuyt0thfo/edit

Expected response (identical to GET SHOW):

{
  "_id": "54875u4tytuyt0thfo", 
  "swordName": "Legbiter",
  "swordOwner": "Magnus Barelegs"
}

UPDATE

PUT http://localhost:8080/api/swords/54875u4tytuyt0thfo

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

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

Test Controllers

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 $location.$$path to see if the homepage is displayed (the homepage link shouldn't display on the homepage. The problem is that checking $location.$$path is done after a view loads, so the view has to be reloaded with $route.reload();, however, index.html doesn't reload.

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

inside the ng-repeat Give it the class swordBox:
<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;
}
⚠️ **GitHub.com Fallback** ⚠️