User Tools

Site Tools


android-labs-s16:ios_tutorial_5

The MEAN Stack tutorial

Through this tutorial we wanted to touch base on how full stack development looks like. Essentially, we will be creating a MEAN stack application, which is nothing but a combination of MongoDB, Express, Angular and Node.js. So, let's get on it!

Configuration & Setup

1) If you don’t already have Git installed, download it here

2) Install Node.js

3) Install MongoDB

4) Install necessary packages via NPM (Node Package Manager). NPM is automatically installed with Node

  • Bower – A manager for client-side packages
  • Grunt CLI – Grunt is a JavaScript task runner that can be used to automate tasks such as deployment, unit testing, minification, concatenation, linting etc.
  • Yeoman – A scaffolding tool
  • Full-Stack Generator – Yeoman generator. We will use this to scaffold components of our app
npm install -g bower
npm install -g grunt-cli
npm install -g yo
npm install -g generator-angular-fullstack

Let’s create the app template and initialize a Git repository.

mkdir mean-tutorial && cd $_
yo angular-fullstack mean-tutorial

You will be presented with several options.

Hit enter for every question, selecting the defaults, except the following:

Include bootstrap: Y
Include UI Bootstrap: Y
Scaffold out authentication boilerplate: Y

After creating your files, it should automatically run an npm install for you. If this fails (which seems to happen pretty often), remove your node_modules folder and install again by running the following (Note: make sure you are in the correct directory for your project!)

ONLY RUN THE FOLLOWING 3 COMMANDS IF YOUR INSTALL FAILED!

rm -rf node_modules
npm install --dev

We also need to install our bower dependencies with

bower install

Now we are ready to run our app!

Running Our App

To run our app, we will need to start processes for our database as well as our server

mongod

This should fire up Mongo, which will allow our app to connect to our local Mongo database. Now we can start our server. Leave Mongo running, open a new terminal tab, and run.

grunt serve

If this fails, mongo is most likely not correctly installed.

Go to localhost:9000
Go to localhost:900/api/things

You should now be able to navigate to http://localhost:3000/ in your browser and you should see this page

Your MEAN stack app is up and running! Yay! Since you are not logged in, you have been redirected to /login. Click “home” on the navbar to see the homepage

Push to Github

Let’s push our code up to GitHub… You want the world to see your awesome apps, right? Create a new repo on GitHub (if you don’t know how to do this, don’t worry. It’s super easy, just go to GitHub, create and account, and follow the directions). Once that’s ready, let’s initialize a Git repo on our local machine, commit our code, add our remote (which is our GitHub repo), and push our code to GitHub! (Note: If you server is still running, use CTRL-C to stop it).

When you add your remote, be sure to use the URL for your own GitHub repository!

git init
git add -A
git commit -m "initial scaffolding"
git remote add origin https://github.com/AJFunk/mean-tutorial.git
git push -u origin master

Server logic

CRUD(create, read, update, delete) video games

  • Each game will have a platform and genre
  • View all games
  • View all games on a platform
  • View all games in a genre

Adding CRUD Endpoint

The first thing we need to do is create our endpoints on our server.

yo angular-fullstack:endpoint game
? What will the url of your endpoint to be? /api/games

While we’re at it, let’s remove the default “thing” endpoint that was created with our scaffolding The first thing we need to do is create our endpoints on our server

rm -rf server/api/thing

So, what is an endpoint you ask? Say I want to create a “game” in my database. I’m going to send data to my server that contains the information for a game. But how does my server know that it’s a game? How does it know what I want it to do with that data?

An endpoint is like a special slot that is designed to do 3 things: First, it receives your data. If it likes the data you sent it (meaning your data is properly formed), it does something with your data (creates a new game in this instance). Lastly, it responds back to you – either “Success, your game was created!” or “Failure, something went wrong!”. Of course, these response messages are more verbose than this, but that is the general idea.

If you try to run the app, it will crash with this error:

module.js:340
    throw err;
          ^
Error: Cannot find module '../api/thing/thing.model'
    at Function.Module._resolveFilename (module.js:338:15)
    at Function.Module._load (module.js:280:25)
    at Module.require (module.js:364:17)
    at require (module.js:380:17)
    at Object.<anonymous> (/Users/ajfunk/mean-tutorial/server/config/seed.js:8:13)
    at Module._compile (module.js:456:26)
    at Object.Module._extensions..js (module.js:474:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)
    at Module.require (module.js:364:17)
    at require (module.js:380:17)
    at Object.<anonymous> (/Users/ajfunk/mean-tutorial/server/app.js:18:21)
    at Module._compile (module.js:456:26)
    at Object.Module._extensions..js (module.js:474:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)

Uh oh, we broke it! Time to debug.

The problem seems to be that the module for thing.model cannot be found. Since that is a file we just deleted, that makes sense! Let’s fix this. In server/config/seed.js, we are populating our database whenever the server is started. We’ve remove “things”, but let’s make it automatically seed the database with a some games (as well as keeping the default users).

First, we need to create a Game schema. In server/api/game/game.model.js we need to define what the data for a Game should look like

'use strict';
 
var mongoose = require('mongoose'),
    Schema = mongoose.Schema;
 
var GameSchema = new Schema({
  name: String,
  platform: String,
  genre: String
});
 
module.exports = mongoose.model('Game', GameSchema);

Now we will update seed.js to populate our Mongo database

'use strict';

var Game = require('../api/game/game.model');
var User = require('../api/user/user.model');

Game.find({}).remove(function() {
  Game.create({
    name : 'Halo 5',
    platform : 'Xbox One',
    genre: 'Shooter'
  },{
    name : 'Fallout 4',
    platform : 'PlayStation 4',
    genre: 'Role-Playing'
  },{
    name : 'Super Smash Bros.',
    platform : 'Wii U',
    genre: 'Fighting'
  },{
    name : 'Pokemon X',
    platform : '3DS',
    genre: 'Role-Playing'
  },{
    name : 'Halo 4',
    platform : 'Xbox 360',
    genre: 'Shooter'
  });
});

User.find({}).remove(function() {
  User.create({
    provider: 'local',
    name: 'Test User',
    email: 'test@test.com',
    password: 'test'
  }, {
    provider: 'local',
    role: 'admin',
    name: 'Admin',
    email: 'admin@admin.com',
    password: 'admin'
  }, function() {
      console.log('finished populating users');
    }
  );
});

We also need to remove our Things routes in server/routes.js.

'use strict';

var errors = require('./components/errors');

module.exports = function(app) {

  // Insert routes below
  app.use('/api/games', require('./api/game'));
  app.use('/api/users', require('./api/user'));

  app.use('/auth', require('./auth'));
  
  // All undefined asset or api routes should return a 404
  app.route('/:url(api|auth|components|app|bower_components|assets)/*')
   .get(errors[404]);

  // All other routes should redirect to the index.html
  app.route('/*')
    .get(function(req, res) {
      res.sendfile(app.get('appPath') + '/index.html');
    });
};

Notice that our routes for Games are already included? Grunt is pretty great. If your “grunt serve” is still running, it will detect the change when you save the file and restart the server. You should be able to run the app again.

Client - Angular JS

Now, it’s time to hook these up to our client-side using my personal favorite frontend framework, Angular

Let’s begin by letting Yeoman do some more work for us! We will begin with the “R” in CRUD, which stands for “Read”. Generate our route for viewing games with

? Where would you like to create this route? (client/app/) client/app
? What will the url of your route be? (/games) /games

This generates everything we need for a new view in our app, which can be seen by going to http://localhost:9000/games

This should display text that says “This is the games view.”

We need a way to navigate to this page without manually typing in the URL. Change our games.html to:

<div class="navbar navbar-default navbar-static-top" ng-controller="NavbarController">
  <div class="container">
    <div class="navbar-header">
      <button class="navbar-toggle" type="button" ng-click="isCollapsed = !isCollapsed">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <a href="/" class="navbar-brand">mean-tutorial</a>
    </div>
    <div collapse="isCollapsed" class="navbar-collapse collapse" id="navbar-main">
      <ul class="nav navbar-nav">
        <li ng-repeat="item in menu" ng-class="{active: isActive(item.link)}">
            <a ng-href="{{item.link}}">{{item.title}}</a>
        </li>
        <li ng-show="isAdmin()" ng-class="{active: isActive('/admin')}"><a href="/admin">Admin</a></li>
        <li ng-class="{active: isActive('/games')}"><a href="/games">Games</a></li>
      </ul>

      <ul class="nav navbar-nav navbar-right">
        <li ng-hide="isLoggedIn()" ng-class="{active: isActive('/signup')}"><a href="/signup">Sign up</a></li>
        <li ng-hide="isLoggedIn()" ng-class="{active: isActive('/login')}"><a href="/login">Login</a></li>
        <li ng-show="isLoggedIn()"><p class="navbar-text">Hello {{ getCurrentUser().name }}</p> </li>
        <li ng-show="isLoggedIn()" ng-class="{active: isActive('/settings')}"><a href="/settings"><span class="glyphicon glyphicon-cog"></span></a></li>
        <li ng-show="isLoggedIn()" ng-class="{active: isActive('/logout')}"><a href="" ng-click="logout()">Logout</a></li>
      </ul>
    </div>
  </div>
</div>

You should now see a link in the navbar that will let you navigate to /games

But this page is still boring. It’s time to start displaying the data that is stored in our Mongo database. This is done by:

  • Navigating to /games
  • Making a request to our server
  • Getting a response from our server that contains our data
  • Displaying our data

Modify client/app/games/games.controller.js to look like this:

'use strict';

angular.module('meanTutorialApp')
  .controller('GamesCtrl', function ($scope, $http) {
    $http.get('/api/games')
    .success(function(data) {
      $scope.games = data;
      console.log($scope.games);
    })
    .error(function(err) {
      alert('Error! Something went wrong');
    });
  });

Let’s walk through what is going on here. First, use “$http.get(‘/api/games’)” to make a GET request to our server, which hits our /api/games endpoint. We define a success callback, which then sets $scope.games equal to the data that came back from our server. (Note: $scope.games is just a variable – a special Angular variable – that you can think of as a global variable that is accessible anywhere in the controller). After that, we console.log our data to see if it is what we were expecting.

We also define an error callback. If, for some reason, something goes wrong with our request, we will alert the user that there was an error.

Save that, then open up DevTools (right click in your window and choose “Inspect Element”).

If we take a look at the data, we received an array with 5 objects. These objects each represent a game in our database. Now we can display them on the screen

In games.html, we will be using ng-repeat (an Angular directive) to display all of our data

<div class="col-md-12">
  <h1>Games</h1>
  <table class="table table-striped">
    <thead>
      <th>Name</th>
      <th>Platform</th>
      <th>Genre</th>
    </thead>
    <tbody>
      <tr ng-repeat="game in games">
        <td>{{game.name}}</td>
        <td>{{game.platform}}</td>
        <td>{{game.genre}}</td>
      </tr>
    </tbody>
  </table>
</div>

If you are new to Angular, the ng-repeat at bracket notation may look strange to you. ng-repeat is used to repeat an element over and over based off of the data you provide it. In this case, we use our games array which has 5 elements, each representing a game. It loops through all 5 of these, and create a new with its children.

The brackets are template notation for binding our HTML to the data in our controller. game.name means “display the value for game.name here.” If we change the value of that variable in our controller, it will immediately change in our HTML. This is called data binding, and it is a very powerful feature in Angular.

Now your page should look something like this:

Yay, we are displaying data on our page! Now let’s create new games. Update games.html to like this:

<div class="col-md-12">
  <h1>Games</h1>
  <table class="table table-striped">
    <thead>
      <th>Name</th>
      <th>Platform</th>
      <th>Genre</th>
      <th>Action</th>
    </thead>
    <tbody>
      <tr ng-repeat="game in games">
        <td>{{game.name}}</td>
        <td>{{game.platform}}</td>
        <td>{{game.genre}}</td>
        <td>Delete</td>
      </tr>
    </tbody>
    <tr>
      <td><input type="text" ng-model="newGame.name"></td>
      <td><input type="text" ng-model="newGame.platform"></td>
      <td><input type="text" ng-model="newGame.genre"></td>
      <td>
        <button class="btn btn-primary" ng-click="addNewGame()">Add New</button>
      </td>
    </tr>
  </table>
</div>

We have added a new table row that now has 3 input boxes which each represent a property of a game and a button to save it. Notice how each input box is bound to a variable with ng-model. We have also added placeholders in our ng-repeat that will be used to delete them later on.

Notice that on our button, we have defined ng-click=’addNewGame()’. This is an Angular directive to handle click events. This says “if this button is clicked, execute the addNewGame function”. This function is located in our controller

'use strict';

angular.module('meanTutorialApp')
  .controller('GamesCtrl', function ($scope, $http) {

    $http.get('/api/games')
    .success(function(data) {
      $scope.games = data;
      console.log($scope.games);
    })
    .error(function(err) {
      alert('Error! Something went wrong');
    });

    $scope.addNewGame = function(){
      $http.post('/api/games', $scope.newGame)
      .success(function(){
        $scope.games.push($scope.newGame);
        $scope.newGame = {};
      })
      .error(function(err){
        alert('Error! Something went wrong');
      });
    };

  });
 

When our button is clicked, we will send a POST request to our server, along with our newGame object. On success (which means it was successfully saved), we will add our new game to our games array (Angular will automatically recognize this and add another row via ng-repeat), and then set the newGame object to be empty so we can empty out our input boxes. Again, if there is an error, alert the user.

Enter some new information in the input boxes and hit “Add New”. Voila! A new game has been added to the database! Note that whenever your server is restarted, it will be reseeded with the data from seed.js We can create and read data, but I don’t want Assassin’s Creed in my database anymore. Let’s delete it

First, we will replace our placeholders with some nice delete buttons.

<div class="col-md-12">
  <h1>Games</h1>
  <table class="table table-striped">
    <thead>
      <th>Name</th>
      <th>Platform</th>
      <th>Genre</th>
      <th>Action</th>
    </thead>
    <tbody>
      <tr ng-repeat="game in games">
        <td>{{game.name}}</td>
        <td>{{game.platform}}</td>
        <td>{{game.genre}}</td>
        <td>
          <button class="btn btn-danger" ng-click="deleteGame($index)">Delete</button>
        </td>
      </tr>
    </tbody>
    <tr>
      <td><input type="text" ng-model="newGame.name"></td>
      <td><input type="text" ng-model="newGame.platform"></td>
      <td><input type="text" ng-model="newGame.genre"></td>
      <td>
        <button class="btn btn-primary" ng-click="addNewGame()">Add New</button>
      </td>
    </tr>
  </table>
</div>

Notice that when we click the delete button, it is calling the function “deleteGame” but this time it is passing it the $index variable. $index is the index value of that specific ng-repeat that correlates to the index of the array it is looping through. In other words, when we click the delete button we are saying “remove the element at location $index from the $scope.games array.

games.controller.js now includes our deleteGame function

'use strict';

angular.module('meanTutorialApp')
  .controller('GamesCtrl', function ($scope, $http) {

    $http.get('/api/games')
    .success(function(data) {
      $scope.games = data;
      console.log($scope.games);
    })
    .error(function(err) {
      alert('Error! Something went wrong');
    });

    $scope.addNewGame = function(){
      $http.post('/api/games', $scope.newGame)
      .success(function(){
        $scope.games.push($scope.newGame);
        $scope.newGame = {};
      })
      .error(function(err){
        alert('Error! Something went wrong');
      });
    };

    $scope.deleteGame = function(index){
      $http.delete('/api/games/' + $scope.games[index]._id)
      .success(function(){
        $scope.games.splice(index, 1);
      })
      .error(function(err){
        alert('Error! Something went wrong');
      });
    };

  });
  
This time we use $http.delete, which sends a DELETE request to our server. On success (which means the game was successfully deleted), we can remove that game from our array of games.

You may wondering why the URL is formatted differently than our POST request when we create a game. To answer this, let’s take a look at server/api/game/index.js

'use strict';

var express = require('express');
var controller = require('./game.controller');

var router = express.Router();

router.get('/', controller.index);
router.get('/:id', controller.show);
router.post('/', controller.create);
router.put('/:id', controller.update);
router.patch('/:id', controller.update);
router.delete('/:id', controller.destroy);

module.exports = router;

In this file, we define our endpoints. Notice that the post endpoint does not have any parameters in the URL. This means we must pass our data inside the body of our request. However, the delete endpoint (along with a few others) have a :id parameter. This means that the required id must be passed inside the URL. If we wanted to change this, we could – but this is a discussion for a later tutorial. If this is a bit beyond your grasp right now, don’t worry! Continue on, and know that it will make sense later on down the road.

Click the delete button for Assassin’s Creed and *Poof!* gone forever.

For a better user experience, you can implement a confirmation popup. You don’t want a user to accidentally click a button and do something that is irreversible. We won’t be implementing that as part of this tutorial, but I encourage you to add small features like this afterwards – it’s a great way to learn!

We can create, read, and delete games. We only have the “U” left in CRUD – update! Let’s use some Angular magic to make this happen.

<div class="col-md-12">
  <h1>Games</h1>
  <table class="table table-striped">
    <thead>
      <th>Name</th>
      <th>Platform</th>
      <th>Genre</th>
      <th>Action</th>
    </thead>
    <tbody>
      <tr ng-repeat="game in games">
        <td ng-hide="game.edit">{{game.name}}</td>
        <td ng-show="game.edit"><input type="text" ng-model="game.name"></td>

        <td ng-hide="game.edit">{{game.platform}}</td>
        <td ng-show="game.edit"><input type="text" ng-model="game.platform"></td>

        <td ng-hide="game.edit">{{game.genre}}</td>
        <td ng-show="game.edit"><input type="text" ng-model="game.genre"></td>

        <td ng-hide="game.edit">
          <button class="btn btn-success" ng-click="toggleEdit($index)">Edit</button>
          <button class="btn btn-danger" ng-click="deleteGame($index)">Delete</button>
        </td>
        <td ng-show="game.edit">
          <button class="btn btn-primary" ng-click="saveGame($index)">Save</button>
          <button class="btn btn-danger" ng-click="toggleEdit($index)">Cancel</button>
        </td>
      </tr>
    </tbody>
    <tr>
      <td><input type="text" ng-model="newGame.name"></td>
      <td><input type="text" ng-model="newGame.platform"></td>
      <td><input type="text" ng-model="newGame.genre"></td>
      <td>
        <button class="btn btn-primary" ng-click="addNewGame()">Add New</button>
      </td>
    </tr>
  </table>
</div>

Look carefully at what is happening here. Each has 2 versions: A normal text display, and an input field. The text version uses ng-hide, while the input uses ng-show, and both are bound the the same variable. ng-hide and ng-show are triggered based on the boolean value they are bound to. If the game.edit is true, we will hide the text and show the input.

The buttons also have this same behavior. Clicking the edit button will toggle game.edit, which will then hide the delete and edit buttons and show the save and cancel buttons. Let’s add the necessary JavaScript to our controller and try it out.

'use strict';

angular.module('meanTutorialApp')
  .controller('GamesCtrl', function ($scope, $http) {

    $http.get('/api/games')
    .success(function(data) {
      $scope.games = data;
    })
    .error(function(err) {
      alert('Error! Something went wrong');
    });

    $scope.addNewGame = function(){
      $http.post('/api/games', $scope.newGame)
      .success(function(){
        $scope.games.push($scope.newGame);
        $scope.newGame = {};
      })
      .error(function(err){
        alert('Error! Something went wrong');
      });
    };

    $scope.deleteGame = function(index){
      $http.delete('/api/games/' + $scope.games[index]._id)
      .success(function(){
        $scope.games.splice(index, 1);
      })
      .error(function(err){
        alert('Error! Something went wrong');
      });
    };

    $scope.toggleEdit = function(index){
      $scope.games[index].edit = !$scope.games[index].edit;
    };

  });

All of our fields have now turned into input boxes! Isn’t Angular magnificent? Our toggleEdit function is toggling game.edit between true and false, which allows us to reuse this function when the cancel button is clicked (notice that our cancel button has the same ng-click value!). The cancel button will return the ‘s back to their original state.

However, we have a bug here! If we activate edit mode, change something, and then cancel, the value is not returned to it’s original value. If you reload the page, you will notice that they all return to their proper values. This is because we are not updating them in our database

The last thing to do is to add the ability to save our changes. All we need to do is add a function to attach to the ng-click event for our save button.

'use strict';

angular.module('meanTutorialApp')
  .controller('GamesCtrl', function ($scope, $http) {

    $http.get('/api/games')
    .success(function(data) {
      $scope.games = data;
    })
    .error(function(err) {
      alert('Error! Something went wrong');
    });

    $scope.addNewGame = function(){
      $http.post('/api/games', $scope.newGame)
      .success(function(){
        $scope.games.push($scope.newGame);
        $scope.newGame = {};
      })
      .error(function(err){
        alert('Error! Something went wrong');
      });
    };

    $scope.deleteGame = function(index){
      $http.delete('/api/games/' + $scope.games[index]._id)
      .success(function(){
        $scope.games.splice(index, 1);
      })
      .error(function(err){
        alert('Error! Something went wrong');
      });
    };

    $scope.toggleEdit = function(index){
      $scope.games[index].edit = !$scope.games[index].edit;
    };

    $scope.saveGame = function(index){
      $http.put('/api/games/' + $scope.games[index]._id, $scope.games[index])
      .success(function(){
        $scope.games[index].edit = false;
      })
      .error(function(err){
        alert('Error! Something went wrong');
      });
    };

  });
 

Now you can edit away! However, we should note some more flaws that still exist.

What happens if I make a new game with empty input fields? What happens if I delete the values from all the input boxes and then save it? When I save a new game, why can’t I edit it without reloading? While these are all small issues, they are things that need to be addressed as they will result in poor user experience. Again, it is not within the scope of this tutorial to solve these problems for you – you will learn more by figuring them out on your own! If you are stuck and need help, please ask the TAs.

Deployment

In the previous part of the tutorial, we finished our CRUD actions, allowing us to create, read, update, and delete games. Now, we want to be able to sort our games by genre and platform.

We begin with our HTML. First, we change the data for genres and platforms to be tags with an empty href. This makes them “look clickable.” In other words, it adds default styling (blue text with an underline) and makes the cursor become a pointer on hover. We attach an ng-click handler to both of these, and pass in the appropriate values.

On the top of the page, we add a button to reset the filter, and a line to display what the current filter is.

<div class="col-md-12">
  <h1>Games</h1>
  <button class="btn btn-primary" ng-click="resetGames()">Clear Filter</button>
  Current Filter: {{filter}}
  <table class="table table-striped">
    <thead>
      <th>Name</th>
      <th>Platform</th>
      <th>Genre</th>
      <th>Action</th>
    </thead>
    <tbody>
      <tr ng-repeat="game in games">
        <td ng-hide="game.edit">{{game.name}}</td>
        <td ng-show="game.edit"><input type="text" ng-model="game.name"></td>

        <td ng-hide="game.edit">
          <a href="" ng-click="filterByPlatform(game.platform)">{{game.platform}}</a>
        </td>
        <td ng-show="game.edit"><input type="text" ng-model="game.platform"></td>

        <td ng-hide="game.edit">
          <a href="" ng-click="filterByGenre(game.genre)">{{game.genre}}</a>
        </td>
        <td ng-show="game.edit"><input type="text" ng-model="game.genre"></td>

        <td ng-hide="game.edit">
          <button class="btn btn-success" ng-click="toggleEdit($index)">Edit</button>
          <button class="btn btn-danger" ng-click="deleteGame($index)">Delete</button>
        </td>
        <td ng-show="game.edit">
          <button class="btn btn-primary" ng-click="saveGame($index)">Save</button>
          <button class="btn btn-danger" ng-click="toggleEdit($index)">Cancel</button>
        </td>
      </tr>
    </tbody>
    <tr>
      <td><input type="text" ng-model="newGame.name"></td>
      <td><input type="text" ng-model="newGame.platform"></td>
      <td><input type="text" ng-model="newGame.genre"></td>
      <td>
        <button class="btn btn-primary" ng-click="addNewGame()">Add New</button>
      </td>
    </tr>
  </table>
</div>

Now we need to add functions to our controller to handle our ng-click events. We will be filtering our data by using Array.filter to keep the elements that we want, and remove the ones that don’t match our criteria.

'use strict';

angular.module('meanTutorialApp')
  .controller('GamesCtrl', function ($scope, $http) {

    $scope.filter = 'none';

    $http.get('/api/games')
    .success(function(data) {
      $scope.games = data;
      $scope.originalGames = data;
    })
    .error(function(err) {
      alert('Error! Something went wrong');
    });

    $scope.addNewGame = function(){
      $http.post('/api/games', $scope.newGame)
      .success(function(){
        $scope.games.push($scope.newGame);
        $scope.newGame = {};
      })
      .error(function(err){
        alert('Error! Something went wrong');
      });
    };

    $scope.deleteGame = function(index){
      $http.delete('/api/games/' + $scope.games[index]._id)
      .success(function(){
        $scope.games.splice(index, 1);
      })
      .error(function(err){
        alert('Error! Something went wrong');
      });
    };

    $scope.toggleEdit = function(index){
      $scope.games[index].edit = !$scope.games[index].edit;
    };

    $scope.saveGame = function(index){
      $http.put('/api/games/' + $scope.games[index]._id, $scope.games[index])
      .success(function(){
        $scope.games[index].edit = false;
      })
      .error(function(err){
        alert('Error! Something went wrong');
      });
    };

    $scope.resetGames = function(){
      $scope.games = $scope.originalGames;
      $scope.filter = 'none';
    }

    $scope.filterByGenre = function(genre){
      $scope.resetGames();
      $scope.games = $scope.games.filter(function(game){
        return game.genre === genre;
      });
      $scope.filter = 'Genre: ' + genre;
    };

    $scope.filterByPlatform = function(platform){
      $scope.resetGames();
      $scope.games = $scope.games.filter(function(game){
        return game.platform === platform;
      });
      $scope.filter = 'Platform: ' + platform;
    };

  });

Take some time to review this code. Try to figure out what’s going on here before you continue.

… Got it? Of course you do, but I will explain it for the other people that aren’t quite on your level yet :)

First, we initially set $scope.filter = ‘none’. Also, once our games are loaded, we set $scope.originalGames = data. Essentially, we are making a backup of $scope.games that will serve as the original state of our array. Whenever we want to reset our filter, we will return our games array back to its original value.

We create $scope.resetGames, which does what I just described above. There are also 2 new filter functions: $scope.filterByGenre and $scope.filterByPlatform. Both are doing almost the same thing.

filterByGenre takes a genre as an argument (which is passed in from our ng-click directive), resets the games array (because we want to filter through ALL of the games), and then filters the array. If the game’s genre is equal to the genre that was passed into the function, it returns true (which keeps it in the array), otherwise it returns false (which removes it from the array). It the sets $scope.filter equal to the genre so we can display that on the screen. filterByPlatform is doing the same thing, just comparing platform instead of genre.

Load up the page, and click on a platform or genre. Success! You can even reset the filter using the “Clear Filter” button.” The world wants to see your app, so let’s deploy it to Heroku where it will be publicly accessible! If you haven’t already created an account Heroku, go do that. You will also need to setup Heroku Toolbelt

yo angular-fullstack:heroku

Leave the name blank to get a random name (Heroku has the best names!) cd into the dist folder and open the app

cd dist && heroku open

Your app URL should open in your browser. However, there seems to be an error Let's check heroku logs

heroku logs --app <put-app-name>

This displays our recent logs, and we quickly see the error message:

2015-11-19T20:57:05.686770+00:00 app[web.1]:       throw er; // Unhandled 'error' event
2015-11-19T20:57:05.686772+00:00 app[web.1]:       ^
2015-11-19T20:57:05.686772+00:00 app[web.1]:
2015-11-19T20:57:05.686773+00:00 app[web.1]: Error: failed to connect to [localhost:27017]
2015-11-19T20:57:05.686774+00:00 app[web.1]:     at null.<anonymous> (/app/node_modules/mongoose/node_modules/mongodb/lib/mongodb/connection/server.js:556:25)
2015-11-19T20:57:05.686775+00:00 app[web.1]:     at emitThree (events.js:97:13)
2015-11-19T20:57:05.686776+00:00 app[web.1]:     at emit (events.js:175:7)
2015-11-19T20:57:05.686777+00:00 app[web.1]:     at null.<anonymous> (/app/node_modules/mongoose/node_modules/mongodb/lib/mongodb/connection/connection_pool.js:156:15)
2015-11-19T20:57:05.686777+00:00 app[web.1]:     at emitTwo (events.js:87:13)
2015-11-19T20:57:05.686778+00:00 app[web.1]:     at emit (events.js:172:7)
2015-11-19T20:57:05.686780+00:00 app[web.1]:     at emitOne (events.js:77:13)
2015-11-19T20:57:05.686779+00:00 app[web.1]:     at Socket.<anonymous> (/app/node_modules/mongoose/node_modules/mongodb/lib/mongodb/connection/connection.js:534:10)
2015-11-19T20:57:05.686781+00:00 app[web.1]:     at Socket.emit (events.js:169:7)
2015-11-19T20:57:05.686781+00:00 app[web.1]:     at emitErrorNT (net.js:1253:8)
2015-11-19T20:57:05.686783+00:00 app[web.1]:     at process._tickCallback (node.js:364:17)
2015-11-19T20:57:05.686782+00:00 app[web.1]:     at doNTCallback2 (node.js:450:9)
2015-11-19T20:57:06.414640+00:00 heroku[web.1]: Process exited with status 1
2015-11-19T20:57:06.448849+00:00 heroku[web.1]: State changed from starting to crashed

It seems we can’t connect to our MongoDB, which makes sense! We never created one, nor did we tell our app which db to connect to. So let’s do that!

heroku addons:create mongolab --app limitless-wildwood-4228

Refresh the page, and it should be up and running! Congratulations, you’ve successfully created and deployed your first CRUD application using the MEAN stack!

android-labs-s16/ios_tutorial_5.txt · Last modified: 2016/03/03 23:48 by achin