AngularJS Tutorial: Learn to build modern web apps | Thinkster

deliriousattackInternet and Web Development

Dec 4, 2013 (3 years and 11 months ago)

249 views

AngularJS Tutorial: Learn to build modern web apps | Thinkster
http://www.thinkster.io/pick/521e8672e2a3b28f98000314/angularjs-tutorial-
learn-to-build-modern-web-apps
29-08-2013 18:52:10
(/AngularTutorial)
AngularJS Tutorial
(/AngularTutorial)
Learn to build modern web
apps using #AngularJS.
Created & maintained by
Matt Frisbie.
Learn everything
about AngularJS.
There's more coming
- enter your email
and stay in the loop!
you@hello.com
Submit

Tweet this
(https://twitter.com/intent/tweet?
original_referer=http%3A%2F%2Fwww.thinkster.io%2F&text=AngularJS%20Tutorial%3A%20Learn%20to%20build%20modern%20web%20apps%20
%23AngularJS by
@AngularTutorial&tw_p=tweetbutton&url=http://www.thinkster.io%2Fpick%2F521e8672e2a3b28f98000314%2Fangularjs-
tutorial-learn-to-build-
modern-web-
apps%3Fref%3Dtwt)

Share on Facebook
(https://www.facebook.com/sharer/sharer.php?
u=http://www.thinkster.io%2Fpick%2F521e8672e2a3b28f98000314%2Fangularjs-
tutorial-learn-to-build-
modern-web-
apps%3Fref%3Dfb)
Support us!
Buy the eBook &
source source code
for only
$25!
(http://gum.co/SUry)
Introduction
Getting Familiar With the
MEAN Stack
Writing The
Authentication Service
Building Out The
Application
Getting Into the CRUD
Adding Fantasy Teams
Building The Player
Picker
Upcoming Tutorial
Sections
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.
Our intention is to provide to 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.
thinkster
thinkster
Page 1
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!
Download the source code and PDF eBook here,
securely through Gumroad.
(http://gum.co/SUry)
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"
(http://www.thinkster.io/pick/51d287681e4b9c9098000013/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/
(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.
Page 2
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.
Page 3
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.
Page 4
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)
Page 5
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
(https://github.com/msfrisbie/mean-
stripdown)
.

Clone the application with
git clone
git@github.com
:msfrisbie/mean-stripdown.git
The application uses Node.js and MongoDB, make
sure you have those installed.

Install Node.js:
http://howtonode.org/how-to-install-
nodejs
(http://howtonode.org/how-to-install-nodejs)

Install MongoDB:
http://docs.mongodb.org/manual/installation/
(http://docs.mongodb.org/manual/installation/)

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?




Page 6
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:
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
Page 7
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
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
extends layouts/default
block content
section(ng-view)
script(type=”text/javascript”).
window.user = !{user};
Page 8
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:
public
├──
css


├──
...
Check these boxes to keep
track of your progress
Page 9
├──
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.
Page 10
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,
go through this video
(http://www.thinkster.io/pick/521d9e3fc4645dfa9900000c)
- it will give a good refresher on how generally
services can be used. For more in-depth coverage,
read through Part 11: Under the Hood
(http://www.thinkster.io/pick/51d287681e4b9c9098000013/a-
better-way-to-learn-angularjs#item-
51e7b3b38aa51e42b3000001)
.
Page 11
Additionally, read through
Part 13: $http and Server
Interaction
(http://www.thinkster.io/pick/51d287681e4b9c9098000013/a-
better-way-to-learn-angularjs#item-
51e7b9578aa51e42b3000024)
.
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
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
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:
window.angular.module('ngff.services.global', [])
.factory('Global', function(){
});

window.angular.module('ngff.services.global', [])
.factory('Global', function(){
var current_user = window.user;
return current_user;
});

Page 12
public/js/services/global.js
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

Add the new module dependency
‘ngff.services.global’ to the ‘ngff.services’ module:
public/js/app.js

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
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;
}
};
});
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’, []);

window.angular.module('ngff.services', ['ngff.services.global']);

Page 13
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
(http://www.thinkster.io/pick/51d287681e4b9c9098000013/a-
better-way-to-learn-angularjs#item-
51e7995d6646e9640500001d)
.
Also, the tutorial will dive right into Angular scope.
For a review,
read through Part 5: Scope
(http://www.thinkster.io/pick/51d287681e4b9c9098000013/a-
better-way-to-learn-angularjs#item-
51e7a63b11a5e483ff00001e)
.
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
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
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:
window.angular.module('ngff.controllers.header', [])
.controller('HeaderController', [
function() {
}]);

window.angular.module('ngff.controllers.header', [])
.controller('HeaderController', ['$scope', 'Global',
function ($scope, Global) {
}]);

Page 14
public/js/controllers/header.js
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
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
window.angular.module('ngff.controllers.header', [])
.controller('HeaderController', ['$scope', 'Global',
function ($scope, Global) {
$scope.global = Global;
}]);
<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>

<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()">
Page 15
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

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

<a href="#" class="dropdown-toggle" data-toggle="dropdown">
{{ global.currentUser().name }} <b class="caret"></b>
</a>

Page 16

Create a new service file, nfl.js:
public/js/services/nfl.js

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
Writing the NFL Controller

Next, create a new controller, nfl.js:
public/js/controllers/nfl.js

window.angular.module('ngFantasyFootball.services', [])
.factory('NFLTeams', function() {
var NFL = {};
return NFL;
});

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

window.angular.module('ngff.controllers.nfl', [])
.controller('NFLController', ['$scope','$routeParams','Global','NFL',
function($scope, $routeParams, Global, NFL) {
$scope.global = Global;
}]);
Page 17
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

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

Since you are also declaring this controller as a
module, you must add it to the controllers module
dependencies in public/js/app.js:
public/js/app.js
Routing

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

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

window.angular.module('ngff.controllers', ['ngff.controllers.header', 'ngff.controllers.index', 'ngff.controllers.nfl']);
Page 18
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
(http://www.thinkster.io/pick/51d287681e4b9c9098000013/a-
better-way-to-learn-angularjs#item-
51e7a99c11a5e483ff000036)
,
Part 8: Templates
(http://www.thinkster.io/pick/51d287681e4b9c9098000013/a-
better-way-to-learn-angularjs#item-
51e7ade911a5e483ff00005b)
, and
Part 9: Routing
(http://www.thinkster.io/pick/51d287681e4b9c9098000013/a-
better-way-to-learn-angularjs#item-
51e7aee511a5e483ff000066)

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

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

<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>
Page 19
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 nested element, 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
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.

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

Page 20
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
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
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
(http://mongoosejs.com/docs/2.7.x/docs/methods-
statics.html)

Following this, define a statics method on the model:

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

Page 21
app/models/league.js
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
Thus, league.js in full:
app/models/league.js
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
LeagueSchema.statics = {
load: function (id, cb) {
this.findOne({ _id : id }).populate('commissioner').exec(cb);
}
};

mongoose.model('League', LeagueSchema);
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);

var mongoose = require('mongoose')
, async = require('async')
, League = mongoose.model('League')
, _ = require('underscore')
Page 22
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
show()
returns the league object in the request
object
league()
takes a league id, finds the league, and
assigns it to 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
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().

exports.create = function (req, res) {
var league = new League(req.body)
league.commissioner = req.user
league.save()
res.jsonp(league)
}
Page 23

show()
app/controllers/leagues.js
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
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().

Read up on the next() method:
http://stackoverflow.com/questions/8710669/having-
a-hard-time-trying-to-understand-next-next-in-
express-js
(http://stackoverflow.com/questions/8710669/having-
a-hard-time-trying-to-understand-next-next-in-
express-js)

all()

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

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()
})
}


Page 24
app/controllers/leagues.js
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
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
Not much new material to explain here. The
remove() method operates pretty much as you
would expect.
exports.all = function(req, res){
League.find().populate('commissioner').exec(function(err, leagues) {
if (err) {
res.render('error', {status: 500});
} else {
res.jsonp(leagues);
}
});
}

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

exports.destroy = function(req, res){
var league = req.league
league.remove(function(err){
if (err) {
res.render('error', {status: 500});
} else {
res.jsonp(1);
}
})
}
Page 25
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
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.

...
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)
Page 26

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
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
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
(http://docs.angularjs.org/api/ngResource.$resource)
.


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

window.angular.module('ngff.services.leagues', [])
.factory('Leagues', ['$resource',
function($resource){
return $resource(
'leagues/:leagueId',
{
leagueId:'@_id'
},
{
update: {method: 'PUT'}
}
)
}]);
Page 27
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
Add the following methods:

create()

window.angular.module('ngff.controllers.leagues', [])
.controller('LeaguesController', ['$scope','$routeParams','$location','Global','Leagues',
function ($scope, $routeParams, $location, Global, Leagues) {
$scope.global = Global;
}]);

Page 28
public/js/controllers/leagues.js
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
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
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
$scope.create = function () {
var league = new Leagues({
name: this.league.name
});
league.$save(function (response) {
$location.path("leagues/" + response._id);
});
this.league.name = "";
};

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

$scope.findOne = function () {
Leagues.get({ leagueId: $routeParams.leagueId }, function (league) {
$scope.league = league;
});
};

$scope.update = function () {
var league = $scope.league;
league.$update(function () {
$location.path('leagues/' + league._id);
});
};
Page 29
This time, you use the $update method for the
league object, and redirect with $location in the
callback.

destroy()
public/js/controllers/leagues.js
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

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



<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="remove(league)" >Remove</a>)
</li>
</ul>
<br>
<a href="#!/leagues/create">Create a new league</a>
</div>
Page 30
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
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
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

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

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

Page 31
public/views/leagues/view.html
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
<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>


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);
Page 32
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

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);
}
})
}
Page 33
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
This is functionally identical to the leagues routes.
AngularJS Routing

Modify config.js as follows:
app/config.js
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:

...
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)

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

Page 34
public/js/services/fantasyteams.js
This is functionally identical to the leagues service.
Fantasy Teams Controller

Create fantasyteams.js as follows:
window.angular.module('ngff.services.fantasyTeams', [])
.factory('FantasyTeams', ['$resource',
function($resource){
return $resource(
'fantasyteams/:fantasyTeamId',
{
fantasyTeamId:'@_id'
},
{
update: {method: 'PUT'}
}
)
}]);

Page 35
public/js/controllers/fantasyteams.js
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.
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.fantasy.league,
name: this.fantasy.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 = teams;
});
};
$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)
}
}
};
}]);
Page 36
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

Create create.html:
public/views/fantasyteams/create.html

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

<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="fantasy.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>
Page 37
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


<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>
Page 38
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
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

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


$scope.navbarEntries = [
{
"title": "Leagues",
"link": "leagues"
},
{
"title": "Fantasy Teams",
"link": "fantasyteams"
},
{
"title": "NFL Teams",
"link": "nflteams"
},
{
"title": "Players",
"link": "players"
}
];
Page 39

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
Building The Player
Picker
Before beginning this section, you should be familiar
with Angular filters. If not,
read through Part 3:
Filters
(http://www.thinkster.io/pick/51d287681e4b9c9098000013/a-
better-way-to-learn-angularjs#item-
51e7a11b7f915f0eaa000001)
.
Also, this section will build some custom directives,
so
familiarize yourself with Part 4: Directives
(http://www.thinkster.io/pick/51d287681e4b9c9098000013/a-
better-way-to-learn-angularjs#item-
51e7a31411a5e483ff00000b)
.
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

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

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"}
];
Page 40
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
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
Simple enough, you’re just handing off the data
from the NFL data service.

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

window.angular.module('ngff.directives', [])
.directive('positions', function() {
return {
restrict: "E",
templateUrl: "views/players/positionselect.html"
};
})

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

Page 41
public/views/players/positionselect.html
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
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

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:
<select ng-model="search.pos">
<option value="">All positions</option>
<option ng-repeat="position in positions" value="{{ $index }}">{{ position.abbr }}</option>
</select>

.when('/players',
{
templateUrl: 'views/players/list.html'
})

<div ng-controller=”PlayersController”>
<positions></positions>
</div>


Page 42
public/js/directives.js
And the respective views:

Create nflteamselect.html:
public/views/players/nflteamselect.html
Create searchlimitselect.html:
public/views/players/searchlimitselect.html

Finally, add the new directive elements to the
list.html view:
public/views/players/list.html
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:
.directive('nflteams', function() {
return {
restrict: "E",
templateUrl: "views/players/nflteamselect.html"
};
})
.directive('searchlimit', function() {
return {
restrict: "E",
templateUrl: "views/players/searchlimitselect.html"
};
})

<select ng-model="search.team">
<option value="">All NFL teams</option>
<option ng-repeat="nflteam in nflteams" value="{{ $index }}">{{ nflteam.team }}</option>
</select>
<select ng-model="limitct">
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
</select>

<div ng-controller=”PlayersController”>
<positions></positions>
<nflteams></nflteams>
<searchlimit></searchlimit>
</div>

Page 43
app/models/player.js
Nothing here you haven’t seen before.

Next, create the server player controller, players.js:
app/controllers/players.js
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:
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);

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 (!league) 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);
}
});
}

Page 44
config/routes.js
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

Next, extend the PlayersController in players.js:
public/js/controllers/players.js

In the players/list.html view, append a simple
repeater to spit out all players returned:
public/views/players/list.html
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)

window.angular.module('ngff.services.players', [])
.factory('Players', ['$resource',
function($resource){
return $resource(
'players/:playerId',
{
playerId:'@_id'
}
);
}]);

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

<div ng-controller="PlayersController" ng-init="find()">
<div ng-repeat="player in players">{{ player }}</div>
</div>
Page 45

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



<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>
Page 46

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.

Read more here:
http://code.angularjs.org/1.1.5/docs/api/ng.filter:filter
(http://code.angularjs.org/1.1.5/docs/api/ng.filter:filter)
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:


Page 47

Create positionselect.html:
public/views/players/positionselect.html

Create nflteamselect.html:
public/views/players/nflteamselect.html
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

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

<select ng-model="strictsearch.pos">
<option value="">All positions</option>
<option ng-repeat="position in positions" value="{{$index}}">{{position.abbr}}</option>
</select>

<select ng-model="strictsearch.team">
<option value="">All NFL teams</option>
<option ng-repeat="nflteam in nflteams" value="{{ $index }}">{{ nflteam.team }}</option>
</select>

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

Page 48
- And much, much more!

Tweet this
(https://twitter.com/intent/tweet?original_referer=http%3A%2F%2Fwww.thinkster.io%2F&text=AngularJS%20Tutorial%3A%20Learn%20to%20build%20modern%20web%20apps%20 %23AngularJS by @AngularTutorial&tw_p=tweetbutton&url=http://www.thinkster.io%2Fpick%2F521e8672e2a3b28f98000314%2Fangularjs-tutorial-learn-to-build-modern-web-apps%3Fref%3Dtwt)

Share on Facebook
(https://www.facebook.com/sharer/sharer.php?u=http://www.thinkster.io%2Fpick%2F521e8672e2a3b28f98000314%2Fangularjs-tutorial-learn-to-build-modern-web-apps%3Fref%3Dfb)
© 2013 Thinkster


Follow
@GoThinkster
(https://twitter.com/GoThinkster)


Built in sunny Palo Alto

Page 49