├── .gitignore
├── README.md
├── course.json
├── images
└── mean.jpg
└── mean-tutorial.mdown
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | bower_components
3 |
4 | Gemfile.lock
5 | vendor/bundle
6 | log
7 | tmp
8 | .bundle
9 | .DS_Store
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Learn to Build Modern Web Apps with MEAN
2 | =======
3 |
4 | **update**: This tutorial is no longer being maintained, updated, nor supported by me.
5 |
6 | This repository contains the markdown for the [MEAN Stack tutorial on Thinkster.io](https://thinkster.io/mean-stack-tutorial/).
7 |
--------------------------------------------------------------------------------
/course.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Learn to Build Modern Web Apps with MEAN",
3 | "description": "This tutorial will teach you to use the MEAN stack (MongoDB, Express, AngularJS, and Node) to build a reddit clone",
4 | "url": "mean-stack-tutorial",
5 | "version": "v1.1",
6 | "twitter": "IAmMattGreen",
7 | "coverImage": "images/mean.jpg",
8 | "git": "https://github.com/emgeee/mean-tutorial",
9 |
10 | "author": {
11 | "github": "emgeee"
12 | },
13 | "contributors": [
14 | {"github": "apai4"},
15 | {"github": "EricSimons"}
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/images/mean.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emgeee/mean-tutorial/db59b02915f68fddd0b5afb7e6ad3fee57eec911/images/mean.jpg
--------------------------------------------------------------------------------
/mean-tutorial.mdown:
--------------------------------------------------------------------------------
1 |
2 | [thinkster twitter]: http://twitter.com/AngularTutorial
3 | [thinkster email]: mailto:hello@thinkster.io
4 | [REST]: http://en.wikipedia.org/wiki/Representational_state_transfer
5 | [CRUD]: http://en.wikipedia.org/wiki/Create,_read,_update_and_delete
6 | [install-node]: https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager
7 | [install-mongo]: http://docs.mongodb.org/manual/installation/
8 | [ng-repeat]: https://docs.angularjs.org/api/ng/directive/ngRepeat
9 | [ng-click]: https://docs.angularjs.org/api/ng/directive/ngClick
10 | [ng-submit]: https://docs.angularjs.org/api/ng/directive/ngSubmit
11 | [ng-model]: https://docs.angularjs.org/api/ng/directive/ngModel
12 | [ng-hide]: https://docs.angularjs.org/api/ng/directive/ngHide
13 | [ng-show]: https://docs.angularjs.org/api/ng/directive/ngShow
14 | [Angular expression]: https://docs.angularjs.org/guide/expression
15 | [ng-route]: https://docs.angularjs.org/api/ngRoute
16 | [ng http]: https://docs.angularjs.org/api/ng/service/$http
17 | [ng copy]: https://docs.angularjs.org/api/ng/function/angular.copy
18 | [services]: https://docs.angularjs.org/guide/services
19 | [data bindings]: https://docs.angularjs.org/guide/databinding
20 | [built in filters]: https://docs.angularjs.org/api/ng/filter
21 | [object reference]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects
22 | [angular style guide]: https://github.com/mgechev/angularjs-style-guide
23 | [factory vs service]: http://tylermcginnis.com/angularjs-factory-vs-service-vs-provider/
24 | [ui otherwise]: https://github.com/angular-ui/ui-router/wiki/URL-Routing#otherwise-for-invalid-routes
25 | [ui view]: https://github.com/angular-ui/ui-router/wiki/Quick-Reference#ui-view
26 | [ui parameters]: https://github.com/angular-ui/ui-router/wiki/URL-Routing#url-parameters
27 | [ui stateParams]: https://github.com/angular-ui/ui-router/wiki/URL-Routing#stateparams-service
28 | [ui resolve]: https://github.com/angular-ui/ui-router/wiki#resolve
29 | [mongoose populate]: http://mongoosejs.com/docs/populate.html
30 | [mongoose objectid]: http://mongoosejs.com/docs/api.html#schema-objectid-js
31 | [mongoose query]: http://mongoosejs.com/docs/queries.html
32 | [mongo objectid]: http://api.mongodb.org/java/current/org/bson/types/ObjectId.html
33 | [express get]: http://expressjs.com/api#app.get
34 | [express json]: http://expressjs.com/api#res.json
35 | [express param]: http://expressjs.com/4x/api.html#app.param
36 | [express generator]: https://github.com/expressjs/generator
37 |
38 | [better way]: https://www.thinkster.io/angulartutorial/a-better-way-to-learn-angularjs/
39 | [scopes]: https://www.thinkster.io/angulartutorial/a-better-way-to-learn-angularjs/#part-5-scope
40 | [filters]: https://www.thinkster.io/angulartutorial/a-better-way-to-learn-angularjs/#part-3-filters
41 | [config]: https://www.thinkster.io/egghead/the-config-function
42 | [inline template]: https://www.thinkster.io/egghead/templateurl
43 | [promises]: https://www.thinkster.io/egghead/promises
44 | [modern web apps firebase]: https://www.thinkster.io/angulartutorial/
45 |
46 | # Introduction
47 |
48 | {intro-video: mean-intro}
49 |
50 | The goal of this tutorial is to guide you through the creation of a
51 | Reddit/Hacker News clone using the MEAN stack. By completing this tutorial, you
52 | will gain a basic understanding of the MEAN stack including building a REST
53 | interface with Express.js on top of Node.js and using that interface to perform
54 | CRUD operations on a database via an AngularJS frontend.
55 |
56 |
57 | ## Why MEAN Stack?
58 |
59 | The acronym "MEAN" stands for "[MongoDB](http://www.mongodb.org/)
60 | [Express.js](http://expressjs.com/) [AngularJS](https://angularjs.org/)
61 | [Node.js](http://nodejs.org/)" and represents a group of technologies which are
62 | known to synergize well together. The major benefit of the MEAN stack is that
63 | it's extremely quick to prototype with. Node.js allows you to use Javascript on
64 | the backend as well as the frontend which can save you from having to learn a
65 | separate language. In addition, the
66 | [NoSQL](http://www.mongodb.com/nosql-explained) nature of MongoDB allows you to
67 | quickly change and alter the data layer without having to worry about
68 | migrations, which is a very valuable attribute when you're trying to build a
69 | product without clear specifications. Finally, these technologies have a lot
70 | of community support behind them so finding answers to questions or hiring help
71 | is going to be much easier using these technologies.
72 |
73 |
74 | ## Prerequisites
75 |
76 | This course assumes knowledge of programming and at least basic knowledge of
77 | JavaScript. In addition, you should be comfortable with basic web application
78 | concepts including [REST][] and [CRUD][]. Before you begin, you will also need
79 | to have Node.js and MongoDB already installed. Because you will need to install
80 | various packages for Node.js, you should follow [these installation
81 | instructions][install-node] which use [npm](https://www.npmjs.org/).
82 | Installation instructions for MongoDB can be found [on the Mongo
83 | website][install-mongo]. This tutorial will be based on AngularJS v1.3.10,
84 | Node.js v0.10.31 and MongoDB 2.6.7.
85 |
86 |
87 | {x: install node}
88 | Install Node.js
89 |
90 | {x: install mongo}
91 | Install MongoDB
92 |
93 |
94 | ## Recommendations for Completing this Tutorial
95 |
96 | Throughout the course of this tutorial, links to additional concepts and
97 | information will be included. You can use these links as supplementary material
98 | which can help you gain insight into the stack and its various components. As
99 | always, if you have any questions Google and
100 | [Stackoverflow](http://stackoverflow.com/) are your best bet. If you're unsure
101 | about something specific to this tutorial, feel free to ping me on twitter
102 | at [@IAmMattGreen](https://twitter.com/IAmMattGreen)!
103 |
104 | We at Thinkster are firm believers in actually writing code. Therefor we
105 | **strongly** encourage you to type out all the code instead of copy+pasting it.
106 |
107 |
108 | ## Project Specifications
109 |
110 | Before beginning work on any project, it's usually a good idea to know what you're
111 | building. Below is a basic list of things we want our users to be able to do:
112 |
113 | * Create new posts
114 | * View all posts ordered by upvotes
115 | * Add comments about a given post
116 | * View comments for a given post
117 | * Upvote posts and comments
118 |
119 |
120 | In addition to technologies that make up MEAN, we're going to enlist the help
121 | of several other libraries to help us achieve our goals:
122 |
123 | * [Mongoose.js](http://mongoosejs.com/) for adding structure to MongoDB
124 | * [Angular ui-router](https://github.com/angular-ui/ui-router) for client-side routing
125 | * [Twitter Bootstrap](http://getbootstrap.com/) for some quick styling
126 |
127 |
128 |
129 | # Jumping in with Angular
130 |
131 | {video: ch-1}
132 |
133 | To begin this tutorial, we're going to start with the Angular side of things.
134 | AngularJS is a frontend framework developed by Google with the goal of making
135 | single page web applications easier to build, test, and maintain. Throughout
136 | this tutorials, we'll be linking to our [A Better Way to Learn Angular][better
137 | way] guide which can provide supplementary information.
138 |
139 | Without further ado, let's jump in...
140 |
141 |
142 | ## Getting Started
143 |
144 | As mentioned before, this tutorial will take you through building out a Hacker
145 | News/Reddit clone, which we're going to name "Flapper News". To keep things
146 | simple, we're going to kick things off with two files.
147 |
148 |
149 | {x: create basic angular app}
150 | Create two empty files called `index.html` (for writing the template) and `app.js` (for defining our AngularJS logic)
151 |
152 | To begin, our `index.html` will look like this:
153 |
154 | ```html
155 |
156 |
165 |
166 |
167 | ```
168 |
169 | Our `app.js` is going to look like this:
170 |
171 | ```javascript
172 | var app = angular.module('flapperNews', []);
173 |
174 | app.controller('MainCtrl', [
175 | '$scope',
176 | function($scope){
177 | $scope.test = 'Hello world!';
178 | }]);
179 | ```
180 |
181 | With these several lines of code, we've set up a new AngularJS app and created
182 | a new controller. You'll notice that we declare a variable `test` in the controller and display its contents
183 | using the AngularJS `{{}}` notation. This is demonstrating one of the most powerful features of
184 | AngularJS, which is its two-way data-bindings.
185 |
186 |
187 |
188 | {x: understand data bindings}
189 | If you aren't familiar with data-binding in AngularJS, read the AngularJS Guide on [ two-way data-binding ][data bindings]
190 |
191 |
192 | ## Displaying Lists
193 |
194 | One thing that's is going to be absolutely fundamental to our app is displaying
195 | lists. Fortunately, angular makes this really easy using the
196 | [ng-repeat][] directive.
197 |
198 |
199 | {x: add scope posts array}
200 | To begin, we're going to modify our
201 | controller to include a new `$scope` variable that defines a list of post
202 | titles. Add the following code inside the controller function in `app.js`:
203 |
204 | ```javascript
205 | $scope.posts = [
206 | 'post 1',
207 | 'post 2',
208 | 'post 3',
209 | 'post 4',
210 | 'post 5'
211 | ];
212 | ```
213 |
214 | {info}
215 | The `$scope` variable serves as the bridge between Angular controllers and
216 | Angular templates. If you want something to be accessible in the template such
217 | as a function or variable, bind it to `$scope`
218 |
219 |
220 | {x: using ng-repeat}
221 | Now that we have a list of data we want to repeat, let's use `ng-repeat` to do it. Add the following code to line 8 of index.html,
222 | replacing the div that was there before:
223 |
224 | ```html
225 |
226 | {{post}}
227 |
228 | ```
229 |
230 | When you refresh the page you should see a list of posts!
231 |
232 | Now what if we want to display additional information about our posts?
233 | `ng-repeat` lets us do that too!
234 |
235 |
236 | {x: amend posts objects}
237 | Let's amend our `posts` object to include some
238 | additional information we might be interested in displaying like the number of
239 | upvotes:
240 |
241 | ```javascript
242 | $scope.posts = [
243 | {title: 'post 1', upvotes: 5},
244 | {title: 'post 2', upvotes: 2},
245 | {title: 'post 3', upvotes: 15},
246 | {title: 'post 4', upvotes: 9},
247 | {title: 'post 5', upvotes: 4}
248 | ];
249 | ```
250 |
251 |
252 | {x: display posts objects}
253 | Now we change our `ng-repeat` directive to display the new information:
254 |
255 | ```html
256 |
259 | ```
260 |
261 | Of course it is important to order posts by number of upvotes, and we can tap
262 | into an [angular filter][filters] to make it happen.
263 |
264 |
265 | {x: filter data}
266 | Add a filter to our posts based on the number of upvotes in descending order:
267 |
268 | ```html
269 |
272 | ```
273 |
274 | AngularJS comes with several [built in filters][built in filters] but you can
275 | also write custom filters tailored to your specific needs.
276 |
277 |
278 | # Getting User Input
279 |
280 | {video: ch-2}
281 |
282 | Now that we've learned how to display lists of information with Angular,
283 | it'd really be great if we could have the user add posts. To do this,
284 | we first need to add a function to our `$scope` variable.
285 |
286 |
287 | {x: create addpost function}
288 | Create a $scope function that will add an object into the posts array:
289 |
290 | ```javascript
291 | $scope.addPost = function(){
292 | $scope.posts.push({title: 'A new post!', upvotes: 0});
293 | };
294 | ```
295 |
296 | When this function is invoked, it will append a new post to our `$scope.posts`
297 | variable. Now we're going to have to allow the user to actually execute this
298 | function.
299 |
300 |
301 | {x: create addpost button}
302 | Create a button that executes our addPost $scope function using the [ng-click][ng-click] directive:
303 |
304 | ```html
305 |
306 | ```
307 |
308 | Great, we can now click a button and have a new post show up! Let's extend this
309 | by allowing the user to actually specify what they want the title to be. First,
310 | we need to build out the form in HTML and sprinkle it with some Angular Magic.
311 |
312 | {x: form with ng-submit}
313 | Create a form below the ng-repeat div that will allow us to enter custom posts:
314 |
315 | ```html
316 |
320 | ```
321 |
322 | Here we've created a form that encompasses our title text-box and 'Post' button.
323 | We are also now calling our `addPost()` function using the
324 | [ng-submit][ng-submit] directive, which has the added benefit of the user being
325 | able to press the 'enter' key to submit the form. Finally, we're using the
326 | [ng-model][ng-model] directive to bind the contents of the text box to
327 | `$scope`. This will allow our controller to access the contents of the text box
328 | using `$scope.title`.
329 |
330 | To accompany the changes to our template, we need to make some tweaks to
331 | `addPost()`.
332 |
333 |
334 | {x: tweak addPost}
335 | Have the addPost function retrieve the title entered into our form, which is bound to the $scope variable `title`, and set `title` to blank once it has been added to the posts array:
336 |
337 | ```Javascript
338 | $scope.addPost = function(){
339 | $scope.posts.push({title: $scope.title, upvotes: 0});
340 | $scope.title = '';
341 | };
342 | ```
343 |
344 | When we add a post we are now getting the title from `$scope.title`, which we
345 | then clear after the post has been created. At this point, it makes sense to
346 | prevent the user from posting a blank title.
347 |
348 |
349 | {x: prevent blank title}
350 | Prevent a user from submitting a post with a blank title by adding the
351 | following line to the beginning of `addPost()`:
352 |
353 | ```javascript
354 | if(!$scope.title || $scope.title === '') { return; }
355 | ```
356 |
357 | ## Enable Upvoting
358 |
359 | Now that we can add some new posts, why don't we allow a user to upvote
360 | existing ones? To get started, lets revisit our `ng-repeat` directive.
361 |
362 |
363 | {x: add upvote span}
364 | Next to each post, we need to place a click-able element that will increment the
365 | corresponding post's upvote counter:
366 |
367 | ```html
368 |
372 | ```
373 |
374 | We've now added a `^` character inside a `` tag that when clicked, calls
375 | the `incrementUpvotes()` function in our controller, but we don't have
376 | that function in our controller yet!
377 |
378 |
379 | {x: add upvote function}
380 | Define the `incrementUpvotes()` function in our controller:
381 |
382 | ```javascript
383 | $scope.incrementUpvotes = function(post) {
384 | post.upvotes += 1;
385 | };
386 | ```
387 |
388 | Notice that for this function we are passing the current instance of `post` to
389 | the function. This is happening [by reference][object reference] so when we
390 | increment upvotes, it gets automatically reflected back to the HTML page.
391 |
392 | ## Submitting Links
393 |
394 | Ultimately, Flapper News is about sharing links to content, so lets enable users
395 | to submit links along with their titles. We'll start by adding a second text box
396 | to our form that a user can use to submit a link. We'll also add some placeholder
397 | text to make it clear which form is which:
398 |
399 |
400 | {x: link text field}
401 | Add a link field to our form that is bound to the scope variable `link`:
402 |
403 | ```javascript
404 |
411 | ```
412 |
413 |
414 | {x: extend addPost for links}
415 | Next we're going to want to modify our `addPost()` function to include the link
416 | (notice that we aren't going to force the user to submit a link if they don't
417 | want to):
418 |
419 | ```javascript
420 | $scope.addPost = function(){
421 | if(!$scope.title || $scope.title === '') { return; }
422 | $scope.posts.push({
423 | title: $scope.title,
424 | link: $scope.link,
425 | upvotes: 0
426 | });
427 | $scope.title = '';
428 | $scope.link = '';
429 | };
430 | ```
431 |
432 | Finally we need to modify the `ng-repeat` section to make the title a link
433 | to the content, but only if a link was specified.
434 |
435 | We'll do this by using a new directive called [ng-hide][], which hides elements
436 | when an [Angular expression][] evaluates to true.
437 |
438 |
439 | {x: hide blank link}
440 | Use to `ng-hide` to hide the linked version of the
441 | title if no link exists and correspondingly show the unlinked version:
442 |
443 | ```html
444 |
454 | ```
455 |
456 | It is worth noting that [ng-show][] is merely the inverse of `ng-hide`. You can
457 | use either one for programmatically displaying or hiding elements.
458 |
459 | # Adding Some Style
460 |
461 | {video: ch-3_4}
462 |
463 | At this point, we have the basics of an application - a user can add new posts
464 | which are automatically ordered based on the number of upvotes. Up until now,
465 | however, our interface has been lacking in the looks department. We can spruce
466 | it up a bit using some basic Bootstrap styling.
467 |
468 |
469 | {x: use bootstrap}
470 | Change your `index.html` file to include Twitter Bootstrap along with a new layout:
471 |
472 | ```html
473 |
474 |
475 | Flapper News
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
525 |
526 |
527 | ```
528 |
529 | At the top we've included Bootstrap from a
530 | [CDN](http://en.wikipedia.org/wiki/Content_delivery_network). In the body tag,
531 | we've made use of Bootstrap's [grid system](http://getbootstrap.com/css/#grid)
532 | to align our content in the middle of the screen. We've also stylized the posts
533 | list and "Add a new post" form to make things a little easier to read. There's
534 | a lot more that could be done on this front so feel free to mess around with
535 | more styling before (or after) you continue.
536 |
537 |
538 | {x: add own styles}
539 | (optional) Add your own styles!
540 |
541 | # Angular Services
542 |
543 | Up to this point, we've been storing important data directly in the controller.
544 | While this works, it has some disadvantages:
545 |
546 | * when the controller goes out of scope, we lose the data
547 | * that data cannot be easily accessed from other controllers or directives
548 | * the data is difficult to mock, which is important when writing automated tests
549 |
550 | To rectify this problem, we're going to refactor our `$scope.posts` variable into a
551 | [service][services].
552 |
553 |
554 |
555 | ## My First Service... Is Really a Factory
556 |
557 | In Angular, services are declared much like controllers. Inside `app.js`,
558 | we're going to attach a new service to our `flapperNews` module.
559 |
560 |
561 | {x: create factory}
562 | Create a factory for posts in `app.js` (our `MainCtrl` controller should appear below this):
563 |
564 | ```javascript
565 | app.factory('posts', [function(){
566 | // service body
567 | }])
568 | ```
569 |
570 | {info}
571 | By [Angular conventions][angular style guide], lowerCamelCase is used for
572 | factory names that won't be `new`'ed.
573 |
574 | You may be wondering why we're using the keyword *factory* instead of
575 | *service*. In angular, factory and service are related in that they are both
576 | instances of a third entity called *provider*.
577 |
578 |
579 | {x: different types of services}
580 | The difference between them are nuanced but if you'd like to learn more check out [Angular Factory vs Service vs Provider][factory vs service].
581 |
582 | {x: move posts to factory}
583 | Now that we've created our service, lets go ahead and move our posts variable to
584 | it:
585 |
586 | ```javascript
587 | app.factory('posts', [function(){
588 | var o = {
589 | posts: []
590 | };
591 | return o;
592 | }]);
593 | ```
594 |
595 | What we're doing here is creating a new object that has an array property
596 | called `posts`. We then return that variable so that our `o` object essentially
597 | becomes exposed to any other Angular module that cares to inject it. You'll
598 | note that we could have simply exported the `posts` array directly, however, by
599 | exporting an object that contains the `posts` array we can add new objects and
600 | methods to our services in the future.
601 |
602 |
603 | ## Injecting the Service
604 |
605 | Our next step is to inject the service into our controller so we can access its
606 | data. Simply add the name of the service as a parameter to the controller we
607 | wish to access it from:
608 |
609 |
610 | {x: inject post service}
611 | Inject posts service into `MainCtrl`
612 |
613 | ```javascript
614 | app.controller('MainCtrl', [
615 | '$scope',
616 | 'posts',
617 | function($scope, posts){
618 | ...
619 |
620 | )};
621 | ```
622 |
623 | As you'll recall, two-way data-binding only applies to variables bound to
624 | `$scope`. To display our array of posts that exist in the posts factory
625 | (`posts.posts`), we'll need to set a scope variable in our controller to mirror
626 | the array returned from the service.
627 |
628 |
629 | {x: mirror posts}
630 | Bind the `$scope.posts` variable in our controller to the posts array in our service:
631 |
632 | ```javascript
633 | $scope.posts = posts.posts;
634 | ```
635 |
636 | Now any change or modification made to `$scope.posts` will be stored in the
637 | service and immediately accessible by any other module that injects the `posts`
638 | service.
639 |
640 | # Angular Routing
641 |
642 | {video: ch-5}
643 |
644 |
645 | Now that we understand the basics of Angular templates, controllers, and
646 | services, we're going to start diving into some of the concepts that make
647 | client side web applications so dynamic and powerful. To do this, we're going
648 | to need to learn how to deal with multiple views and controllers, which we will
649 | accomplish using the wonderful
650 | [ui-router](https://github.com/angular-ui/ui-router/) library.
651 |
652 |
653 | {x: add ui-router}
654 | To get started, lets include the library after we include Angular in our
655 | `index.html`:
656 |
657 | ```html
658 |
659 | ```
660 |
661 |
662 | {x: include ui-router}
663 | Because we are adding an external module, we need to be sure to include it as a
664 | dependency in our app:
665 |
666 | ```javascript
667 | angular.module('flapperNews', ['ui.router'])
668 | ```
669 |
670 | You may be wondering why we have chosen to use `ui-router` instead of the more
671 | standard [ngRoute][ng-route] module - `ui-router` is newer and provides more
672 | flexibility and features than `ngRoute`. We will be using a few of these in
673 | this tutorial.
674 |
675 |
676 | ## Adding a New State
677 |
678 | Now that we have `ui-router` included, we need to configure it. In our
679 | `app.js`, we're going to use the Angular `config()` function to setup a
680 | *home* state.
681 |
682 |
683 | {x: configure home state}
684 | Create the config block for our app and configure a home state using [$stateProvider](https://github.com/angular-ui/ui-router/wiki) and [$urlRouterProvider](https://github.com/angular-ui/ui-router/wiki/URL-Routing). Use `otherwise()` to redirect unspecified routes.
685 |
686 | ```javascript
687 | app.config([
688 | '$stateProvider',
689 | '$urlRouterProvider',
690 | function($stateProvider, $urlRouterProvider) {
691 |
692 | $stateProvider
693 | .state('home', {
694 | url: '/home',
695 | templateUrl: '/home.html',
696 | controller: 'MainCtrl'
697 | });
698 |
699 | $urlRouterProvider.otherwise('home');
700 | }]);
701 | ```
702 |
703 | Here we set up our *home* route. You'll notice that the state is given a name
704 | ('home'), URL ('/home'), and template URL ('/home.html'). We've also told
705 | Angular that our new state should be controlled by `MainCtrl`. Finally, using
706 | the [otherwise()][ui otherwise] method we've specified what should happen if
707 | the app receives a URL that is not defined. All that's left to do is define the
708 | `home.html` template. Instead of creating a new file, we are going to move most
709 | of our HTML into an [inline template][].
710 |
711 |
712 | {x: add home template}
713 | Add our inline home template right before the `