Your progress isn't saved!
Sign in and we'll remember exactly where you left off the next time you visit - on any of your devices.
Sign up or sign in with

AngularJS Tutorial: Learn to Build Modern Web Apps


Introduction

This tutorial will guide you through the process of creating a full-stack application. It features step-by-step instructions on how to build a fantasy football application, code snippets of the full application, and explanations on design decisions.

We have written a refresh of this tutorial to be a frontend-only implementation using Firebase. The new version of the tutorial can be found here.

Our intention is to provide the AngularJS community with instructions on how to use AngularJS correctly and effectively, but also in its most modern form. The application you are building will go beyond basic use of AngularJS, and we will attempt to explore as much of the framework as possible. We also feel strongly about maintaining modernity in a tutorial, so we will keep it congruent with AngularJS as the framework and community matures. This tutorial is built on top of AngularJS v1.2.0rc1.

The tutorial is a living thing, a work in progress. We are constantly extending the tutorial and making changes and corrections. If you find errata, think something should be changed, or would like to suggest an improvement or new section, we would love to hear from you.

Source code and PDF eBook

This tutorial is provided to you free of charge on the site. We built this in the interest of advancing the AngularJS framework and community. 

We encourage you to purchase the source code and PDF of this tutorial to help fund our continued efforts in building more material and features for Thinkster. Your money literally goes towards paying our rent and food for the next few months while we make the tutorial even more awesome!

All users who purchase the tutorial will have free access to new versions of the up-to-date source code and PDF as they are released.

You have 100% ownership over the PDF and source code and can use them however you like.

Prerequisites

This tutorial assumes you already have a working knowledge of AngularJS. Throughout, there will be references to parts of the "A Better Way to Learn AngularJS" curriculum if you need to clarify or refresh on a certain subject. We recommend going through the entire curriculum before beginning this tutorial.

A knowledge of MongoDB, NodeJS, and ExpressJS will be of great assistance, but is not required.

The Stack

This application will be built on top of the MEAN stack - MongoDB, ExpressJS, AngularJS, and NodeJS. The wonderful people at http://www.mean.io/ have written a boilerplate application stack. We took the stack and stripped it down to a more basic form, which is the point from which you will start.

AngularJS can just as easily be used with a Ruby on Rails, Django, CakePHP, or any other server-side framework. Similarly, you could substitute MongoDB for any other database to use with AngularJS. We chose the MEAN stack for the tutorial because it offers an extremely clean implementation of the application, but by no means is it the only implementation.

We included a backend component to the tutorial because it is impossible to create a truly awesome application with only AngularJS and other JS libraries - you need a server and database component. Thus, the tutorial will go through the construction of an entire application, both frontend and backend.

Part of this tutorial will be spent demystifying why the MEAN stack works the way it does. This is essential to a complete understanding of how to build an application with AngularJS.

Why fantasy football for a tutorial?

Besides the fact that fantasy football is totally awesome and a ton of fun? 

We are bored to tears by building Twitter clones over and over again in tutorials, and wanted to mix it up a bit. Building a fantasy football application is challenging, and can be broken down into pieces, so it fits into the tutorial paradigm well.

What is fantasy football?

Fantasy football is centered around creating a “fantasy” team out of real life NFL players, and pitting your fantasy team against other teams. Groups of users, usually 8-12, will create their own teams as part of a “league”, and will compete against other individuals within that league.

Individuals in a fantasy football league will choose real players in the American National Football League (NFL) for their teams, and these teams will face each other weekly in a one-on-one matchup during actual NFL games. Players in the real games perform actions that score points in fantasy football, and whichever fantasy team scores more points in that matchup wins. Teams with the best win/loss records enter fantasy playoffs, and an eventual champion is selected.

Makeup of a fantasy team

There are 32 NFL teams, and your fantasy team will consist of players from some of these teams.

There are lots of different positions on a NFL team, but for fantasy football purposes, we simplify the different positions greatly: all you have to worry about is Quarterback (QB), Runningback (RB), Wide Receiver (WR), Tight End (TE), Kicker (K), and Defense/Special Teams (D/ST). Defense/Special Teams is a special position on your roster, it represents the entire Defense and Special Teams units, which are made up of many players, for one of the 32 teams. All other players you select will be individual players.

How players score points isn’t important right now, you can worry about that later. For those of you familiar with fantasy football, this application will operate under standard scoring rules.

Fantasy teams will have 16 members, but only 9 of them will actually count towards scoring in the weekly matchup against another fantasy team. The other 7 will remain on the team’s “bench”, and any points they score will not count towards your team’s total that week. Your team, and every team in the league, will select players in a “fantasy draft” - more about that later. 

Your fantasy team’s 9-player active roster will have 1 Quarterback (QB), 2 Runningbacks (RB), 2 Wide Receivers (WR), 1 Tight End (TE), 1 Flex (which can either be a RB, WR, or TE), 1 Kicker (K), and 1 Defense/Special Teams (D/ST). 

An example 16-man roster might look like the following:

Active Roster

QB: Aaron Rodgers (GB)

RB: Adrian Peterson (MIN)

RB: Arian Foster (HOU)

WR: Calvin Johnson (DET)

WR: A.J. Green (CIN)

TE: Jimmy Graham (NO)

FLEX: Ray Rice (BAL)

K: Stephen Gostkowski (NE)

D/ST: Seattle Seahawks

Bench

TE: Rob Gronkowski (NE)

RB: Marshawn Lynch (SEA)

WR: Brandon Marshall (CHI)

QB: Matt Ryan (ATL)

RB: C.J. Spiller (BUF)

K: Blair Walsh (MIN)

D/ST: Chicago Bears (CHI)

Recap

Hopefully some of that stuck, but if a lot of it went over your head, don’t worry. As you build the application, you will begin to understand much more clearly how fantasy football works. Let’s get started!

Getting Familiar With the MEAN Stack

We’ve provided the starting point for the application on github: https://github.com/msfrisbie/mean-stripdown.

Clone the application with git clone https://github.com/msfrisbie/mean-stripdown.git

The application uses Node.js and MongoDB, make sure you have those installed.

Install the app dependencies with npm install

With all this set up, you should be able to run the application! From the mean-stripdown directory, running node server should start an ExpressJS node server on port 3000. Navigate to localhost:3000, and you should see the skeleton app working!

What Am I Actually Dealing With Here?

Before you actually get your hands dirty, familiarize yourself with what is provided in the skeleton application.

App Directory

This contains all the files involved in server-side program flow. Your directory structure will look like this:

Stop scrolling to load code snippet
app
├── controllers
│   ├── index.js
│   └── users.js
├── models
│   └── user.js
└── views
    ├── 404.jade
    ├── 500.jade
    ├── includes
    │   ├── foot.jade
    │   └── head.jade
    ├── index.jade
    ├── layouts
    │   └── default.jade
    └── users
        ├── auth.jade
        ├── signin.jade
        └── signup.jade

The server is using the Jade templating engine to render views. You won’t need to worry about this too much right now, as none of your AngularJS templates will be done in Jade. You see two controllers, one user model, and a bunch of views provided for you. 

The default.jade, foot.jade, and head.jade views are the ‘wrapper’ templates for the application, which surround the AngularJS templates. Looking through these should be pretty self-explanatory.

Authentication and the Execution Environment

You might be asking yourself, “Matt, isn’t this an AngularJS tutorial? Why is the server handling all these views?”

The answer lies in application security. AngularJS, by itself, cannot be used to securely authenticate a user. AngularJS exists entirely in the browser’s JavaScript execution, and therefore it must be assumed that the user has complete control over the execution environment. The user is able to modify any part of the code you provide to them, and so authentication cannot be solely handled by the browser, there must be a remote server aspect to it.

The stack provided sets this up nicely. The server uses PassportJS, cookies, and a User model to authenticate users in a standard fashion. The auth.jade, signin.jade, and signup.jade views, along with the users.js controller, are all part of this. The server provides the browser with a cookie to identify the user session, and every transaction with the server after that will use that cookie to identify the user, not by anything Angular will provide.

Now you might be asking, “OK Matt, that’s all well and good that the server’s authentication is squared away, but now how does AngularJS know the user is authenticated?”

Good question! You’ll start by examining index.jade:

app/views/index.jade
Stop scrolling to load code snippet
extends layouts/default

block content
  section(ng-view)
  script(type="text/javascript").
    window.user = !{user};

When rendering this template, if the user has authenticated, the user object can be interpolated into the view. When passed to the client, the user object is attached to the window object, and is now available to your JavaScript as window.user. When the user has not authenticated, the window.user object will be null, and everything still works.

Getting Into AngularJS

You won’t stop with the window.user object, though. Even though this object is available globally, using this throughout the application to handle authentication introduces a bit of code smell. Since you won’t need to use the user object everywhere, but you’d like to use it in a *lot* of places, this seems like the perfect opportunity to write your first service.

Public Directory

This contains CSS, images, libraries, and all your AngularJS files and views. Your directory structure will look like this:

Stop scrolling to load code snippet
public
├── css
│   ├── ...
├── img
│   ├── ...
├── js
│   ├── app.js
│   ├── config.js
│   ├── controllers
│   │   ├── header.js
│   │   └── index.js
│   ├── directives.js
│   ├── filters.js
│   ├── init.js
│   └── services
│       └── global.js
├── lib
│   ├── angular
│   │   ├── ...
│   ├── angular-bootstrap
│   │   ├── ...
│   ├── angular-cookies
│   │   ├──...
│   ├── angular-mocks
│   │   ├── ...
│   ├── angular-resource
│   │   ├── ...
│   ├── angular-route
│   │   ├── ...
│   ├── angular-scenario
│   │   ├── ...
│   ├── bootstrap
│   │   ├── ...
│   ├── jquery
│   │   ├── ...
│   ├── json3
│   │   ├── ...
├── robots.txt
└── views
    ├── header.html
    └── index.html

The lib directory contains angular.js proper, and also modules that you will list as dependencies for your application.

All the AngularJS files you will modify live in the js/ directory. Views obviously live in the views directory.

app.js attaches an angular instance to the window as a window.app object, and defines module dependencies.

config.js sets up routing and other configuration options

The controllers/ directory contains all your application controllers, separated into their own files. You will continue this separate file convention as the application grows.

The services/ directory contains all your application services

The directives.js and filters.js will contain your directives and filters, respectively. These will eventually be broken out into multiple files.

init.js serves to provide some setup configuration. In it, you will notice that the application is manually bootstrapped, as opposed to declaring the app in a view with ng-app

Writing The Authentication Service

Before proceeding, make sure you are familiar with how services work in AngularJS. If you need a refresher on Angular services, read through Part 11: Under the Hood.

Additionally, read through Part 13: $http and Server Interaction.

Global Service

Recall that you are trying to convey authentication information to AngularJS cleanly. You will start with the global.js file, which is basically empty:

public/js/services/global.js
Stop scrolling to load code snippet
window.angular.module('ngff.services.global', [])
  .factory('Global', function(){

  });

This Global service will return an object which you can use to identify the user, as well as ascertain if a user is logged in or not. Since login/logout is a synchronous server action, this service will be refreshed each time the authentication state changes. Therefore, you are able to directly grab the window.user object, and use that to convey authentication to the AngularJS application.

Change global.js to match the following:

public/js/services/global.js
Stop scrolling to load code snippet
window.angular.module('ngff.services.global', [])
  .factory('Global', function(){
    var current_user = window.user;
    return current_user;
  });

Great! It returns the current_user object in the service, so injecting the Global service in your application will give us access to that user object. This would work fine, but you’d like to encapsulate the current_user object so the application doesn't directly access it. 

Let’s instead refactor it to return an object with methods that indirectly interact with the current_user object:

public/js/services/global.js
Stop scrolling to load code snippet
window.angular.module('ngff.services.global', [])
  .factory('Global', function(){
    var current_user = window.user;
    return {
      currentUser: function() {
        return current_user;
      },
      isSignedIn: function() {
        return !!current_user;
      }
    };
  });

Now that you have created a service, you need to add it as a dependency to the application.

Setting Up Application Dependencies

You will see the following in app.js:

public/js/app.js
Stop scrolling to load code snippet
window.app = angular.module('ngFantasyFootball', ['ngCookies', 'ngResource', 'ui.bootstrap', 'ngRoute', 'ngff.controllers', 'ngff.directives', 'ngff.services']);

// bundling dependencies
window.angular.module('ngff.controllers', ['ngff.controllers.header','ngff.controllers.index']);
window.angular.module('ngff.services', []);

Add the new module dependency ‘ngff.services.global’ to the ‘ngff.services’ module:

public/js/app.js
Stop scrolling to load code snippet
window.angular.module('ngff.services', ['ngff.services.global']);

At this point, you are able to create a user in the application, and sign in. When you’re signed in, use your browser’s console to check the value of window.user. You should see the user object. Navigating to /signout (not #!/signout, an important difference) and checking window.user should show it to be null.

Terrific! The first part of authentication is taken care of.

Dependency Injection in a Controller

You should be comfortable with at least basic concepts in AngularJS controllers and dependency injection. For a refresher, read through Part 2: Taking It for a Spin.

Also, the tutorial will dive right into Angular scope. For a review, read through Part 5: Scope.

Now that you have this global service working, let’s actually make it do something. header.js contains the controller for the header bar. It will also be empty:

public/js/controllers/header.js
Stop scrolling to load code snippet
window.angular.module('ngff.controllers.header', [])
  .controller('HeaderController', [
    function() {

    }]);

You can’t use the Global service in the view without attaching it to the scope. 

Inject both of these into the controller:

public/js/controllers/header.js
Stop scrolling to load code snippet
window.angular.module('ngff.controllers.header', [])
  .controller('HeaderController', ['$scope', 'Global',
    function ($scope, Global) {

    }]);

Recall that the order in which these are listed does not matter, as Angular’s dependency injection will evaluate them individually, regardless or parameter ordinality. 

Use the injected service object, and attach it to the scope:

public/js/controllers/header.js
Stop scrolling to load code snippet
window.angular.module('ngff.controllers.header', [])
  .controller('HeaderController', ['$scope', 'Global',
    function ($scope, Global) {
      $scope.global = Global;
    }]);

Using The Controller in the View

You’ll notice that, even when you’re signed in, that you can still see the Sign In and Sign Up buttons in the header bar. This doesn't quite make sense, and you’d like to instead show the user’s name, and a way to sign out. Take a look at public/views/header.html:

public/views/header.html
Stop scrolling to load code snippet
<div class="navbar-inner" ng-controller="HeaderController">
  <ul class="nav">
    <li>
      <a class="brand" href="/">ngFantasyFootball</a>
    </li>
  </ul>
  <ul class="nav pull-right" ng-hide="">
    <li><a href="signup">Signup</a></li>
    <li class="divider-vertical"></li>
    <li><a href="signin">Signin</a></li>
  </ul>
  <ul class="nav pull-right" ng-show="">
    <li class="dropdown">
      <a href="#" class="dropdown-toggle" data-toggle="dropdown">
        User <b class="caret"></b>
      </a>
      <ul class="dropdown-menu">
        <li><a href="/signout">Signout</a></li>
      </ul>
    </li>
  </ul>
</div>

You see that the two visibility directives, ng-show and ng-hide, are set to empty conditionals. Also, you see that the user dropdown is set to show just ‘User’. 

Make use of the service you just built, and fill in the directives:

public/views/header.html
Stop scrolling to load code snippet
  <ul class="nav pull-right" ng-hide="global.isSignedIn()">
    <li><a href="signup">Signup</a></li>
    <li class="divider-vertical"></li>
    <li><a href="signin">Signin</a></li>
  </ul>
  <ul class="nav pull-right" ng-show="global.isSignedIn()">

global refers to the $scope.global attribute object that you set in the controller. The isSignedIn() method returns a boolean (a double-bang of the current_user object), and so these two <ul>s will mutually exclude each other.

With this, you can see that when you’re signed in, you now see a User dropdown with a signout option, as expected. When you sign out, the buttons switch back to Sign In/Sign Up. Awesome!

Now replace ‘User’ with the user’s actual name.  The currentUser() method returns the current_user object, so use the name and interpolate it into the view:

public/views/header.html
Stop scrolling to load code snippet
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
  {{ global.currentUser().name }} <b class="caret"></b>
</a>

Refreshing the page, the User menu button should have your name in it now.

Building Out The Application

Now that you have authentication all taken care of, let’s move on to building out the application.

The 32 NFL teams and the data about them is about as static as data gets, so instead of serving this from the server every time, it makes more sense to just provide them in a service. Players and other entities that refer to teams will just provide a team index, and the Angular service will take care of the rest.

At this point, it’s important to establish a naming convention for your application. Since ‘team’ could refer to either a NFL team or a fantasy team, you will refer to one as NFLTeam, and one as FantasyTeam in all directories, files, and code.

Writing the NFL Service

Create a new service file, nfl.js:

public/js/services/nfl.js
Stop scrolling to load code snippet
window.angular.module('ngff.services.nfl', [])
  .factory('NFL', function() {
    var NFL = {};
    return NFL;
  });

Obviously, this service doesn't return anything, so have it return a teams attribute, an array of all the NFL teams in alphabetical order by city:

public/js/services/nfl.js
Stop scrolling to load code snippet
window.angular.module('ngff.services.nfl', [])
  .factory('NFL', function() {
    var NFL = {};
    NFL.teams = [
      {"abbr":"ARI", "team":"Arizona", "mascot": "Cardinals", "conference":"NFC", "division": "West"},
      {"abbr":"ATL", "team":"Atlanta", "mascot": "Falcons", "conference":"NFC", "division": "South"},
      {"abbr":"BAL", "team":"Baltimore", "mascot": "Ravens", "conference":"AFC", "division": "North"},
      {"abbr":"BUF", "team":"Buffalo", "mascot": "Bills", "conference":"AFC", "division": "East"},
      {"abbr":"CAR", "team":"Carolina", "mascot": "Panthers", "conference":"NFC", "division": "South"},
      {"abbr":"CHI", "team":"Chicago", "mascot": "Bears", "conference":"NFC", "division": "North"},
      {"abbr":"CIN", "team":"Cincinnati", "mascot": "Bengals", "conference":"AFC", "division": "North"},
      {"abbr":"CLE", "team":"Cleveland", "mascot": "Browns", "conference":"AFC", "division": "North"},
      {"abbr":"DAL", "team":"Dallas", "mascot": "Cowboys", "conference":"NFC", "division": "East"},
      {"abbr":"DEN", "team":"Denver", "mascot": "Broncos", "conference":"AFC", "division": "West"},
      {"abbr":"DET", "team":"Detroit", "mascot": "Lions", "conference":"NFC", "division": "North"},
      {"abbr":"GB", "team":"Green Bay", "mascot": "Packers", "conference":"NFC", "division": "North"},
      {"abbr":"HOU", "team":"Houston", "mascot": "Texans", "conference":"AFC", "division": "South"},
      {"abbr":"IND", "team":"Indianapolis", "mascot": "Colts", "conference":"AFC", "division": "South"},
      {"abbr":"JAC", "team":"Jacksonville", "mascot": "Jaguars", "conference":"AFC", "division": "South"},
      {"abbr":"KC", "team":"Kansas City", "mascot": "Chiefs", "conference":"AFC", "division": "West"},
      {"abbr":"MIA", "team":"Miami", "mascot": "Dolphins", "conference":"AFC", "division": "East"},
      {"abbr":"MIN", "team":"Minnesota", "mascot": "Vikings", "conference":"NFC", "division": "North"},
      {"abbr":"NE", "team":"New England", "mascot": "Patriots", "conference":"AFC", "division": "East"},
      {"abbr":"NO", "team":"New Orleans", "mascot": "Saints", "conference":"NFC", "division": "South"},
      {"abbr":"NYG", "team":"New York", "mascot": "Giants", "conference":"NFC", "division": "East"},
      {"abbr":"NYJ", "team":"New York", "mascot": "Jets", "conference":"AFC", "division": "East"},
      {"abbr":"OAK", "team":"Oakland", "mascot": "Raiders", "conference":"AFC", "division": "West"},
      {"abbr":"PHI", "team":"Philadelphia", "mascot": "Eagles", "conference":"NFC", "division": "East"},
      {"abbr":"PIT", "team":"Pittsburgh", "mascot": "Steelers", "conference":"AFC", "division": "North"},
      {"abbr":"SD", "team":"San Diego", "mascot": "Chargers", "conference":"AFC", "division": "West"},
      {"abbr":"SEA", "team":"Seattle", "mascot": "Seahawks", "conference":"NFC", "division": "West"},
      {"abbr":"SF", "team":"San Francisco", "mascot": "49ers", "conference":"NFC", "division": "West"},
      {"abbr":"STL", "team":"St. Louis", "mascot": "Rams", "conference":"NFC", "division": "West"},
      {"abbr":"TB", "team":"Tampa Bay", "mascot": "Buccaneers", "conference":"NFC", "division": "South"},
      {"abbr":"TEN", "team":"Tennessee", "mascot": "Titans", "conference":"AFC", "division": "South"},
      {"abbr":"WAS", "team":"Washington", "mascot": "Redskins", "conference":"NFC", "division": "East"}
    ];
    return NFL;
  });

Writing the NFL Controller

Next, create a new controller, nfl.js:

public/js/controllers/nfl.js
Stop scrolling to load code snippet
window.angular.module('ngff.controllers.nfl', [])
  .controller('NFLController', ['$scope','$routeParams','Global','NFL',
    function($scope, $routeParams, Global, NFL) {
      $scope.global = Global;

    }]);

You will do a boilerplate inclusion of Global in all controllers, as you can expect to use it at some point. Additionally, the $routeParams service is being injected, and you will use this for the first time in a bit.

Fill out this scope. You will use this controller to view all the teams, as well as individual teams, so you will have the controller prepare for both.

public/js/controllers/nfl.js
Stop scrolling to load code snippet
window.angular.module('ngff.controllers.nfl', [])
  .controller('NFLController', ['$scope','$routeParams','Global','NFL',
    function($scope, $routeParams, Global, NFL) {
      $scope.global = Global;

      $scope.nflteams = NFL.teams;
      $scope.nflteam = NFL.teams[$routeParams['nflTeamId']];
    }]);

Includes and Dependencies

You still need to include these new controller and service files with the rest of the application, so add the paths to app/views/includes/foot.jade:

app/views/includes/foot.jade
Stop scrolling to load code snippet
script(type='text/javascript', src='js/filters.js')

script(type='text/javascript', src='js/services/global.js')
script(type='text/javascript', src='js/services/nfl.js')

script(type='text/javascript', src='js/controllers/index.js')
script(type='text/javascript', src='js/controllers/header.js')	
script(type='text/javascript', src='js/controllers/nfl.js')	

script(type='text/javascript', src='js/init.js')

Since you are also declaring these as modules, you must add them to the module dependencies in public/js/app.js:

public/js/app.js
Stop scrolling to load code snippet
window.angular.module('ngff.controllers', ['ngff.controllers.header', 'ngff.controllers.index', 'ngff.controllers.nfl']);
window.angular.module('ngff.services',['ngff.services.global','ngff.services.nfl']);

Routing

You should be familiar with Angular views, templating, and routing before beginning this section. If not, read through Part 7: The View and the DOM, Part 8: Templates, and Part 9: Routing

Now add routes for each of these views. The view templates will be public/views/nfl/list.html and public/views/nfl/view.html, and since they’re both using the same controller, you can just set that in config.js:

public/js/config.js
Stop scrolling to load code snippet
window.app.config(['$routeProvider', function($routeProvider) {
  $routeProvider
  .when('/', 
  { 
    templateUrl: 'views/index.html' 
  })
  .when('/nflteams',
  {
    templateUrl: 'views/nfl/list.html'
  })
  .when('/nflteams/:nflTeamId',
  {
    templateUrl: 'views/nfl/view.html'
  })
  .otherwise({redirectTo: '/'});
}]);

Notice that :nflTeamId exists for the individual team route, and the string value of this in the URL will be available as $routeParams[‘nflTeamId’] in the controller once $routeParams is injected.

Views

Create a new directory public/views/nfl/, and in it, create a list.html view:

public/views/nfl/list.html
Stop scrolling to load code snippet
<section ng-controller="NFLController">
  <h2>NFL Teams</h2>
  <div ng-repeat="nflteam in nflteams">
    <a href="#!/nflteams/{{ $index }}">{{ nflteam.team }} {{ nflteam.mascot }}</a>
  </div>
</section>

Here, you are introduced to the ng-repeat directive for the first time. nflteams is the collection, nflteam is the iterator, and each instance in nflteams will render the ng-repeat HTML, here an <a> tag inside a <div>. $index is simply the 0-based index of iterator, which here is the ID of the NFL team.

Also create view.html:

public/views/nfl/view.html
Stop scrolling to load code snippet
<section ng-controller="NFLController">
  <h2>{{ nflteam.team }} {{ nflteam.mascot }}</h2>
  <p><strong>Abbreviation: </strong>{{ nflteam.abbr }}</p>
  <p><strong>Conference: </strong>{{ nflteam.conference }}</p>
  <p><strong>Division: </strong>{{ nflteam.division }}</p>
  <p><a href="#!/nflteams">All NFL teams</a></p>
</section>

Hopefully nothing surprising here.

With all of this, you should have your first working pages in your application! Run the server, and navigate to localhost:3000/#!/nflteams to try it out.

Getting Into the CRUD

While all this bandying about on the frontend has been dandy, it’s high time you got to working out the entire stack. This section is going to contain a considerable amount of Node.js, but in order to build an interesting and persistent web application, use of a server and database is necessary. It is worth mentioning that, while NodeJS bundled with ExpressJS and MongoDB is an excellent option, you could just as easily substitute any other web server and database to work with AngularJS. 

With that, let’s get started.

League CRUD

Leagues are as good as any piece of the application to start with.

You will start off as simply as possible. A league will have a name, simply a string, and a commissioner, which will be a User.

Working In the Backend

Recall that, when building backend functionality, these files go in the app/ directory.

Model

Create a new model file league.js in the app/models/ directory with the boilerplate code:

app/models/league.js
Stop scrolling to load code snippet
var mongoose = require('mongoose')
  , env = process.env.NODE_ENV || 'development'
  , config = require('../../config/config')[env]
  , Schema = mongoose.Schema;

The ORM used for MongoDB in this framework is MongooseJS. You’ll tackle some more advanced stuff with it later on, but for now, the concepts shown should be relatively familiar to you if you have used MongoDB or a similar NoSQL database before.

Next, define the schema for a league:

app/models/league.js
Stop scrolling to load code snippet
var LeagueSchema = new Schema({
  name: {type: String},
  commissioner: {type: Schema.ObjectId, ref: 'User'}
});

This creates the simple schema described above. Using the Schema.ObjectID type will allow us to more easily fill out model references when querying.

Read more about statics model methods here: http://mongoosejs.com/docs/2.7.x/docs/methods-statics.html

Following this, define a statics method on the model:

app/models/league.js
Stop scrolling to load code snippet
LeagueSchema.statics = {
  load: function (id, cb) {
    this.findOne({ _id : id }).populate('commissioner').exec(cb);
  }
};

The populate() method uses the commissioner BSON ObjectId to find the matching document in the ‘User’ model. You can call load() from the controller to cleanly retrieve a populated League document.

Following this, compile the League model with the LeagueSchema you just constructed:

app/models/league.js
Stop scrolling to load code snippet
mongoose.model('League', LeagueSchema);

Thus, league.js in full:

app/models/league.js
Stop scrolling to load code snippet
var mongoose = require('mongoose')
  , env = process.env.NODE_ENV || 'development'
  , config = require('../../config/config')[env]
  , Schema = mongoose.Schema;

var LeagueSchema = new Schema({
	name: {type: String},
	commissioner: {type: Schema.ObjectId, ref: 'User'}
});

LeagueSchema.statics = {
  load: function (id, cb) {
    this.findOne({ _id : id }).populate('commissioner').exec(cb);
  }
};

mongoose.model('League', LeagueSchema);

League Controller

The model you just made is worthless without its accompanying controller.

Create leagues.js with the following controller boilerplate, which is a fairly self-explanatory set of object setup and require calls:

app/controllers/leagues.js
Stop scrolling to load code snippet
var mongoose = require('mongoose')
  , async = require('async')
  , League = mongoose.model('League')
  , _ = require('underscore')

The controller format may seem a bit bizarre, but it will make sense very soon. You’re going to need 6 methods:

create() creates a league, returns the new league object

league() takes a league id, finds the league, and assigns it to the request object

show() returns the league object in the request object

all() returns an array of all leagues

update() updates a league, returns the updated league object

destroy() destroys a league, returns the destroyed league object

Add the following methods:

create()

app/controllers/leagues.js
Stop scrolling to load code snippet
exports.create = function (req, res) {
  var league = new League(req.body)
  league.commissioner = req.user
  league.save()
  res.jsonp(league)
}

AngularJS takes the form data for the new league and sends it as part of the request body. For now, it’s only the name of the league, but you can pass the req.body object to the League constructor and it will take care of the rest. Also, ExpressJS identifies the user that generated the request with the request cookie, and packages the user object into the request as req.user, so you can use that here, too, for the commissioner field. A call to save() on the league object writes to the database, and you return the league object as the request response with res.jsonp().

show()

app/controllers/leagues.js
Stop scrolling to load code snippet
exports.show = function(req, res){
  res.jsonp(req.league);
}

Try to contain your excitement, I know it’s difficult. This is invoked when the league object is already part of the request object, and simply responds with jsonp().

league()

app/controllers/leagues.js
Stop scrolling to load code snippet
exports.league = function(req, res, next, id){
  var League = mongoose.model('League')

  League.load(id, function (err, league) {
    if (err) return next(err)
    if (!league) return next(new Error('Failed to load league ' + id))
    req.league = league
    next()
  })
}

This one is a bit longer, but it’s mostly error handling, something you’ll get to a bit later.

When this is invoked, it uses the statics method you wrote earlier to retrieve a league document from the database by id.  On success, it adds the retrieved document to the request object, and invokes next().

all()

app/controllers/leagues.js
Stop scrolling to load code snippet
exports.all = function(req, res){
 League.find().populate('commissioner').exec(function(err, leagues) {
   if (err) {
     res.render('error', {status: 500});
   } else {      
       res.jsonp(leagues);
   }
 });
}

Most of this should look pretty familiar. It is performing a more or less identical operation to the load() statics method, but to all the league documents returned. On success, it returns an array of all the populated league objects.

update()

app/controllers/leagues.js
Stop scrolling to load code snippet
exports.update = function(req, res){
  var league = req.league
  league = _.extend(league, req.body)

  league.save(function(err) {
    res.jsonp(league)
  })
}

Update should look pretty straightforward at this point. The only thing to note here is the utilization of the Underscore.JS extend() method, which is simply merging the request’s old league object, and the req.body new league object, giving priority to the req.body object for overlapping fields. It then saves and returns the new object.

destroy()

app/controllers/leagues.js
Stop scrolling to load code snippet
exports.destroy = function(req, res){
  var league = req.league
  league.remove(function(err){
    if (err) {
      res.render('error', {status: 500});
    }  else {
      res.jsonp(1);
    }
  })
}

Not much new material to explain here. The remove() method operates pretty much as you would expect.

At this point, a lot of the way this is structured probably still doesn’t make sense to you. Not to worry, much will be revealed now that you’ve arrived at server-side routing.

League Routing

Next, move into the final remaining project folder, the config/ directory. For now, the only file you care about in here is routes.js. Add the following routes:

config/routes.js
Stop scrolling to load code snippet
  ...
  app.param('userId', users.user)

  var leagues = require('../app/controllers/leagues')  
  app.get('/leagues', leagues.all)
  app.post('/leagues', auth.requiresLogin, leagues.create)
  app.get('/leagues/:leagueId', leagues.show)
  app.put('/leagues/:leagueId', auth.requiresLogin, leagues.update)
  app.del('/leagues/:leagueId', auth.requiresLogin, leagues.destroy)

  app.param('leagueId', leagues.league)

After requiring the leagues controller, the CRUD routes are fairly plain if you are familiar with routing patterns. It’s worth examining the config/middlewares/authorization.js file to understand what is going on there with middleware, but you won’t worry about it for now, as it too is straightforward. All you need to know to understand these routes is that the methods following the route string are 'chained' and invoked serially.

The app.param() method looks for a :leagueId parameter in the URL string params only, and if it sees one, it invokes the leagues.league() method. As shown before, the leagues.league method will find the league document by the leagueId and add it as a league attribute to the request object. Recall that many of the league controller routes are able to access the league object directly from the request object with req.league - this is how that is possible. 

Frontend Complement

Now that the backend is squared away, let’s build out the frontend components.

Start off by adding all the necessary routes to public/js/config.js. They should be inserted following the nflteams routes, and should come before the otherwise() clause:

public/js/config.js
Stop scrolling to load code snippet
  .when('/leagues', 
  { 
    templateUrl: 'views/leagues/list.html' 
  })
  .when('/leagues/create', 
  { 
    templateUrl: 'views/leagues/create.html' 
  })  
  .when('/leagues/:leagueId/edit', 
  { 
    templateUrl: 'views/leagues/edit.html' 
  })
  .when('/leagues/:leagueId', 
  { 
    templateUrl: 'views/leagues/view.html' 
  })

Frontend Service

The AngularJS $resource service is an incredibly powerful one which allows you to concisely interact with RESTful APIs. You’ve surely realized by now that that is exactly what you created on the backend, and AngularJS makes it incredibly easy to use.

Create a new service file, leagues.js, with the following:

public/js/services/leagues.js
Stop scrolling to load code snippet
window.angular.module('ngff.services.leagues', [])
  .factory('Leagues', ['$resource',
    function($resource){
      return $resource(
        'leagues/:leagueId',
        {
          leagueId:'@_id'
        },
        {
          update: {method: 'PUT'}
        }
      )
    }]);

This service returns an instance of the $resource service. $resource is quite versatile, and you can read this for a refresher: http://docs.angularjs.org/api/ngResource.$resource.  

Here, you set the parameterized resource url, ‘leagues/:leagueId’, the paramDefaults as the object’s _id attribute, and override the $update method to use PUT instead of POST, to match what you constructed on the backend. The @ in the paramDefaults will attempt to extract a leagueId from the resource object provided to it.

Frontend Controller

This service is short but versatile, and now you can apply it in the controller, which will exist in a new file public/js/controllers/leagues.js. This controller will have five methods:

create() creates a new league object on the backend, then navigates to the new league page

find() finds leagues in the database based on a single query parameter, which can be blank

findOne() retrieves a league by id from the URL parameters

update() updates a league by id

destroy() destroys the league object by id

Start off the new leagues.js controller with the proper setup, including all necessary dependency injection:

public/js/controllers/leagues.js
Stop scrolling to load code snippet
window.angular.module('ngff.controllers.leagues', [])
  .controller('LeaguesController', ['$scope','$routeParams','$location','Global','Leagues',
    function ($scope, $routeParams, $location, Global, Leagues) {
      $scope.global = Global;

    }]);

Add the following methods:

create()

public/js/controllers/leagues.js
Stop scrolling to load code snippet
  $scope.create = function () {
    var league = new Leagues({ 
      name: this.league.name
    });

    league.$save(function (response) {
      $location.path("leagues/" + response._id);
    });

    this.league.name = "";
  };

A new league service object is created with the form data, and the $save method is invoked. The $save callback will use the $location service to navigate to the individual league page for the new league. Finally, you’ll clear the existing form data.

find()

public/js/controllers/leagues.js
Stop scrolling to load code snippet
  $scope.find = function (query) {
    Leagues.query(query, function (leagues) {
      $scope.leagues = leagues;
    });
  };

Relatively straightforward - the query parameter is passed to the query method on the Leagues service object, and the returned array is assigned to the scope in the callback.

findOne()

public/js/controllers/leagues.js
Stop scrolling to load code snippet
$scope.findOne = function () {
  Leagues.get({ leagueId: $routeParams.leagueId }, function (league) {
    $scope.league = league;
  });
};

This one is very similar to find(), instead using the get() method in the service object using the leagueId identifier. The callback operates in the same way.

update()

public/js/controllers/leagues.js
Stop scrolling to load code snippet
$scope.update = function () {
  var league = $scope.league;
  league.$update(function () {
    $location.path('leagues/' + league._id);
  });
};

This time, you use the $update method for the league object, and redirect with $location in the callback.

destroy()

public/js/controllers/leagues.js
Stop scrolling to load code snippet
$scope.destroy = function (league) {
  league.$remove();
  for (var i in $scope.leagues) {
    if ($scope.leagues[i] == league) {
      $scope.leagues.splice(i, 1)
    }
  }
};

Here, you remove the league object on the backend with $remove, and then remove it from the $scope array in Angular.

This completes the controller! 

Remember to include it and the League service you created in foot.jade. 

Also, you declared two new modules, 'ngff.services.leagues' and 'ngff.controllers.leagues'. Make sure and add them to app.js under the 'ngff.services' and 'ngff.controllers' modules.

League Views

Next, create the four views referenced above in a new directory, public/views/leagues/:

list.html

public/views/leagues/list.html
Stop scrolling to load code snippet
<div ng-controller="LeaguesController" ng-init="find()">
  <ul class="unstyled">
    <h2>Leagues</h2>
    <li ng-repeat="league in leagues">
      <a href="#!/leagues/{{ league._id }}">{{league.name}}</a>
      (<a href="#!/leagues/{{ league._id }}/edit">Edit</a>)
      (<a href="" ng-click="destroy(league)" >Remove</a>)
    </li>
  </ul>
  <br>
  <a href="#!/leagues/create">Create a new league</a>
</div>

Instead of assigning the controller, you assign it in the parent element in the view. Also, notice that you are using the ng-init directive, which will invoke the find() method before the template enters execution mode during bootstrap. find() will be defined shortly.

create.html

public/views/leagues/create.html
Stop scrolling to load code snippet
<div ng-controller="LeaguesController">
  <form class="form-horizontal" ng-submit="create()">
    <div class="control-group">
      <label class="control-label" for="name">Name</label>
      <div class="controls">
        <input type="text" ng-model="league.name" id="name" placeholder="Name">
      </div>
    </div>
    <div class="control-group">
      <div class="controls">
        <input type="submit" class="btn">
      </div>
    </div>
  </form>
</div>

The first form of the application! As you might expect, the ng-submit directive invokes the create() method when the form is submitted.

edit.html

public/views/leagues/edit.html
Stop scrolling to load code snippet
<div ng-controller="LeaguesController">
  <form class="form-horizontal" ng-submit="update()" ng-init="findOne()">
    <div class="control-group">
      <label class="control-label" for="name">Name</label>
      <div class="controls">
        <input type="text" ng-model="league.name" id="name" placeholder="Name">
      </div>
    </div>
    <div class="control-group">
      <div class="controls">
        <input type="submit" class="btn">
      </div>
    </div>
  </form>
</div>

This form is more or less the same as create.html, with two important differences: the ng-submit method, and the ng-init method.

view.html

public/views/leagues/view.html
Stop scrolling to load code snippet
<div ng-controller="LeaguesController" ng-init="findOne()">
  <h2>{{ league.name }}</h2>
  <p><strong>Commissioner: </strong>{{ league.commissioner.name }}</p>
  <br>
  <a href="#!/leagues">View all leagues</a>
</div>

The simplest of the views. There should be no surprises here.

After all this, you should be able to create, view, edit, and remove fantasy leagues. 

Congratulations! You’ve finished the first Angular CRUD functionality.

Adding Fantasy Teams

Next, you’re going to add the CRUD functionality for fantasy teams

Fantasy teams have a name (string), an owner (User), are in one league (League), and has many players (array of Players).

Much of the fantasy team infrastructure will be very similar to Leagues.

Model

Create fantasyteam.js as follows:

app/models/fantasyteam.js
Stop scrolling to load code snippet
var mongoose = require('mongoose')
  , env = process.env.NODE_ENV || 'development'
  , config = require('../../config/config')[env]
  , Schema = mongoose.Schema;

var FantasyTeamSchema = new Schema({
  owner: {type: Schema.ObjectId, ref: 'User'},
  league: {type: Schema.ObjectId, ref: 'League'},
  name : {type: String},
  players : [{type: Schema.ObjectId, ref: 'Player'}]
});

FantasyTeamSchema.statics = {
  load: function (id, cb) {
    this.findOne({ _id : id }).populate('owner').populate('league').exec(cb);
  }
};

mongoose.model('FantasyTeam', FantasyTeamSchema);

The fantasy team will eventually have an array of player BSON ids, so you will set this up here, but it won’t be used until later.

Fantasy Team Controller

Create fantasyteams.js as follows:

app/controllers/fantasyteams.js
Stop scrolling to load code snippet
var mongoose = require('mongoose')
  , async = require('async')
  , FantasyTeam = mongoose.model('FantasyTeam')
  , _ = require('underscore')

exports.create = function (req, res) {
  var fantasyteam = new FantasyTeam(req.body)
  fantasyteam.owner = req.user
  fantasyteam.league = req.body.league
  fantasyteam.save()
  res.jsonp(fantasyteam)
}

exports.show = function(req, res){
  res.jsonp(req.fantasyteam);
}

exports.fantasyteam = function(req, res, next, id){
  var FantasyTeam = mongoose.model('FantasyTeam')
  FantasyTeam.load(id, function (err, fantasyteam) {
    if (err) return next(err)
    if (!fantasyteam) return next(new Error('Failed to load fantasy team ' + id))
    req.fantasyteam = fantasyteam
    next()
  })
}

exports.all = function(req, res){
 FantasyTeam.find().populate('owner').populate('league').exec(function(err, fantasyteams) {
   if (err) {
      res.render('error', {status: 500});
   } else {      
      res.jsonp(fantasyteams);
   }
 });
}

exports.update = function(req, res){
  var fantasyteam = req.fantasyteam
  fantasyteam = _.extend(fantasyteam, req.body)
  fantasyteam.save(function(err) {
    res.jsonp(fantasyteam)
  })
}

exports.destroy = function(req, res){
  var fantasyteam = req.fantasyteam
  fantasyteam.remove(function(err){
    if (err) {
      res.render('error', {status: 500});
    }  else {
      res.jsonp(1);
    }
  })
}

This should all look very similar to the league controller you wrote before. The only real tweak here is taking the league reference from the frontend and turning it into a BSON id in the document.

Fantasy Team Routing

Modify routes.js as follows:

config/routes.js
Stop scrolling to load code snippet
  ...  
  app.param('leagueId', leagues.league)

  // fantasy team routes
  var fantasyteams = require('../app/controllers/fantasyteams')  
  app.get('/fantasyteams', fantasyteams.all)
  app.post('/fantasyteams', auth.requiresLogin, fantasyteams.create)
  app.get('/fantasyteams/:fantasyTeamId', fantasyteams.show)
  app.put('/fantasyteams/:fantasyTeamId', auth.requiresLogin, fantasyteams.update)
  app.del('/fantasyteams/:fantasyTeamId', auth.requiresLogin, fantasyteams.destroy)

  app.param('fantasyTeamId', fantasyteams.fantasyteam)

This is functionally identical to the leagues routes.

AngularJS Routing

Modify config.js as follows:

public/js/config.js
Stop scrolling to load code snippet
  .when('/fantasyteams', 
  {
    templateUrl: 'views/fantasyteams/list.html'
  })
  .when('/fantasyteams/create', 
  { 
    templateUrl: 'views/fantasyteams/create.html' 
  })  
  .when('/fantasyteams/:fantasyTeamId/edit', 
  { 
    templateUrl: 'views/fantasyteams/edit.html' 
  })
  .when('/fantasyteams/:fantasyTeamId', 
  { 
    templateUrl: 'views/fantasyteams/view.html' 
  })

This is functionally identical to the leagues routes. Once again, this should come following the leagues routes, but preceding the otherwise() clause.

Fantasy Teams Service

Create fantasyteams.js as follows:

public/js/services/fantasyteams.js
Stop scrolling to load code snippet
window.angular.module('ngff.services.fantasyTeams', [])
  .factory('FantasyTeams', ['$resource', 
    function($resource){
      return $resource(
        'fantasyteams/:fantasyTeamId', 
        {
          fantasyTeamId:'@_id'
        }, 
        {
          update: {method: 'PUT'}
        }
      )
    }]);

This is functionally identical to the leagues service.

Fantasy Teams Controller

Create fantasyteams.js as follows:

public/js/controllers/fantasyteams.js
Stop scrolling to load code snippet
window.angular.module('ngff.controllers.fantasyTeams', [])
  .controller('FantasyTeamsController', ['$scope','$routeParams','$location','Global','Leagues','FantasyTeams',
    function($scope, $routeParams, $location, Global, Leagues, FantasyTeams) {

      $scope.global = Global;

      $scope.populateLeagues = function(query) {
        Leagues.query(query, function (leagues) {
          $scope.leagues = leagues;
        });
      };

      $scope.create = function () {
        var fantasyteam = new FantasyTeams({ 
          league: this.fantasyteam.league,
          name: this.fantasyteam.name,
          players: this.players 
        });

        fantasyteam.$save(function (response) {
          $location.path("fantasyteams/" + response._id);
        });

        this.league = "";
        this.name = "";
        this.players = [];
      };

      $scope.update = function () {
        var fantasyteam = $scope.fantasyteam;

        fantasyteam.$update(function () {
          $location.path('fantasyteams/' + fantasyteam._id);
        });
      };

      $scope.find = function (query) {
        FantasyTeams.query(query, function (fantasyteams) {
          $scope.fantasyteams = fantasyteams;
        });
      };

      $scope.findOne = function () {
        FantasyTeams.get({ fantasyTeamId: $routeParams.fantasyTeamId }, function (fantasyteam) {
          $scope.fantasyteam = fantasyteam;
        });
      };

      $scope.remove = function (fantasyteam) {
        fantasyteam.$remove();
        for (var i in $scope.fantasyteams) {
          if ($scope.fantasyteams[i] == fantasyteam) {
            $scope.fantasyteams.splice(i, 1)
          }
        }
      };
    }]);

Again, you created a FantasyTeam service and controller that Angular needs to be informed about. Make sure and include the .js files in foot.jade, and also add the dependencies 'ngff.services.fantasyTeams' and 'ngff.controllers.fantasyTeams' to app.js.

Since you need to populate a dropdown for all leagues in the database, you can see that the Leagues service is injected here, and the populateLeagues() method in the scope retrieves all the Leagues for the dropdown. Everything else here shouldn't surprise you, as it functions much in the same way as the Leagues angular controller. Notice again that you set up the functionality for players, but it won’t be used until later.

Frontend Views

Create list.html:

public/views/fantasyteams/list.html
Stop scrolling to load code snippet
<div ng-controller="FantasyTeamsController" ng-init="find()">
  <ul class="unstyled">
    <h2>Fantasy Teams</h2>
      <li ng-repeat="fantasyteam in fantasyteams">
      <a href="#!/fantasyteams/{{ fantasyteam._id }}">{{fantasyteam.name}}</a>
      (<a href="#!/fantasyteams/{{ fantasyteam._id }}/edit">Edit</a>)
      (<a href="" ng-click="remove(fantasyteam)" >Remove</a>)
    </li>
  </ul>
  <br>
  <a href="#!/fantasyteams/create">Create a new fantasy team</a>
</div>

Create create.html:

public/views/fantasyteams/create.html
Stop scrolling to load code snippet
<div ng-controller="FantasyTeamsController"> 
  <form class="form-horizontal" ng-submit="create()" ng-init="populateLeagues()"> 
    <div class="control-group"> 
      <label class="control-label" for="name">Name</label> 
      <div class="controls"> 
        <input type="text" ng-model="fantasyteam.name" id="name" placeholder="Name"> 
      </div> 
      <label class="control-label" for="league">League</label> 
      <div class="controls"> 
        <select ng-model="fantasyteam.league" name="league" required="required" ng-options="c._id as c.name for c in leagues"> 
          <!-- <option value="">Choose a league:</option> --> 
        </select> 
      </div> 
    </div> 
    <div class="control-group"> 
      <div class="controls"> 
        <input type="submit" class="btn"> 
      </div> 
    </div> 
  </form>
</div>

Here, you are seeing the ng-options directive for the first time. In the ng-init directive, you are invoking the populateLeagues() method to attach an array of all leagues returned from the service to the scope. ng-options takes this enumerable object ‘leagues’, iterates through each league ‘c’, and creates an <option> with text ‘c.name’ and value ‘c._id’. 

The default <option> entry is provided commented out to demonstrate an interesting aspect of how AngularJS handles the view interacting with the model. Without an default <option> with an empty value provided, you will notice that Angular provides a blank one to you anyway on a new page load. However, when an option is selected, the blank one will disappear. This is because, when building out the ng-options directive, the ng-model is initialized as empty. None of the values of the <option>s in the league array match that value, so it adds a blank one. Once you select an option, the model takes on that value, and the empty <option> is no longer necessary.

Uncomment the default <option> above in the view.

Create edit.html:

public/views/fantasyteams/edit.html
Stop scrolling to load code snippet
<div ng-controller="FantasyTeamsController"> 
  <form class="form-horizontal" ng-submit="update()" ng-init="findOne();populateLeagues()"> 
    <div class="control-group"> 
      <label class="control-label" for="name">Name</label> 
      <div class="controls"> 
        <input type="text" ng-model="fantasyteam.name" id="name" placeholder="Name"> 
      </div>      
      <label class="control-label" for="league">League</label> 
      <div class="controls"> 
        <select ng-model="fantasyteam.league._id" name="league" required="required" ng-options="c._id as c.name for c in leagues"> 
          <option value="">Choose a league:</option> 
        </select> 
      </div> 
    </div> 
    <div class="control-group"> 
      <div class="controls"> 
        <input type="submit" class="btn"> 
      </div> 
    </div> 
  </form>
</div>

Nearly identical to create.html. One thing to notice is that it is possible to chain multiple methods in the ng-init directive, as done above.

Create view.html:

public/views/fantasyteams/view.html
Stop scrolling to load code snippet
<div ng-controller="FantasyTeamsController" ng-init="findOne()">
  <h2>{{ fantasyteam.name }}</h2>
  <p><strong>Owner: </strong>{{ fantasyteam.owner.name }}</p>
  <p><strong>League: </strong>{{ fantasyteam.league.name }}</p>
  <p><strong>Players:</strong></p>
  <ul>
    <li ng-repeat="player in fantasyteam.players">{{ player }}</li>
  </ul>
  <br>
  <a href="#!/fantasyteams">View all fantasy teams</a>
</div>

That's it! With all of this, you should now have complete league and fantasy team CRUD functionality.

Test your application out and make sure you are able to perform CRUD operations on leagues and teams.

Navigation

At this point, you’re probably getting pretty tired of navigating by typing in the url, so let’s add some links to the navbar.

Return to the HeaderController, and add in the following:

public/js/controllers/header.js
Stop scrolling to load code snippet
$scope.navbarEntries = [
  {
    "title": "Leagues",
    "link": "leagues"
  },
  {
    "title": "Fantasy Teams",
    "link": "fantasyteams"
  },
  {
    "title": "NFL Teams",
    "link": "nflteams"
  },
  {
    "title": "Players",
    "link": "players"
  }
];

Next, add in some logic to the view so that these buttons only show up when the user is logged in:

public/views/header.html
Stop scrolling to load code snippet
<div class="navbar-inner" ng-controller="HeaderController">
  <ul class="nav">
    <li><a class="brand" href="/">ngFantasyFootball</a></li>
    <li ng-repeat="entry in navbarEntries" ng-show="global.isSignedIn()"><a href="#!/{{entry.link}}">{{entry.title}}</a></li>
  </ul>
  <ul class="nav pull-right" ng-hide="global.isSignedIn()">
    <li><a href="signup">Signup</a></li>
    <li class="divider-vertical"></li>
    <li><a href="signin">Signin</a></li>
  </ul>
  <ul class="nav pull-right" ng-show="global.isSignedIn()">
    <li class="dropdown">
      <a href="#" class="dropdown-toggle" data-toggle="dropdown">
        {{global.currentUser().name}} <b class="caret"></b>
      </a>
      <ul class="dropdown-menu">
        <li ><a href="/signout">Signout</a></li>
      </ul>
    </li>
  </ul>
</div>

Building The Player Picker

Before beginning this section, you should be familiar with Angular filters. If not, read through Part 3: Filters.

Also, this section will build some custom directives, so familiarize yourself with Part 4: Directives.

Adding Positions to the NFL Service

Add a positions attribute, an array of NFL player positions, to the nfl.js service:

public/js/services/nfl.js
Stop scrolling to load code snippet
    NFL.positions = [
      {"abbr":"QB",  "pos":"Quarterback"},
      {"abbr":"RB",  "pos":"Runningback"},
      {"abbr":"WR",  "pos":"Wide Receiver"},
      {"abbr":"TE",  "pos":"Tight End"},
      {"abbr":"K",   "pos":"Kicker"},
      {"abbr":"D/ST","pos":"Defense/Special Teams"}
    ];

You will be using this shortly.

Player Picker Directives

When picking players, you’re going to want to filter by name, position, team, and you’re also going to want to change the number of results returned. For this, let’s build out some directives!

Modify directives.js as follows:

public/js/directives.js
Stop scrolling to load code snippet
window.angular.module('ngff.directives', [])
  .directive('positions', function() {
    return {
      restrict: "E",
      templateUrl: "views/players/positionselect.html"
    };
  })

This directive will allow us to use <positions></positions> in a view to insert a dropdown of NFL positions.

Directive Components

Obviously, you’re going to need to build a PlayersController, as well as write a positionselect view.

Create a players.js controller file, and fill it with the following:

public/js/controllers/players.js
Stop scrolling to load code snippet
window.angular.module('ngff.controllers.players', [])
  .controller('PlayersController', ['$scope', 'Global', 'NFL',
    function ($scope, Global, NFL) {
      $scope.global = Global;

      $scope.positions = NFL.positions;
      $scope.nflteams = NFL.teams;
      $scope.limitct = 10;
    }]);

Simple enough, you’re just handing off the data from the NFL data service.

Add the players.js file to foot.jade, and add the ngff.controllers.players dependency to app.js

Next, create positionselect.html in a new directory, public/views/players/ and fill it with the following:

public/views/players/positionselect.html
Stop scrolling to load code snippet
<select ng-model="search.pos">
  <option value="">All positions</option>
  <option ng-repeat="position in positions" value="{{ $index }}">{{ position.abbr }}</option>
</select>

Normally, the ng-options directive would be used here, but you need the $index iterator, so ng-repeat is substituted.

You are also attaching the value to the search.pos model in the scope, which will be used to filter players shortly.

Wiring It All Up

With this, you can now begin to build the player picker view. 

Add the following single route to config.js:

public/js/config.js
Stop scrolling to load code snippet
  .when('/players',
  {
    templateUrl: 'views/players/list.html'
  })

In the same way as before, it must come after the fantasy team routes, and before the otherwise() clause.

Create the views/players/list.html view, with only the new directive in it:

public/views/players/list.html
Stop scrolling to load code snippet
<div ng-controller="PlayersController">
  <positions></positions>
</div>

Navigating to the players route should show a dropdown with the NFL positions filled in.

More Selectors

With this working, add two more directives to directives.js:

public/js/directives.js
Stop scrolling to load code snippet
.directive('nflteams', function() {
  return {
    restrict: "E",
    templateUrl: "views/players/nflteamselect.html"
  };
})
.directive('searchlimit', function() {
  return {
    restrict: "E",
    templateUrl: "views/players/searchlimitselect.html"
  };
})

And the respective views:

Create nflteamselect.html:

public/views/players/nflteamselect.html
Stop scrolling to load code snippet
<select ng-model="search.team">
  <option value="">All NFL teams</option>
  <option ng-repeat="nflteam in nflteams" value="{{ $index }}">{{ nflteam.team }}</option>
</select>

Create searchlimitselect.html:

public/views/players/searchlimitselect.html
Stop scrolling to load code snippet
<select ng-model="limitct">
  <option value="10">10</option>
  <option value="25">25</option>
  <option value="50">50</option>
</select>

Finally, add the new directive elements to the list.html view:

public/views/players/list.html
Stop scrolling to load code snippet
<div ng-controller="PlayersController">
  <positions></positions>
  <nflteams></nflteams>
  <searchlimit></searchlimit>
</div>

Player Backend

For now, the player model is a very simple one, consisting of four strings: pos, num, name, and team.

Create the new player.js model on the server as follows:

app/models/player.js
Stop scrolling to load code snippet
var mongoose = require('mongoose')
  , env = process.env.NODE_ENV || 'development'
  , config = require('../../config/config')[env]
  , Schema = mongoose.Schema;

var PlayerSchema = new Schema({
  pos: {type : String},
  num: {type : String},
  name: {type : String},
  team: {type : String}
});

 PlayerSchema.statics = {
   load: function (id, cb) {
     this.findOne({ _id : id }).exec(cb);
   }
 };

mongoose.model('Player', PlayerSchema);

Nothing here you haven’t seen before.

Next, create the server player controller, players.js:

app/controllers/players.js
Stop scrolling to load code snippet
var mongoose = require('mongoose')
  , async = require('async')
  , Player = mongoose.model('Player')
  , _ = require('underscore')

exports.show = function(req, res){
  res.jsonp(req.league);
}

exports.player = function(req, res, next, id){
  var Player = mongoose.model('Player')

  Player.load(id, function (err, player) {
    if (err) return next(err)
    if (!player) return next(new Error('Failed to load player' + id))
    req.player = player
    next()
  })
}

exports.all = function(req, res){
  Player.find(function(err, players) {
    if (err) {
      res.render('error', {status: 500});
    } else {			
      res.jsonp(players);
    }
  });
}

This is basically a stripped down version of the other controllers, as players are read-only for the end user’s purposes.

Next, add the server routes to routes.js:

config/routes.js
Stop scrolling to load code snippet
  app.param('fantasyTeamId', fantasyteams.fantasyteam)

  // player routes
  var players = require('../app/controllers/players')
  app.get('/players', players.all)
  app.get('/players/:playerId', players.show)

  app.param('playerId', players.player)

You’re going to let AngularJS do the filtering of the players instead of MongoDB. It’s true that this isn't in the best interest of performance, but this is more for demonstration purposes than anything.

Player Frontend

Build a similar service to interface with this read-only API, players.js:

public/js/services/players.js
Stop scrolling to load code snippet
window.angular.module('ngff.services.players', [])
  .factory('Players', ['$resource', 
    function($resource){
      return $resource(
        'players/:playerId', 
        {
          playerId:'@_id'
        }
      );
    }]);

Next, extend the PlayersController in players.js. Note the new injection of the Players service:

public/js/controllers/players.js
Stop scrolling to load code snippet
window.angular.module('ngff.controllers.players', [])
  .controller('PlayersController', ['$scope', 'Global', 'NFL', 'Players',
    function ($scope, Global, NFL, Players) {
      $scope.global = Global;

      $scope.positions = NFL.positions;
      $scope.nflteams = NFL.teams;
      $scope.limitct = 10;

      $scope.find = function (query) {
        Players.query(query, function (players) {
          $scope.players = players;
        })
      }
    }]);

In the players/list.html view, append a simple repeater to spit out all players returned:

public/views/players/list.html
Stop scrolling to load code snippet
<div ng-controller="PlayersController" ng-init="find()">
  <div ng-repeat="player in players">{{ player }}</div>
</div>

Make sure and include the new service and controller files in foot.jade, as well as add them to the app.js dependencies as you have done before.

As you’ve now surely realized, there are no players in the database, and there’s no way to populate it using the API you constructed. Fortunately, Thinkster has you covered. In the root directory of the project, you’ll find a scripts/ directory. In it, there is a Node script called mongoimport.js. Run it with node mongoimport, this will import all NFL players into your development database.

With this, you should see all the players printed out when you navigate to the players page.

Filters

You surely don’t want to see all the hundreds of players at once, so let’s build a filtering mechanism into the player picker.

Replace the div containing the raw player data from before with something a little more refined, shown below. Also, add in a text field to search for player names. Your list.html should now look like this:

public/views/players/list.html
Stop scrolling to load code snippet
<div ng-controller="PlayersController" ng-init="find()">
  <input type="text" ng-model="search.name">
  <positions></positions>
  <nflteams></nflteams>
  <searchlimit></searchlimit>
  <table>
    <tr ng-repeat="player in players | filter:search | limitTo:limitct">
      <td>{{ player.pos }}</td>
      <td>{{ player.num }}</td>
      <td>{{ player.name }}</td>
    </tr>
  </table>
</div>

The search object will be taken from the positions and nflteams dropdowns, as well as the text input. The object’s attributes will be matched up against those in the players objects, and matching ones will be filtered out. You are now also using a limit, taken from the limitct dropdown.

If you did everything properly, you should now be able to filter players by name, position, team, and limit the number of results. 

Give yourself a pat on the back, this is pretty cool.

Corner Cases

If you play around with the player picker enough, you might have noticed that filtering by team doesn’t QUITE work correctly. Selecting, for example, to filter for players on Arizona returns players on Arizona, Detroit, New York, and Tennessee. This is because the indexes of these teams are 0, 10, 20, and 30, and you are searching for teams against the index ‘0’. Angular’s returns all results with a ‘0’ in their team index, as this is technically a match. You must create a custom filter that will return values with exact matches, not matching substrings.

AngularJS v1.1.5 introduced the comparator option for filters, which allows the user to use either presets, or a custom comparison function, to determine what the filter will return. For your purposes, passing in ‘true’ will require identical values for a match to be recognized. The comparator defaults to the substring match that you are using now.

Refactor

Each comparator value that is applied to a filter is applied to the entire search object. You don’t want to use exact string matches for the player names, only for the position and NFL team dropdowns. Thus, you will need to separate them out into two search objects, and filter separately.

Return to your directive templates and attach them to a new ‘strictsearch’ model:

Create positionselect.html:

public/views/players/positionselect.html
Stop scrolling to load code snippet
<select ng-model="strictsearch.pos">
  <option value="">All positions</option>
  <option ng-repeat="position in positions" value="{{$index}}">{{position.abbr}}</option>
</select>

Create nflteamselect.html:

public/views/players/nflteamselect.html
Stop scrolling to load code snippet
<select ng-model="strictsearch.team">
  <option value="">All NFL teams</option>
  <option ng-repeat="nflteam in nflteams" value="{{ $index }}">{{ nflteam.team }}</option>
</select>

Now you can chain the filters with separate objects, applying different comparators to each one. Note that the comparator argument defaults to ‘false’  

Modify list.html as follows:

public/views/players/list.html
Stop scrolling to load code snippet
<div ng-controller="PlayersController" ng-init="find()">
  <input type="text" ng-model="search.name" placeholder="Player name">
  <positions></positions>
  <nflteams></nflteams>
  <searchlimit></searchlimit>
  <table>
    <tr ng-repeat="player in players | filter:search | filter:strictsearch:true | limitTo:limitct">
      <td>{{ player.name }}<span ng-show="player.num"> - #{{ player.num }}</span></td>
      <td>{{ positions[player.pos].abbr }}</td>
      <td>{{ nflteams[player.team].abbr }}</td>
    </tr>
  </table>
</div>

With this, you are now able to filter the players correctly by team, position, and name.

Upcoming Tutorial Sections

We will be expanding the tutorial with a lot more very soon:

- Draft Board using socket.io

- Setting fantasy lineups

- Waiver wire and trades

- And much, much more!