Comments 0

Document transcript

AngularJS Tutorial: Learn to build modern web apps | Thinksterhttp://www.thinkster.io/pick/521e8672e2a3b28f98000314/angularjs-tutorial-learn-to-build-modern-web-apps29-08-2013 18:52:10(/AngularTutorial)AngularJS Tutorial(/AngularTutorial)Learn to build modern webapps using #AngularJS.Created & maintained byMatt Frisbie.Learn everythingabout AngularJS.There's more coming- enter your emailand stay in the loop!you@hello.comSubmit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 codefor only$25!(http://gum.co/SUry)IntroductionGetting Familiar With theMEAN StackWriting TheAuthentication ServiceBuilding Out TheApplicationGetting Into the CRUDAdding Fantasy TeamsBuilding The PlayerPickerUpcoming TutorialSectionsAngularJS Tutorial:Learn to build modernweb appsIntroductionThis tutorial will guide you through the process ofcreating a full-stack application. It features step-by-step instructions on how to build a fantasy footballapplication, code snippets of the full application, andexplanations on design decisions.Our intention is to provide to the AngularJScommunity with instructions on how to useAngularJS correctly and effectively, but also in itsmost modern form. The application you are buildingwill go beyond basic use of AngularJS, and we willattempt to explore as much of the framework aspossible. We also feel strongly about maintainingmodernity in a tutorial, so we will keep it congruentwith AngularJS as the framework and communitymatures. This tutorial is built on top of AngularJSv1.2.0rc1.The tutorial is a living thing, a work in progress. Weare constantly extending the tutorial and makingchanges and corrections. If you find errata, thinksomething should be changed, or would like tosuggest an improvement or new section, we wouldlove to hear from you.Source code and PDF eBookThis tutorial is provided to you free of charge on thesite. We built this in the interest of advancing theAngularJS framework and community.thinksterthinksterPage 1We encourage you to purchase the source code andPDF of this tutorial to help fund our continuedefforts in building more material and features forThinkster. Your money literally goes towards payingour rent and food for the next few months while wemake 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 freeaccess to new versions of the up-to-date source codeand PDF as they are released.You have 100% ownership over the PDF andsource codeand can use them however you like.PrerequisitesThis tutorial assumes you already have a workingknowledge of AngularJS. Throughout, there will bereferences to parts of the"A Better Way to LearnAngularJS"(http://www.thinkster.io/pick/51d287681e4b9c9098000013/a-better-way-to-learn-angularjs)curriculum if youneed to clarify or refresh on a certain subject. Werecommend going through the entire curriculumbefore beginning this tutorial.A knowledge of MongoDB, NodeJS, and ExpressJSwill be of great assistance, but is not required.The StackThis application will be built on top of the MEANstack - MongoDB, ExpressJS, AngularJS, and NodeJS.The wonderful people athttp://www.mean.io/(http://www.mean.io/)have written a boilerplateapplication stack. We took the stack and stripped itdown to a more basic form, which is the point fromwhich you will start.Page 2AngularJS can just as easily be used with a Ruby onRails, Django, CakePHP, or any other server-sideframework. Similarly, you could substituteMongoDB for any other database to use withAngularJS. We chose the MEAN stack for the tutorialbecause it offers an extremely clean implementationof the application, but by no means is it the onlyimplementation.We included a backend component to the tutorialbecause it is impossible to create a truly awesomeapplication with only AngularJS and other JSlibraries - you need a server and databasecomponent. Thus, the tutorial will go through theconstruction of an entire application, both frontendand backend.Part of this tutorial will be spent demystifying whythe MEAN stack works the way it does. This isessential to a complete understanding of how tobuild an application with AngularJS.Why fantasy football for a tutorial?Besides the fact that fantasy football is totallyawesome and a ton of fun?We are bored to tears by building Twitter clonesover and over again in tutorials, and wanted to mixit up a bit. Building a fantasy football application ischallenging, and can be broken down into pieces, soit 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, andpitting your fantasy team against other teams.Groups of users, usually 8-12, will create their ownteams as part of a “league”, and will compete againstother individuals within that league.Page 3Individuals in a fantasy football league will choosereal players in the American National FootballLeague (NFL) for their teams, and these teams willface each other weekly in a one-on-one matchupduring actual NFL games. Players in the real gamesperform actions that score points in fantasy football,and whichever fantasy team scores more points inthat matchup wins. Teams with the best win/lossrecords enter fantasy playoffs, and an eventualchampion is selected.Makeup of a fantasy teamThere are 32 NFL teams, and your fantasy team willconsist of players from some of these teams.There are lots of different positions on a NFL team,but for fantasy football purposes, we simplify thedifferent positions greatly: all you have to worryabout is Quarterback (QB), Runningback (RB), WideReceiver (WR), Tight End (TE), Kicker (K), andDefense/Special Teams (D/ST). Defense/SpecialTeams is a special position on your roster, itrepresents the entire Defense and Special Teamsunits, which are made up of many players, for oneof the 32 teams. All other players you select will beindividual players.How players score points isn’t important right now,you can worry about that later. For those of youfamiliar with fantasy football, this application willoperate under standard scoring rules.Fantasy teams will have 16 members, but only 9 ofthem will actually count towards scoring in theweekly matchup against another fantasy team. Theother 7 will remain on the team’s “bench”, and anypoints they score will not count towards your team’stotal that week. Your team, and every team in theleague, will select players in a “fantasy draft” - moreabout that later.Page 4Your fantasy team’s 9-player active roster will have1 Quarterback (QB), 2 Runningbacks (RB), 2 WideReceivers (WR), 1 Tight End (TE), 1 Flex (which caneither be a RB, WR, or TE), 1 Kicker (K), and 1Defense/Special Teams (D/ST).An example 16-man roster might look like thefollowing:Active RosterQB: 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 SeahawksBenchTE: Rob Gronkowski (NE)RB: Marshawn Lynch (SEA)WR: Brandon Marshall (CHI)QB: Matt Ryan (ATL)RB: C.J. Spiller (BUF)Page 5K: Blair Walsh (MIN)D/ST: Chicago Bears (CHI)RecapHopefully some of that stuck, but if a lot of it wentover your head, don’t worry. As you build theapplication, you will begin to understand muchmore clearly how fantasy football works. Let’s getstarted!Getting Familiar Withthe MEAN StackWe’ve provided the starting point for the applicationon github:https://github.com/msfrisbie/mean-stripdown(https://github.com/msfrisbie/mean-stripdown).

Clone the application withgit clonegit@github.com:msfrisbie/mean-stripdown.gitThe application uses Node.js and MongoDB, makesure you have those installed.

With all this set up, you should be able to run theapplication! From the mean-stripdown directory,runningnode servershould start an ExpressJS nodeserver on port 3000. Navigate to localhost:3000, andyou should see the skeleton app working!What Am I Actually Dealing WithHere?Page 6Before you actually get your hands dirty, familiarizeyourself with what is provided in the skeletonapplication.App DirectoryThis contains all the files involved in server-sideprogram flow. Your directory structure will look likethis: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.jadeThe server is using the Jade templating engine torender views. You won’t need to worry about thistoo much right now, as none of your AngularJStemplates will be done in Jade. You see twocontrollers, one user model, and a bunch of viewsprovided for you.The default.jade, foot.jade, and head.jade views arethe ‘wrapper’ templates for the application, whichsurround the AngularJS templates. Looking throughthese should be pretty self-explanatory.Authentication and the ExecutionEnvironmentPage 7You might be asking yourself, “Matt, isn’t this anAngularJS tutorial? Why is the server handling allthese views?”The answer lies in application security. AngularJS,by itself, cannot be used to securely authenticate auser. AngularJS exists entirely in the browser’sJavaScript execution, and therefore it must beassumed that the user has complete control over theexecution environment. The user is able to modifyany part of the code you provide to them, and soauthentication cannot be solely handled by thebrowser, there must be a remote server aspect to it.The stack provided sets this up nicely. The serveruses PassportJS, cookies, and a User model toauthenticate users in a standard fashion. Theauth.jade, signin.jade, and signup.jade views, alongwith the users.js controller, are all part of this. Theserver provides the browser with a cookie toidentify the user session, and every transaction withthe server after that will use that cookie to identifythe user, not by anything Angular will provide.Now you might be asking, “OK Matt, that’s all welland good that the server’s authentication is squaredaway, but now how does AngularJS know the user isauthenticated?”Good question! You’ll start by examining index.jade:app/views/index.jadeWhen rendering this template, if the user hasauthenticated, the user object can be interpolatedinto the view. When passed to the client, the userobject is attached to the window object, and is nowavailable to your JavaScript aswindow.user. Whenthe user has not authenticated, the window.userobject will be null, and everything still works.Getting Into AngularJSextends layouts/defaultblock contentsection(ng-view)script(type=”text/javascript”).window.user = !{user};Page 8You won’t stop with the window.user object, though.Even though this object is available globally, usingthis throughout the application to handleauthentication introduces a bit of code smell. Sinceyou won’t need to use the user object everywhere,but you’d like to use it in a *lot* of places, this seemslike the perfect opportunity to write your firstservice.Public DirectoryThis contains CSS, images, libraries, and all yourAngularJS files and views. Your directory structurewill look like this:public├──css│

├──...Check these boxes to keeptrack of your progressPage 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.htmlThe lib directory contains angular.js proper, andalso modules that you will list as dependencies foryour application.All the AngularJS files you will modify live in the js/directory. Views obviously live in the viewsdirectory.Page 10app.jsattaches an angular instance to the windowas a window.app object, and defines moduledependencies.config.jssets up routing and other configurationoptionsThecontrollers/directory contains all yourapplication controllers, separated into their ownfiles. You will continue this separate file conventionas the application grows.Theservices/directory contains all your applicationservicesThedirectives.jsandfilters.jswill contain yourdirectives and filters, respectively. These willeventually be broken out into multiple files.init.jsserves to provide some setup configuration.In it, you will notice that the application is manuallybootstrapped, as opposed to declaring the app in aview with ng-appWriting TheAuthenticationServiceBefore proceeding, make sure you are familiar withhow services work in AngularJS. If you need arefresher on Angular services,go through this video(http://www.thinkster.io/pick/521d9e3fc4645dfa9900000c)- it will give a good refresher on how generallyservices 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 11Additionally, read throughPart 13: $http and ServerInteraction(http://www.thinkster.io/pick/51d287681e4b9c9098000013/a-better-way-to-learn-angularjs#item-51e7b9578aa51e42b3000024).Global ServiceRecall that you are trying to convey authenticationinformation to AngularJS cleanly. You will start withthe global.js file, which is basically empty:public/js/services/global.jsThis Global service will return an object which youcan use to identify the user, as well as ascertain if auser is logged in or not. Since login/logout is asynchronous server action, this service will berefreshed each time the authentication statechanges. Therefore, you are able to directly grab thewindow.userobject, and use that to conveyauthentication to the AngularJS application.

Change global.js to match the following:public/js/services/global.jsGreat! It returns the current_user object in theservice, so injecting the Global service in yourapplication will give us access to that user object.This would work fine, but you’d like to encapsulatethecurrent_userobject so theapplication doesn't directly access it.

Let’s instead refactor it to return an object withmethods that indirectly interact with thecurrent_userobject: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 12public/js/services/global.jsNow that you have created a service, you need toadd it as a dependency to the application.Setting Up ApplicationDependenciesYou 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 theapplication, and sign in. When you’re signed in, useyour browser’s console to check the value ofwindow.user. You should see the user object.Navigating to /signout (not #!/signout, an importantdifference) and checking window.user should showit to be null.Terrific! The first part of authentication is takencare of.Dependency Injection in aControllerwindow.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 dependencieswindow.angular.module(‘ngff.controllers’, ['ngff.controllers.header','ngff.controllers.index']);window.angular.module(‘ngff.services’, []);window.angular.module('ngff.services', ['ngff.services.global']);Page 13You should be comfortable with at least basicconcepts in AngularJS controllers and dependencyinjection. 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’sactually make it do something. header.js containsthe controller for the header bar. It will also beempty:public/js/controllers/header.jsYou can’t use the Global service in the view withoutattaching it to the scope.

Inject both of these into the controller:public/js/controllers/header.jsRecall that the order in which these are listed doesnot matter, as Angular’s dependency injection willevaluate them individually, regardless or parameterordinality.

Use the injected service object, and attach it to thescope:window.angular.module('ngff.controllers.header', []).controller('HeaderController', [function() {}]);window.angular.module('ngff.controllers.header', []).controller('HeaderController', ['$scope', 'Global',function ($scope, Global) {}]);Page 14public/js/controllers/header.jsUsing The Controller in the ViewYou’ll notice that, even when you’re signed in, thatyou can still see the Sign In and Sign Up buttons inthe header bar. This doesn't quite make sense, andyou’d like to instead show the user’s name, and away to sign out. Take a look atpublic/views/header.html:public/views/header.htmlYou see that the two visibility directives, ng-showand ng-hide, are set to empty conditionals. Also, yousee that the user dropdown is set to show just ‘User’.

Make use of the service you just built, and fill in thedirectives:public/views/header.htmlwindow.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 15global refers to the $scope.global attribute objectthat you set in the controller. The isSignedIn()method returns a boolean (a double-bang of thecurrent_user object), and so these two <ul>s willmutually 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 switchback to Sign In/Sign Up. Awesome!

Now replace ‘User’ with the user’s actual name. ThecurrentUser() method returns the current_userobject, so use the name and interpolate it into theview:public/views/header.html

Refreshing the page, the User menu button shouldhave your name in it now.Building Out TheApplicationNow 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 aboutas static as data gets, so instead of serving this fromthe server every time, it makes more sense to justprovide them in a service. Players and other entitiesthat 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 namingconvention for your application. Since ‘team’ couldrefer to either a NFL team or a fantasy team, youwill refer to one as NFLTeam, and one asFantasyTeam 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, sohave it return a teams attribute, an array of all theNFL teams in alphabetical order by city:public/js/services/nfl.jsWriting the NFL Controller

Now add routes for each of these views. The viewtemplates will be public/views/nfl/list.html andpublic/views/nfl/show.html, and since they’re bothusing the same controller, you can just set that inconfig.js:public/js/config.jsNotice that :nflTeamId exists for the individual teamroute, and the string value of this in the URL will beavailable as $routeParams[‘nflTeamId’] in thecontroller 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 19Here, you are introduced to the ng-repeat directivefor the first time. nflteams is the collection, nflteamis the iterator, and each instance in nflteams willrender 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.

With all of this, you should have your first workingpages in your application! Run the server, andnavigate to localhost:3000/#!/nflteams to try it out.Getting Into theCRUDWhile all this bandying about on the frontend hasbeen dandy, it’s high time you got to working out theentire stack. This section is going to contain aconsiderable amount of Node.js, but in order tobuild an interesting and persistent web application,use of a server and database is necessary. It is worthmentioning that, while NodeJS bundled withExpressJS and MongoDB is an excellent option, youcould just as easily substitute any other web serverand database to work with AngularJS.With that, let’s get started.League CRUDLeagues are as good as any piece of the applicationto 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 20You will start off as simply as possible. A league willhave a name, simply a string, and a commissioner,which will be a User.Working In the BackendRecall 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.jsThe ORM used for MongoDB in this framework isMongooseJS. You’ll tackle some more advanced stuffwith it later on, but for now, the concepts shownshould be relatively familiar to you if you have usedMongoDB or a similar NoSQL database before.

Next, define the schema for a league:app/models/league.jsThis creates the simple schema described above.Using the Schema.ObjectID type will allow us tomore easily fill out model references whenquerying.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, compile the League model with theLeagueSchema you just constructed:app/models/league.jsThus, league.js in full:app/models/league.jsLeague ControllerThe model you just made is worthless without itsaccompanying controller.

create()app/controllers/leagues.jsAngularJS takes the form data for the new leagueand sends it as part of the request body. For now, it’sonly the name of the league, but you can pass thereq.body object to the League constructor and it willtake care of the rest. Also, ExpressJS identifies theuser that generated the request with the requestcookie, and packages the user object into the requestas req.user, so you can use that here, too, for thecommissioner field. A call to save() on the leagueobject writes to the database, and you return theleague object as the request response withres.jsonp().exports.create = function (req, res) {var league = new League(req.body)league.commissioner = req.userleague.save()res.jsonp(league)}Page 23

show()app/controllers/leagues.jsTry to contain your excitement, I know it’s difficult.This is invoked when the league object is alreadypart of the request object, and simply responds withjsonp().

league()app/controllers/leagues.jsThis one is a bit longer, but it’s mostly errorhandling, something you’ll get to a bit later.When this is invoked, it uses the statics method youwrote earlier to retrieve a league document from thedatabase by id. On success, it adds the retrieveddocument 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 = leaguenext()})}Page 24app/controllers/leagues.jsMost of this should look pretty familiar. It isperforming a more or less identical operation to theload() statics method, but to all the leaguedocuments returned. On success, it returns an arrayof all the populated league objects.

update()app/controllers/leagues.jsUpdate should look pretty straightforward at thispoint. The only thing to note here is the utilization ofthe Underscore.JS extend() method, which is simplymerging the request’s old league object, and thereq.body new league object, giving priority to thereq.body object for overlapping fields. It then savesand returns the new object.

destroy()app/controllers/leagues.jsNot much new material to explain here. Theremove() method operates pretty much as youwould 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.leagueleague = _.extend(league, req.body)league.save(function(err) {res.jsonp(league)})}exports.destroy = function(req, res){var league = req.leagueleague.remove(function(err){if (err) {res.render('error', {status: 500});} else {res.jsonp(1);}})}Page 25At this point, a lot of the way this is structuredprobably still doesn’t make sense to you. Not toworry, much will be revealed now that you’vearrived at server-side routing.League Routing

Next, move into the final remaining project folder,the config/ directory. For now, the only file you careabout in here is routes.js. Add the following routes:config/routes.jsAfter requiring the leagues controller, the CRUDroutes are fairly plain if you are familiar withrouting patterns. It’s worth examining theconfig/middlewares/authorization.js file tounderstand what is going on there with middleware,but you won’t worry about it for now, as it too isstraightforward. All you need to know to understandthese routes is that the methods following the routestring are 'chained' and invoked serially.The app.param() method looks for a :leagueIdparameter in the URL string params only, and if itsees one, it invokes the leagues.league() method. Asshown before, the leagues.league method will findthe league document by the leagueId and add it as aleague attribute to the request object. Recall thatmany of the league controller routes are able toaccess the league object directly from the requestobject with req.league - this is how that is possible.Frontend ComplementNow that the backend is squared away, let’s buildout 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 topublic/js/config.js. They should be inserted followingthe nflteams routes, and should come before theotherwise() clause:public/js/config.jsFrontend ServiceThe AngularJS $resource service is an incrediblypowerful one which allows you to concisely interactwith RESTful APIs. You’ve surely realized by nowthat 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 thefollowing:public/js/services/leagues.jsThis service returns an instance of the $resourceservice. $resource is quite versatile, and you canread 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 27Here, you set the parameterized resource url,‘leagues/:leagueId’, the paramDefaults as the object’s_id attribute, and override the $update method touse PUT instead of POST, to match what youconstructed on the backend. The @ in theparamDefaults will attempt to extract a leagueIdfrom the resource object provided to it.Frontend ControllerThis service is short but versatile, and now you canapply it in the controller, which will exist in a newfile public/js/controllers/leagues.js. This controllerwill have five methods:create()creates a new league object on the backend,then navigates to the new league pagefind()finds leagues in the database based on asingle query parameter, which can be blankfindOne()retrieves a league by id from the URLparametersupdate()updates a league by iddestroy()destroys the league object by id

Start off the new leagues.js controller with theproper setup, including all necessary dependencyinjection:public/js/controllers/leagues.jsAdd 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 28public/js/controllers/leagues.jsA new league service object is created with the formdata, and the $save method is invoked. The $savecallback will use the $location service to navigate tothe individual league page for the new league.Finally, you’ll clear the existing form data.

find()public/js/controllers/leagues.jsRelatively straightforward - the query parameter ispassed to the query method on the Leagues serviceobject, and the returned array is assigned to thescope in the callback.

findOne()public/js/controllers/leagues.jsThis one is very similar to find(), instead using theget() method in the service object using the leagueIdidentifier. The callback operates in the same way.

destroy()public/js/controllers/leagues.jsHere, you remove the league object on the backendwith $remove, and then remove it from the $scopearray in Angular.This completes the controller!

Remember to include it and the League service youcreated 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 ViewsNext, create the four views referenced above in anew directory, public/views/leagues/:

After all this, you should be able to create, view, edit,and remove fantasy leagues.Congratulations! You’ve finished the first AngularCRUD functionality.Adding FantasyTeamsNext, you’re going to add the CRUD functionality forfantasy teamsFantasy teams have a name (string), an owner(User), are in one league (League), and has manyplayers (array of Players).Much of the fantasy team infrastructure will be verysimilar to Leagues.Model

Modify routes.js as follows:config/routes.jsThis is functionally identical to the leagues routes.AngularJS Routing

Modify config.js as follows:app/config.jsThis is functionally identical to the leagues routes.Once again, this should come following the leaguesroutes, but preceding the otherwise() clause.Fantasy Teams Service

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"><selectng-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 37Here, you are seeing the ng-options directive for thefirst time. In the ng-init directive, you are invokingthe populateLeagues() method to attach an array ofall 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 commentedout to demonstrate an interesting aspect of howAngularJS handles the view interacting with themodel. Without an default <option> with an emptyvalue provided, you will notice that Angularprovides a blank one to you anyway on a new pageload. However, when an option is selected, the blankone will disappear. This is because, when buildingout the ng-options directive, the ng-model isinitialized as empty. None of the values of the<option>s in the league array match that value, so itadds a blank one. Once you select an option, themodel takes on that value, and the empty <option> isno longer necessary.

Create view.html:public/views/fantasyteams/view.htmlThat's it! With all of this, you should now havecomplete league and fantasy teamCRUD functionality.

Test your application out and make sure you areable to perform CRUD operations on leagues andteams.NavigationAt this point, you’re probably getting pretty tired ofnavigating by typing in the url, so let’s add somelinks to the navbar.

Next, add in some logic to the view so that thesebuttons only show up when the user is logged in:public/views/header.htmlBuilding The PlayerPickerBefore beginning this section, you should be familiarwith 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,sofamiliarize yourself with Part 4: Directives(http://www.thinkster.io/pick/51d287681e4b9c9098000013/a-better-way-to-learn-angularjs#item-51e7a31411a5e483ff00000b).Adding Positions to the NFLService

Modify directives.js as follows:public/js/directives.jsThis directive will allow us to use <positions></positions> in a view to insert a dropdown of NFLpositions.Directive ComponentsObviously, you’re going to need to build aPlayersController, as well as write a positionselectview.

Create a players.js controller file, and fill it with thefollowing:public/js.controllers/players.jsSimple enough, you’re just handing off the datafrom 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 41public/views/players/positionselect.htmlNormally, the ng-options directive would be usedhere, but you need the $index iterator, so ng-repeatis substituted.You are also attaching the value to the search.posmodel in the scope, which will be used to filterplayers shortly.Wiring It All UpWith this, you can now begin to build the playerpicker view.

Add the following single route to config.js:public/js/config.jsIn the same way as before, it must come after thefantasy team routes, and before the otherwise()clause.

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

Navigating to the players route should show adropdown with the NFL positions filled in.More Selectors

Finally, add the new directive elements to thelist.html view:public/views/players/list.htmlPlayer BackendFor now, the player model is a very simple one,consisting of four strings: pos, num, name, and team.

Make sure and include the new service andcontroller files in foot.jade, as well as add them tothe app.js dependencies as you have done before.As you’ve now surely realized, there are no playersin the database, and there’s no way to populate itusing the API you constructed. Fortunately,Thinkster has you covered. In the root directory ofthe project, you’ll find a scripts/ directory. In it, thereis a Node script called mongoimport.js. Run it withnode mongoimport, this will import all NFL playersinto your development database.

With this, you should see all the players printed outwhen you navigate to the players page.FiltersYou surely don’t want to see all the hundreds ofplayers at once, so let’s build a filtering mechanisminto the player picker.

Replace the div containing the raw player data frombefore with something a little more refined, shownbelow. Also, add in a text field to search for playernames. Your list.html should now look like this:public/views/players/list.htmlThe search object will be taken from the positionsand nflteams dropdowns, as well as the text input.The object’s attributes will be matched up againstthose in the players objects, and matching ones willbe filtered out. You are now also using a limit, takenfrom 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 beable to filter players by name, position, team, andlimit the number of results.Give yourself a pat on the back, this is pretty cool.Corner CasesIf you play around with the player picker enough,you might have noticed that filtering by teamdoesn’t QUITE work correctly. Selecting, forexample, to filter for players on Arizona returnsplayers on Arizona, Detroit, New York, andTennessee. This is because the indexes of theseteams are 0, 10, 20, and 30, and you are searchingfor teams against the index ‘0’. Angular’s returns allresults with a ‘0’ in their team index, as this istechnically a match. You must create a custom filterthat will return values with exact matches, notmatching substrings.AngularJS v1.1.5 introduced the comparator optionfor filters, which allows the user to use eitherpresets, or a custom comparison function, todetermine what the filter will return. For yourpurposes, passing in ‘true’ will require identicalvalues for a match to be recognized. The comparatordefaults to the substring match that you are usingnow.

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)RefactorEach comparator value that is applied to a filter isapplied to the entire search object. You don’t want touse exact string matches for the player names, onlyfor the position and NFL team dropdowns. Thus, youwill need to separate them out into two searchobjects, and filter separately.Return to your directive templates and attach themto a new ‘strictsearch’ model:Page 47

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

Create nflteamselect.html:public/views/players/nflteamselect.htmlNow you can chain the filters with separate objects,applying different comparators to each one. Notethat the comparator argument defaults to ‘false’