User Tools

Site Tools


android-labs-s16:mean_stack

You will nead to install mongodb and nodejs for this tutorial. Check out the instructions here. Do this first– the components can take some time to download and install.

You will need to use your favorite text editor for this tutorial. If you don't have a favorite text editor, you should find one! Sublime Text is a popular choice.

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!

This tutorial is designed for beginners, but assumes you already have basic knowledge of HTML and JavaScript.

What can I expect from this?

  1. Get a high-level understanding of the main components that make up a MEAN stack app
  2. Piece together the app as you follow along. You will understand what full stack development is like, and be able to use the various tools presented
  3. Deploy your application to Heroku

What is the MEAN Stack?

The MEAN stack is a popular stack of technologies used to create web applications. However, you should be aware of the fact that many of these technologies are interchangeable. For example, you could use Backbone, Ember, or React on the frontend instead of Angular. You could use Hapi instead of Express, and MySQL of Postgres instead of Mongo. Each of these technologies has certain advantages and drawbacks that we will discuss in future tutorials, but we will be only be covering the MEAN stack today.

MEAN is an acronym for the four main technologies that it is comprised of:

  1. MongoB: Non-relational database
  2. Express: Node framework that has powerful middleware features
  3. Angular: A frontend JavaScript library created and maintained by Google that is used to create single page applications
  4. Node: server-side JavaScript environment based on V8

What are the advantages of using the MEAN stack?

That main advantage of the MEAN stack is the ability to write JavaScript code on the front and back ends. This is a big deal! Previously, you would have to know multiple languages to create a full stack application. JavaScript is unique in this respect, and adds to its power, usefulness, flexibility, and overall popularity.

What we will be building

Scope of our application:

  1. CRUD(create, read, update, delete) video games
  2. Each game will have a platform and genre
  3. View all games
  4. View all games on a platform
  5. View all games in a genre

Interacting with the Command Line

At some point in your develop career you *will* have to interact with software tools from a command line. If you don't have any experience with command lines, don't despair! It takes time, but its not all that complicated in the end!

To open a command line interface, or CLI:

  1. Windows: press the windows key, type cmd, and press enter.
  2. Ubuntu: press ctrl + alt + t
  3. OSX: search for and open the Terminal application

Linux and OSX both default to bash, a program that allows you access to your computer through text. Windows does not use bash. As such the commands for OSX and Linux are the same, where Windows has its own commands.

You'll need just three commands (outside of the commands node requires) for this tutorial:

  1. pwd- print working (current) directory
  2. cd- Change directory to another relative to the current directory
  3. ls- list files in this directory. On windows this command is dir
  4. mkdir- make a directory relative to the current directory

Commands you run in a terminal run until they're finished. Depending on the command they may finish immediately, run for a while, or run forever. To kill a command, press ctrl + c on Linux and Windows, cmd + c on OSX.

Configuration & Setup

Once you've finished installing node and mongo as directed in the first sectino, open a CLI session and make sure node is installed.

$ node -v
v5.6.0

If you dont see a version number listed, node is not correctly installed.

Install necessary packages via NPM (Node Package Manager). NPM is automatically installed with Node and allows you to install packages from the internet to your computer.

  • 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 grunt-cli yo generator-angular-fullstack

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

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

You will be presented with several options. The yeoman generator allows you to customize your newly scaffolded application by swapping out functionality or technology. Hit enter for every question, selecting the defaults.

After creating your files, it should automatically run an npm install for you. This will take some time. Once the terminal output stops, Yeoman is done working. The result is a working MEAN stack server and frontend. Lets run it!

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 
$ grunt serve

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

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

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

Check out the login page, either by clicking the link on the landing page, or going to http://localhost:9000/login

Your MEAN stack app is up and running! Yay! Yeoman includes a default API under http://localhost:9000/api/things.

Running grunt serve will run a server locally on your computer forever. Grunt is pretty smart and will update the live site at localhost:9000 when you update code. You should leave the terminal session that you ran grunt serve in open for the rest of this tutorial and execute further commands in another terminal window. Make sure you cd in the same directory you were working in before!

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. Again, yeoman will ask you for defaults. Hit enter to select the defaults.

yo angular-fullstack:endpoint game

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.

In server/config/seed.js, we are populating our database whenever the server is started. Let’s make it automatically seed the database with a some games (as well as keeping the default users and things).

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. In server/config/seeds.js:

'use strict';
import User from '../api/user/user.model';
import Game from '../api/game/game.model';

User.find({}).removeAsync()
  .then(() => {
    User.createAsync({
      provider: 'local',
      name: 'Test User',
      email: 'test@example.com',
      password: 'test'
    }, {
      provider: 'local',
      role: 'admin',
      name: 'Admin',
      email: 'admin@example.com',
      password: 'admin'
    })
    .then(() => {
      console.log('finished populating users');
    });
  });

'use strict';


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

Check out server/routes.js. This files defines how express, our web server, responds to different URLs. 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 with

grunt build
grunt serve

Client - Angular JS

Now, it’s time to hook these up to our client-side using 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 frontend route for viewing games with the following. As before, hit enter to accept defaults.

yo angular-fullstack:route 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.” Kinda boring, no?

We need a way to navigate to this page without manually typing in the URL. Open client/components/navbar/navbar.html and replace the contents with the following code. Note we're only adding one line, a nav link that shows games.

<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="nav.isCollapsed = !nav.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="nav.isCollapsed" class="navbar-collapse collapse" id="navbar-main">
      <ul class="nav navbar-nav">
        <li ng-repeat="item in nav.menu" ui-sref-active="active">
          <a ui-sref="{{item.state}}">{{item.title}}</a>
        </li>
        <li ng-show="nav.isAdmin()" ui-sref-active="active"><a ui-sref="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="nav.isLoggedIn()" ui-sref-active="active"><a ui-sref="signup">Sign up</a></li>
        <li ng-hide="nav.isLoggedIn()" ui-sref-active="active"><a ui-sref="login">Login</a></li>
        <li ng-show="nav.isLoggedIn()">
          <p class="navbar-text">Hello {{ nav.getCurrentUser().name }}</p>
        </li>
        <li ng-show="nav.isLoggedIn()" ui-sref-active="active"><a ui-sref="settings"><span class="glyphicon glyphicon-cog"></span></a></li>
        <li ng-show="nav.isLoggedIn()"><a ui-sref="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 client/app/games/games.html, we will be using ng-repeat (an Angular directive) to display all of our data. Head back to the /games page when you're done.

<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 HTML element 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”. Edit client/app/games/games.controller.js

'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 and wipe any changes to models you've made.

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 and save! 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.

All of our CRUD functions are complete! In the next and final part of this tutorial, we will learn how to display games by a specific platform or genre, as well as deploy our app so the whole world can see it!

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 client/app/games/games.html 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 on Heroku, go do that. You will also need to setup Heroku Toolbelt.

Once installed, log in to heroku. The first time heroku will take some time to install.

$ heroku login
Enter your Heroku credentials.
Email: [YOUREMAIL]
Password (typing will be hidden): 
Logged in as [YOUREMAIL]

Yeoman knows how to set up our project for deployment to heroku very easily. Select the defaults when prompted. Leave the name blank to get a random name (Heroku has the best names!)

yo angular-fullstack:heroku

We have a site up! Lets open it.

cd dist && heroku open

Your app URL should open in your browser. However, there seems to be an error

Let's check heroku logs

cd dist
heroku logs

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!

You may run into an error here if you just made a new heroku account. Heroku does not allow unverified accounts to add services, and verification requires adding a credit card. Don't worry, heroku will never charge you for anything unless you specifically upgrade a running site.

If you don't want to enter info for heroku, you're all done. You won't be able to see the final site running, but be assured it would be running securely over the internet.

heroku addons:create mongolab

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/mean_stack.txt · Last modified: 2016/03/04 13:47 by mbarboi