#Angular Curriculum
Unit 1:
- Intro and Setup
- 2 Way Data Binding
- Expressions and Filters
- Intro to Controllers
- Built-In Directives
- Angular Events
- Reddit Clone Project
Unit 2:
- Routing
- A New App Structure
- Custom Filters
- HTTP Service
- Movie API Project
- Dependency Injection
- Custom Services
- Tea Store Project
- Controllers Revisited
- Debugging Angular Apps
- Form validations
- Structuring Angular Apps for 2.0
- Custom Directives
Unit 3:
- Angular + Firebase Intro
- Firebase Auth
- Angular + Express
- Angular + Rails
##Introduction and Setup
###How This Curriculum Works
This Angular curriculum is largely self-driven. I'm giving you all the lessons up front. They are part tutorial, part homework assignment. Be prepared to do some research of your own to complete these lessons.
Before you move on to the next lesson, you should:
- Complete all exercises
- Answer all questions
Let's get started!
###What is Angular?
According to the official Angular introduction, Angular is a:
client-side technology, written entirely in JavaScript. It works with the long-established technologies of the web (HTML, CSS, and JavaScript) to make the development of web apps easier and faster than ever before.
It boils down to this: Angular helps us build complex, single-page applications very quickly.
###Setup
We're going to start by setting up a very simple Angular app. Soon we'll learn about patterns for structuring complicated Angular apps, but for now we're just going to use a single index.html
file with a few scripts.
- Create an
index.html
file. - Add Angular. For now were going to use this CDN:
https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.3/angular.js
. - Add jQuery. jQuery is (sort of) a dependency of Angular.
- Add
ng-app
to the<html>
element in your document. This declares everything inside of the<html>
element part of an Angular app. We will see this 'ng' prefix a lot in Angular. - Test it out! Add the following line anywhere inside of the
<body>
tag:{{1 + 6}}
. Open the page in your browser. If Angular is setup correctly, you should see7
displayed.
Questions:
- Why learn Angular JS over other frameworks like Angular, Backbone, Knockout,etc?
- People have some very strong opinions about Angular. What are 3 common complaints people have about Angular?
- Is Angular an MVC framework?
- Why did I say jQuery is "sort of" a dependency of Angular? Does it depend on it or not?!
- Read the docs for
ng-app
. What is it and what does it do?
Resources:
##2 Way Data Binding
Let's start by building something simple that showcases the power of Angular.
In traditional frameworks, controllers package up data from the models with a template and then render a view to the user. That view is a snapshot in time; it only reflects the state of data at the time it was rendered. Newer JavaScript frameworks like Angular and Ember allow us to write dynamic, live templates. This means that we can write Angular templates that will automatically update when our data changes.
This is called 2-way or bi-directional binding.
- when a model changes, the view knows about it
- when a view changes, the model also knows about it.
Let's try it out!
In index.html
create a text input.
<input type="text" placeholder="What is your name">
Add a new attribute ng-model="name"
to the text input. This binds the value of the text input to a property called "name". Technically, ng-model
tries to bind "name" by evaluating the expression on the current $scope, and since the property "name" doesn't already exist on this $scope, it will be created implicitly and added to the $scope. We'll talk a lot more about this when we learn about controllers in a few lessons, so don't worry about it for now.
Now that we've bound the input to the "name" property, let's display the value of "name" on the page. We can write expressions in our HTML using {{ }}
.
<h1>My name is: {{name}}</h1>
Open up index.html
in your browser. What does the h1
say when the page loads? Try typing something into the input and notice that the h1
reflects whatever value we type into the input. This is our first example of 2-way data binding.
EXERCISE: Replicate the exact same functionality without using Angular. In a new file, write vanilla JS code that will automatically update the h1 when the value in the text input changes.
EXERCISE: Use ng-model with a dropdown menu(select tag). Give the user the following four items to pick from: "Dogs", "Cats", "Dogs and Cats", "Neither". Display the user's choice in an h3. For example, if the user selects "Dogs", the h3 should say "I love dogs <3".
Questions
- What does ng-model do?
- What is "dirty checking"?
- Find a way to set the initial value of "name" as "BoJack"(without writing a controller).
- What are those
{{ }}
expressions? Are they Handlebars? - Explain what 2-way data binding is.
- BONUS: Research the $digest loop
Resources:
##Expressions and Built-In Filters
Angular expressions are "Javascript-like snippets that are usually placed in bindings like "{{ expression }}". We've already used expressions to render a property that we set using ng-model
. However, expressions are not just limited to displaying single properties.
Try writing some simple expressions like:
1 + 2 = {{1 + 2}}
My name is {{"BoJack" + " Horseman"}}
The array [99,43,22] is {{ [99,43,22].length }} items long.
You might think that Angular expressions are similar to the <%= %>
tags in EJS or ERB. While they are definitely similar, there are a few key differences. The most important difference is that you cannot write conditionals or loops inside an expression. We'll see soon that Angular provides its own ways of achieving the same functionality.
Just like with EJS or ERB templates, you do not want to have complex logic in your views. If you want to run more complex code, we'll see how to move logic to a controller shortly.
Another key difference is that we can use Angular filters inside of expressions. Filters are simply bits of code that format data before displaying it. Angular comes with a few built-in filters, and we'll also see how to define custom filters later on. Let's try using one:
Add the following expression to your markup:
{{3.14159265359}}
We can use built-in filters to format our long number. The syntax to use a filter is {{ expression | filter }}
.
Let's try using the built-in currency
filter to display the above numbers as a price:
{{3.14159265359 | currency}}
Notice that this filter does 2 things for us: it rounds the data to 2 decimal places and prepends a dollar sign.
QUICK EXERCISE: Change the above code so that our currency filter uses a Euro symbol instead of a Dollar sign. You'll need to do some research of your own.
EXERCISE: Add a text input to a page that displays user input in all caps and all lowercase. You will need to use 2 built-in filters that we haven't covered. Use the following gif as a reference
Let's try using another built-in filter to format our data. Angular has a number
filter that allows us to round numbers to specific decimal places. Try the following:
{{3.14159265359 | number:3}}
{{3.14159265359 | number:6}}
{{3.14159265359 | number:1}}
EXERICSE: Create a drop down menu where the user can select how many digits to round pi to. BONUS: Find out how to pluralize "digit" correctly. Angular comes with a built-in way of pluralizing things! It should work like the following gif.
EXERCISE: Create a simple tip calculator using the basic Angular concepts that we've covered so far(and nothing more advanced). A user can enter a meal price into an input, then select a percentage to tip from a dropdown menu. Display the resulting tip at the bottom of the page. Check out the following gif to see how it should work.
EXERCISE: With one single expression, prove that the context angular expressions run is not the window object. What is it instead?
Questions:
- What are Angular expressions? How do they compare to EJS/ERB tags?
- What happens when you write invalid code in an expression? What type of error do you get?
- What are Angular filters? Name 4 built-in filters, including one that we haven't used yet.
- We'll soon see how to create custom filters. What is a use case for a custom filter?
##Intro to Controllers
In MVC, controllers provide properties and functionality for use in the view. Angular controllers are no different. They are just functions that provide data for use in the views.
Angular takes care of the hard part: connecting our controllers and views together. All we have to do is add various properties to the $scope
and use them inside of our views.
When a new controller is created, Angular automatically gives it a brand new $scope
. The $scope
object is a JS object that glues together controllers and views. Properties that are on the $scope
object are available to the view and the controller. This will make more sense after a few examples!
All properties added to the $scope
are automatically available in our view.
Let's write our first controller! Inside a new JS file app.js
add
var app = angular.module("firstApp", []);
app.controller("MyFirstController", function($scope){
$scope.name = "Severus Snape";
})
The first line tells Angular to create a module named firstApp
. angular.module('firstApp', [])
returns a new module which we use on the next line when we call .controller()
on app
.
Back in our view, we need specify which module our ng-app
should use. Update our <html>
element to <html ng-app="firstApp">
.
Question: What are Angular modules? Why use them?
We're declaring a new controller named "MyFirstController". The first argument to .controller()
is just the name of the new controller, and the second argument is a function that defines the functionality of the controller. Inside of "MyFirstController", we're adding a name
property to the $scope
with the value "Severus Snape".
Note: This is just one way of writing a controller and adding properties to the $scope
. We will discuss some others in the "Controllers Revisited" lesson.
Now let's use the name
property inside of our view.
Before we can access the name
property, we need to specify which part of our template is "inside" of of our controller. To do that, we use the directive ng-controller
.
In index.html
, add the following code:
<div ng-controller="MyFirstController">
</div>
Just like ng-app
declares that the elements inside of a particular element are part of an Angular app, ng-controller
declares that the elements in side of a particular element belong to a controller.
Now we have access to any properties that we set inside of MyFirstController, as long as we access them within the <div>
.
Let's reuse our code from the very first example. Try this
<div ng-controller="MyFirstController">
<h1>My name is: {{name}}</h1>
<input type="text" placeholder="What is your name">
</div>
When you run this in your browser, you'll see that the initial value that we set for name
in our controller is displayed both inside of the text input
and in the h1
. When we refer to name
, angular automatically looks for a property called name
on the $scope
.
Try moving the h1
and input
somewhere outside of the div
. Notice that we no longer have access to the initial value of "Severus Snape".
Why doesn't this cause an error message?
To wrap up, according to the Angular Docs you should use controllers to:
- Set up the initial state of the $scope object.
- Add behavior to the $scope object.
EXERCISE: Write another controller called "ExercisesController" which you will use for the next 3 exercises.
EXERCISE: Add a property called FavColor
and give it an initial value of your favorite color. Display it in the view
EXERCISE: Add another property called secondsInACentury
. It should be equal to the number of seconds in a century(don't worry about leap years and leap seconds). Make sure you actually calculate the answer with code, instead of just looking it up. Finally, display the answer in your template inside of an h3
tag. Use a built-in filter to format the huge number with commas in the right place.
EXERCISE: Create a property called "rightNow" in the controller that is equal to the current time(use a built-in JS function to find the time) Display it in the view and format it nicely(using a built-in filter) so that it looks something like this: Sunday, October 20, 2015
Questions:
- What the hell is
$scope
? - What are Angular modules?
- Why do we pass in
$scope
as an argument to controller functions?
Resources:
##Built-In Directives
Directives are Angular's way of extending HTML. Angular uses directives to add functionality to HTML elements and attributes. According to the docs:
At a high level, directives are markers on a DOM element (such as an attribute, element name, comment or CSS class) that tell AngularJS's HTML compiler to attach a specified behavior to that DOM element or even transform the DOM element and its children.
There are 4 ways of writing directives, but it's best practice to only write them using tag names like <pop-up-dialog></pop-up-dialog>
or <side-bar>
, and attributes like <span fav-spell="expecto patronum"></span>
or <div weather-widget="94114"></div>
.
Angular comes with a bunch of built-in directives, some of which we've already used. We will focus on built-in directives for now, but soon we will write our own custom directives.
EXERCISE: Name at least 3 built-in directives that we have used so far.
Here's a complete-ish list of Angular's built-in directives in no particular order:
- ng-repeat
- ng-hide
- ng-href
- ng-class
- ng-src
- ng-app
- ng-show
- ng-click
- ng-disabled
- ng-checked
- ng-selected
- ng-model
- ng-style
- ng-disabled
- ng-readonly
- ng-include
- ng-switch
- ng-controller
- ng-view
- ng-if
- ng-init
- ng-bind
- ng-cloak
- ng-bind-template
- ng-model-options
- ng-change
- ng-form
- ng-submit
We're going to start by talking about a few of the most common built-in directives
###ng-repeat
ng-repeat
will iterate over a collection and create a template for every item in the collection. Think of it as the Angular equivalent of a forEach
. It's extremely useful. Let's try an example:
Let's start by defining a collection. In a controller, add the following:
$scope.names = ["Harry", "Ron", "Hermione", "Sirius", "Hedwig", "Tonks"];
Now let's iterate through the names
array in the template. In index.html
(make sure you have declared ng-controller
correctly) add the following:
<ul>
<li ng-repeat="name in names">
{{name}}
</li>
</ul>
EXERCISE: Add a property called symbols
with the value ["♠", "♣", "♥", "♦"]
. Use an ng-repeat
to display each one in the template.
BONUS: Figure out how to make these entity codes actually display as symbols,like the following image
EXERICSE: Try using ng-repeat
to iterate through an array with some duplicates, like [1,1,2,5,6,9,9,9]
. What happens? Research how ng-repeat
handles duplicate data and how to "fix" this issue.
EXERCISE: Use ng-repeat
to iterate through the attributes(keys) of an object and list them on in the template
###ng-show/hide
ng-show
and ng-hide
will show or hide a specific HTML element based off of a provided expression. Let's take a look at some examples.
<div ng-show="3 + 4 == 5">
3 + 4 isn't 5, don't show
</div>
<div ng-show="3 + 4 == 7">
3 + 4 is 7, do show
</div>
<div ng-hide="3 + 4 == 5">
3 + 4 isn't 5, don't hide
</div>
<div ng-hide="3 + 4 == 7">
3 + 4 is 7, do hide
</div>
The element is hidden when the expression provided to ng-show
attribute is false. ng-hide
will hide an element when the expression given to ng-hide is true.
EXERCISE: Inspect an element that is hidden by ng-show/hide in the browser. What does Angular do to hide an element?
EXERCISE: Create a simple password validator like the one shown below. If the password is less than 6 characters, hide the submit button and show the error message. Otherwise, show the button and hide the error
###ng-class
ng-class
will dynamically set an element's class depending on a provided expression.
Define the following CSS class:
.highlight {
background-color: yellow;
}
We can use ng-class
to selectively apply our "highlight" class to elements.
<div ng-class="{highlight: 4 + 4 == 8}"> 4 + 4 = 8</div>
<div ng-class="{highlight: 4 + 4 == 10}">4 + 4 = 10</div>
EXERCISE: Build on top of the previous password validator exercise. Use ng-class
to make the form and character count green when valid and red when invalid. Take a look at the following gif:
FINAL EXERCISE: Build a simple camera shop interface using the data provided below. Display each camera's title, image(look into ng-src
), price, and rating. If an item's "onSale" property is true, display the words "ON SALE!!" and give the price a yellow color. A user should be able to sort by price or rating. You'll need to research how to accomplish this."
Use the following data:
[
{
title: "Nikon D3100 DSLR",
image: "http://ecx.images-amazon.com/images/I/713u2gDQqML._SX522_.jpg",
rating: 3.4,
price: 369.99,
onSale: true
},
{
title: "Canon EOS 70D",
image: "http://ecx.images-amazon.com/images/I/81U00AkAUWL._SX522_.jpg",
rating: 2.0,
price: 1099.0,
onSale: false
},
{
title: "Nikon D810A",
image:"http://ecx.images-amazon.com/images/I/91wtXIfLl2L._SX522_.jpg",
rating: 4.2,
price: 3796.95,
onSale: true
}
]
Questions
- Why use
ng-src
andng-href
? - What are directives?
- Does ng-class require an object to be passed in?
- What order does an ng-repeat display items in?
- How does ng-repeat handle duplicate data?
##Events
Angular provides event-handling directives to help us write interactive applications.
###ng-click
ng-click
is used to run a specific method on the current $scope when an element is clicked. Think of it as the Angular equivalent of the on-click
property. Let's use it to build a random number picker!
In a controller, let's add a property to the $scope
called number
:
$scope.number = 5;
Let's display number
in the template:
<h3>The number is: {{number}}</h3>
Next, let's add a button which will call pickRandomNumber()
(we haven't defined it yet) when clicked.
<button ng-click="pickRandomNumber()">Pick Random Number</button>
Now let's implement pickRandomNumber()
. Remember that ng-click
calls a method on the current $scope, so we need to make sure pickRandomNumber()
is defined on the $scope. Back in your controller, add:
$scope.pickRandomNumber = function(){
$scope.number = Math.floor(Math.random() * 10) + 1
}
And that's it! Try clicking your button and watch as the number changes automatically on the screen.
EXERCISE: Explain in as much detail as you can what happens when you click the button. Why does number
update in the template without us telling it to?
EXERCISE: Add a button that will reverse some text when clicked. Take a look at the example gif below.
EXERCISE: Create a simple Ping Pong Score Keeper. It should display the 2 players' scores, have buttons to increment each player's scores, and highlight the winner(assume games only go to 11). It should also display the current server(switch serves every 2 points). Lastly, make sure to include a reset button. BONUS: Keep track of how many games each player wins.
EXERCISE: Contact List. Create a simple contacts app. Each contact has a name, email, and phone number. A user can create new contacts using a form. A user can search contacts as well(you'll need to research this part). HINT: try binding name, email, and phone as properties on one newContact
object rather than creating 3 different properties on the $scope
###Other Events
There are a bunch of other built-in event directives like
- ng-submit
- ng-change
- ng-mousedown
- ng-mouseenter
- ng-mouseleave
- ng-mousemove
- ng-mouseover
- ng-mouseup
They all work just like ng-click
. When a specific event is triggered, they will run a given method on the current $scope.
EXERCISE: Add a feature to a previous exercise using one of the event directives listed above
#Angular Reddit Clone
It's time to put everything you've learned so far about Angular to use. You're going to make a reddit clone with posts, comments, searching, sorting, animations, and more! Watch the following video for an in-depth walk-through of the features.
Requirements:
- Each post has a title, author, image, and description.
- Each post's date/time is displayed nicely: "Yesterday at 3:09pm", "Last Thursday at 4:42am", etc. You will need an external library. Watch the video for more details.
- A user can upvote/downvote posts
- Posts dynamically reorder according to number of votes
- A user can create new posts
- A user cannot create a new post if any of the 4 inputs are blank. Research angular validations. See the video for an example implementation.
- A user can click to view existing comments on a specific post
- The number of comments is correctly pluralized
- A user can add a new comment to a specific post
- The new post form and comment forms can be toggled on and off
- A user can search through posts
- A user can sort posts by votes, date, and title.
- Animate posts as they are added and removed from the search results. Research Angular animations. See the video for an example implementation.
- Style the app. It should look better than my implementation.
Bonus Features
- A user can choose to sort ascending or descending
- A user can favorite posts and view all favorites in a separate tab
- A user can upload an image(no backend involved)
##A New Structure
As our Angular apps grow we'll need a new file structure. It won't work to put everything in an app.js
file when we have multiple controllers, modules, directives, filters, services, and more.
We're going to start by using the following structure:
app
css
js
app.js
controllers.js
directives.js
filters.js
services.js
index.html
This structure is pretty simple. We have an app.js file where we will declare our app and all dependencies. For now it will just look like:
var app = angular.module("whateverYourModuleNameIs")
We also have files like controllers.js
and directives.js
which contain all of our controllers or custom directives(we'll get there soon). Basically, instead of writing everything inside of app.js
, we've broken it out into separate files grouped by functionality.
In order for this structure to work, make sure you correctly include all the scripts in your index.html
file.
EXERICSE: Convert the reddit clone app to this new file structure
The next thing we'll want to do is serve up our application using some sort of server. For now we can just run python -m SimpleHTTPServer
inside of the app directory. Visit localhost:8000
to see the application.
EXERCISE:Serve your reddit clone app using SimpleHTTPServer. Make sure everything works correctly.
Questions:
- What are possible issues with this new file structure?
- Why do we want to serve our application in the first place? It seems to work fine when we just open
index.html
in the browser...
##Routing
One of Angular's most important features is its handling of routing and browser history navigation.
Most single page apps actually consist of multiple "pages" or "screens". For example, we might have views for a landing page, login page, and signup page. Let's take our reddit clone example. What if we wanted to add a "show page" for an individual post? When a user clicks on a particular post, they see a view with details about the post.
A good example of this is gmail. gmail has different views for your inbox, viewing a specific email, composing a message, viewing starred emails, viewing spam etc. The app is still a single page app, you just don't see everything at once.
EXERCISE: Open up your gmail(or similar email provider) account. Try clicking around and accessing the different features. Pay special attention to the url bar. What do you notice?
Typical issues with complex single page apps are:
- SPA's don't support browser history navigation. The back and forwards buttons don't work.
- You can't bookmark parts of a SPA
- If all of our code is in a single template, things can get incredibly messy
Angular provides solutions for all of the above issues through it's router. We can break out a view into multiple smaller views that are rendered inside of a layout depending on the URL the user is currently accessing.
We'll see how to use the $routeProvider
, to make use of the browser’s history navigation and allow users to bookmark and share specific pages based off of the current URL. Let's get started.
First we need to include the ngRoute
module. It's a separate module that we need to include on our own. You can download the module or link to this CDN: "http://ajax.googleapis.com/ajax/libs/angularjs/1.4.3/angular-route.js"
Next, we need to load the ngRoute
module by adding it as a dependent module.
var app = angular.module("yourAppName", ['ngRoute']);
Our index.html
will act as our layout file. Just like in Rails, we'll add all the common markup(navbar, footer, etc.) to our layout file, and then we'll render specific views in a particular place in our layout. Instead of the yield
ERB tag in Rails, we'll use the ng-view
directive. Add the following to index.html
:
<h1>NAVBAR</h1>
<div ng-view></div>
<h1>FOOTER</h1>
According to the docs:
The ng-view directive is a special directive that’s included with the ngRoute module. Its specific responsibility is to stand as a placeholder for $route view content. It creates its own scope and nests the template inside of it.
Next, let's declare some routes. To add a route to a module, we use the config
function. In our app.js
file, add:
app.config(function($routeProvider) {
$routeProvider
.when('/', {
templateUrl: 'partials/home.html',
controller: 'HomeController'
})
.when('/dogs', {
templateUrl: 'partials/dogs.html',
controller: 'DogsController'
})
});
EXERCISE: Before reading on, take time to understand the above code on your own, and make it work. The end goal is to have 2 routes that display 2 different templates. It's up to you to decide what to put in the templates. The above route declarations tell you exactly what files you need to create. Go write them!
EXERCISE: Seriously, do the above exercise. Don't just move on to see the answer!
The above route declarations define two routes: '/' and '/dogs'. When a user visits '/', Angular will render the partials/home.html
file inside of our layout file and set the HomeController
as the controller on the ng-view
. When a user visits '/dogs', Angular will render the partials/dogs.html
file inside of our layout file and set the DogsController
as the controller on the ng-view
.
To make this work, we need to create 2 templates and 2 controllers. Let's start with the /
route. Create a controller in controllers.js
named HomeController
:
app.controller('HomeController', function($scope){
});
Let's add a property to the scope called message
. Set it to whatever you want:
$scope.message = "W elcome!"
Now let's create the template. Inside of app/partials
create a home.html
file.
<p>This is the home template</p>
<p>Message: {{message}}</p>
Make sure when you visit localhost:8000
, you see the above template rendered between the header and footer we created earlier. Let's repeat the same process for the '/dogs' route.
Controller:
app.controller('DogsController', function($scope){
$scope.message = "Woof Woof!"
});
Template:
<p>This is the dogs template</p>
<p>Message: {{message}}</p>
Make sure the second route works correctly by visiting http://localhost:8000/#/dogs
. Play around with the browser navigation buttons. Try bookmarking a page. It should all work!
QUESTION: Why does Angular put a #
in the route path?
EXERCISE: Figure out how to set a "catchall" route that will render the home.html
template if the user visits any other route
EXERCISE: Make a simple portfolio site using Angular. It should have 3 routes: "projects", "bio", and "resume". Add a Bootstrap navbar to the layout file with links to all 3 routes. Figure out how to have the navbar reflect the current route that a user is on.
EXERCISE: Make a simple route-based calculator. When a user visits "/add/4/10", display "14". Do the same thing for division. To accomplish this, your routes will need to have path variables. Research how to define variable segments in your route. Next, you'll need to research how you access the value of path variables inside of a controller. You'll need to find the angular equivalent of the params
hash in rails
EXERCISE: Refactor the above exercise so that your calculator works using the query string. When a user visits "/add/?x=4&y=10", display "14". You will need to research how to access query string data inside of a controller.
EXERCISE: Configure Angular so that routes do not contain #
's. Research!
Questions:
- Why isn't
ngRoute
part of Angular core? Name at least 2 other Angular modules we could use - Compare and contrast client-side routing with server-side routing
- Aside from route definitions, what else can go in a
.config()
? - What is
$rootScope
? - What is the
$routeChangeSuccess
event?
##Custom Filters
We've seen how to use Angular's built-in filters like currency
, number
, and uppercase
. These are definitely useful, but we often will need to create custom filters to format data to meet our specific needs.
EXERCISE: Come up with a situation where you would want to create a custom filter.
We're going to start by creating a "snake_case" to "kebab-case" filter. We want to be able to write: {{"hello_class" | kebab}}
and see "hello-class" in the view.
We need to start by defining a filter named "kebab". In filters.js
:
app.filter('kebab', function () {
});
Remember that filters are just functions to which we pass input and get some returned output to display. Every filter needs to return a function that takes a single argument:
app.filter('kebab', function () {
return function (input) {
};
});
The last step is to write our logic inside of the returned function.
STOP! Think about how you would write the code to convert to kebab-case. Try it on your own first before you move on
Here's a possible solution that will replace all _
's with -
's:
app.filter('kebab', function () {
return function (input) {
return input.replace(/_/g , "-");
};
});
EXERCISE: Fix our kebab
filter so that it doesn't break when you pass it a non-number input. It should just return the unaltered input.
EXERCISE: Write a camel
filter which will take EITHER a snake_cased or kebab-cased string and convert it to camelCase. So {{"hello-world" | camel}}
should display "helloWorld", and {{"hello_world" | camel}}
should also display "helloWorld"
EXERCISE: Write a pigLatin
filter which converts a given string to Pig Latin. Learn the basics of Pig Latin here.
EXERCISE: Create a filter called redact
which will remove all instances of a provided word from a string and replace it with "REDACTED". `{{"My dog Rusty is adorable" | redact: "Rusty"}} should return "My dog REDACTED is adorable". You will need to research creating custom filters that take parameters.
##HTTP Service
Angular services are simply objects that contain some code that can be shared across your app. Like most things we've discussed, Angular comes with some services already, but we can also write our own custom services too.
You can see a list of the built-in Angular services here. Some of the most important ones are:
- $http
- $location
- $rootScope
- $q
- $animate
- $routeParams
The $http
service is arguable the most important and useful of all the built-in services. According to the docs, it "facilitates communication with the remote HTTP servers via the browser's XMLHttpRequest object or via JSONP." It's Angular's wrapper for AJAX calls. It's the easiest way of communicating with a server from an Angular app. Let's try it out!
In order to use the $http
service in a controller, we need to first add it as a dependency. Like this:
app.controller('someControllerName', function($scope, $http){
});
Now we can access all of the methods defined on the $http
service. They are:
- $http.get
- $http.head
- $http.post
- $http.put
- $http.delete
- $http.jsonp
- $http.patch
We're going to start by using $http.get()
to retrieve some very simple data from Github's Zen API located here: https://api.github.com/zen
and then display the resulting data on the page. It's an extremely simple API; all that it does is respond with a single piece of zen programming wisdom. Try visiting the api in your browser.
Don't forget that $http.get()
returns a promise!
$http.get('https://api.github.com/zen').then(function(data){
$scope.zenData = data;
});
In your template, display the value of zenData
. You'll see that it's JSON with a few different properties:
{
"data":"Keep it logically awesome.",
"status":200,
"config": {
"method":"GET",
"transformRequest":[null],
"transformResponse":[null],
"url":"https://api.github.com/zen",
"headers":{
"Accept":"application/json, text/plain, */*"
}},
"statusText":"OK"}
Most of time, we just want the actual response data, so let's change our code slightly:
$http.get('https://api.github.com/zen').then(function(data){
$scope.zenData = data.data;
});
EXERCISE: Use $http.get()
to make a request to reddit.com/.json
and display the title of every post on your template.
EXERCISE: Try making a request to an invalid URL. Write code to properly handle a request that fails. Does Angular have any built-in functionality that could help you?
EXERCISE: Use $http.get()
and $http.post()
to interact with this Rails API that we've made for you. It's a simple collaborative chat app. The API has two endpoints:
The app is one Rails model, Message, which has two attributes: name and content.
- GET
/messages
- responds with a list of all messages - POST
/messages
- creates a new message with the data you send to it
Create a simple app that displays a list of all the messages coming from the API. Also display a form that allows a user to submit a new message to the database.
Remember that most Rails apps expect your data for a given model to be nested inside of a single object with the name of the model. So the data you send should follow this format:
message: {
name: "Mary",
content: "This is such a cool API!"
}
- Questions, what is a service? Is there a Ruby or Node equivalent to Angular services?
- Explain in as much detail as possible what happens under the hood of
$http.get()
- What is
$q
and how does it relate to$http
?
##Movie Search App
It's time to use everything you've learned so far in the 2nd unit to build a small movie search app. This project incorporates the new file structure, routes, and the $http service. Watch the following video to get a sense of how it works:
####Specs:
When a user enters a movie title into the search box and clicks the search button, make an API call to search for movies using the user input. Then, display all the search results on the same page. When a user clicks on specific search result, send them to another route which displays more detailed information about the specific movie. You'll need to make another type of OMDB API call. Lastly, users can start a new search from any page. To sum up the two main routes:
Search/Home Page:
- Input box for user to enter movie title.
- When the user clicks the search button:
- Search OMDb API for movies with the input text in the title.
- Displays search results on the same page
- Each movie in the search results has a link to a 'show' page
Show page:
- Shows the results of a more detailed OMDb API search using the movie's ID.
- Displays all the details of the specific movie, including the poster image of the movie.
Bonus:
- A user can specify what type of data they are searching for: movie, series, or episode.
- A user can check a box "Get Rotten Tomatoes Data" which will also fetch data from Rotten Tomatoes(using the OMDB API)
- Create your own custom filter that filters out all films starring your least-favorite actor or in a genre you dislike, etc.
##Dependency Injection And Services
Dependency injection is core feature to angular that helps us make more modular code.
Dependency injection is a software pattern that angular uses to figure out what dependencies (e.g. $scope, $http service, etc) a software component (e.g. a controller, a module, etc) has. For example, the following controller depends on $scope and $routeParams:
app.controller('MathController', function($scope, $routeParams){
$scope.val1 = parseInt($routeParams.val1, 10);
$scope.val2 = parseInt($routeParams.val2, 10);
$scope.op = "";
$scope.result = 0;
if ($routeParams.op === "add") {
$scope.op = "+";
$scope.result = $scope.val1 + $scope.val2;
} else if ($routeParams.op === "subtract") {
$scope.op = "-";
$scope.result = $scope.val1 - $scope.val2;
}
});
EXERCISE: Look at an angular app you have made previous (reddit clone or your portfolio site). What dependencies are there? Where do you see dependencies other than the contoller?
EXERCISE: In the above example (MathController) does the order of the dependencies matter? Does $scope have to come before $routeParams? Do the names matter? Could we name them something else?
EXERCISE: DO NOT SKIP THIS EXERCISE. In production code, you typically want your javascript file to be as small as possible so that it can be downloaded faster. To make the files smaller, developers minify their js files. Find a minification tool and minify your js code. Update your html file so that it now points to your newly minified js files. Does your angular app still work? If it stopped working, what is the problem?
EXERCISE: So far we have mainly seen one way to do dependency injection. Research and figure out the other two ways. Which one is the best practice?
Angular supports 3 ways to define dependencies for your code:
- Implicit annotation
- Inline array annotation
- $inject property annotation
We have been usiing implicit annotation so far. Implicit annotation is kind of a hack because angular actually has to parse your code and convert the parameters of your function to a string in order to figure out your dependencies. The implicit annotation dependency injection will break when you minify your code. This happens because minfication tools rename variables to a something smaller, but the minification tool doesn't know that the variable name in the function is meaningful to angular.
BEST PRACTICE: Always use inline array annotation.
EXERCISE: Fix the app you minified earlier to use inline array annotation. Minify the javascript files again. Verify that the code still works when using the minified js.
EXERCISE: When using inline array annotation does the order of anything matter? What order should match?
EXERCISE: Read over the angular docs on dependency injection. Try to understand as much as you can.
##Custom Services
Angular services are components that are added to your code via dependency injection. From the angular docs:
Angular services are:
* Lazily instantiated – Angular only instantiates a service when an application component depends on it.
* Singletons – Each component dependent on a service gets a reference to the single instance generated by the service factory.
EXERCISE: Name at least 3 angular built in services that we have used so far.
Now that we are more comfortable using services and injecting a service as dependency, let's create our own. In your contact app, add the following code to service.js
:
app.factory('contacts', function() {
var contacts = {};
contacts.contactList = [];
contacts.addContact = function(name, email, phone) {
contactList.data.push(name: name, email: email, phone: phone);
};
contacts.findContact = function(name) {
// TODO
};
contact.removeContact = function(index) {
// TODO
};
return contacts;
});
Now in your controller you can add the contact service you created as a dependency. For example, your controller might look like this:
app.controller('ContactController', ["$scope", "contacts", function($scope, contacts){
$scope.contactData = contacts.contactList;
// TODO: Your contacts controller code here.
}]);
EXERCISE: Refactor your contacts app to use a contacts service. Remember to stick with best practices and use the inline array annotation.
EXERCISE: Add a show page to your contacts app. This will require a separate controller but you can use the same contacts service and share it between controllers. The "id" for the show page should by the index of contact in the contactList.
In jQuery you saw promises often. The most common use case was an ajax call. For example, you might have some code like this in jQuery:
$.get("/puppies").done(function() { // do something here})
.fail(function() { // do something here});
The object returned from the $.get
method is a promise that can then be chained to other method calls when certain events take place. You can think of a promise as a more generalized way of implementing a callback.
Another popular library for promises is q
EXERCISE: Read the docs for the q promise library. Why would you prefer to use a promise over a callback? What advantage does it have?
Your service could also have dependencies. We have seen the $http
service. In the example we will also used the $q
service to add deferreds to our service:
app.factory('omdbapi', ["$http", "$q", function($http, $q) {
var omdbservice = {};
var baseUrl = http://www.omdbapi.com/?plot=short&r=json&s=";
var searchTerm = '';
omdbservice.setSearchTerm = function(term) {
searchTerm = encodeURIComponent(term);
}
omdbservice.getSearchTerm = function() {
return decodeURIComponent(searchTerm);
}
omdbservice.search(term) {
if (term !== undefined) {
omdbservice.setSearchTerm(term);
}
var url = baseUrl + searchTerm;
var deferred = $q.defer();
$http.get(url).success(function(data) {
deferred.resolve(data);
}).error(function() {
deferred.reject("Error!")
});
return deferred.promise;
}
return omdbservice;
}]);
EXERCISE: Use the Giphy Api to add a feature to your app. Whenever a new user is submitted, do a search for a gif using the person's name. If you get a result, save that along with the users name email and phone number. Show the user's gif on the show page. HINT: you probably want to use the embedded url froom the giphy search resutls.
- Search by name
- Search by category with drop down list
- Table with column for image, and tea information
- Quantity drop down number list
- Add to bag button
- New items added to bag default to qty of 1, if no qty is selected
- Checkout button
- Checkout/bag starts empty, then updates with number of items
- Tea information section has:
- Price, Caffeine Scale, Ingredients, Rating
- In Stock? - shows
Yes
orNo
when True/False respectively - Categories - goes through categories list and displays each one
- Checkout page
- Order total
- Lists each item that was added from the previous page, and includes its quantity
- Ability to edit quantity
- Editing quantity updates the sub-total and order total
- Ability to remove a product, which then updates the order total
- Items in checkout show the caffeine scale, ingredients, rating, and sub-total
[
{
_id: "55c8ee82152165d244b98300",
name: "Bayard stew",
ingredients: "concentrated gluten, jewelry, dill, beetle nut, toast",
caffeineScale: 244,
price: 1540,
inStock: true,
rating: 1,
imageUrl: "http://s7d5.scene7.com/is/image/Teavana/32664_d?$cimg$",
__v: 0,
categories: [ "dark", "cold"]
},
{
_id: "55c8ee82152165d244b98301",
name: "Incompactness syrup",
ingredients: "fennel, nutmeg leaves, parsley, cream of 'cream of cream', blarney",
caffeineScale: 49,
price: 7348,
inStock: true,
rating: 2,
imageUrl: "http://s7d5.scene7.com/is/image/Teavana/32303_d?$cimg$",
__v: 0,
categories: ["awesome"]
},
{
_id: "55c8ee82152165d244b98302",
name: "Flexner white tea",
ingredients: "hot sauce, iron, beetle nut, fresco, blarney, raw mashed potato",
caffeineScale: 38,
price: 4991,
inStock: true,
rating: 4,
imageUrl: "http://s7d5.scene7.com/is/image/Teavana/31358_d?$cimg$",
__v: 0,
categories: ["cold"]
},
{
_id: "55c8ee82152165d244b98303",
name: "Pressor leaf",
ingredients: "purina chow, flavorings, pepper, acorns, quality tallow, old sock, bay leaf",
caffeineScale: 153,
price: 5496,
inStock: true,
rating: 1,
imageUrl: "http://s7d5.scene7.com/is/image/Teavana/31358_d?$cimg$",
__v: 0,
categories: ["dry", "hot", "awesome"]
},
{
_id: "55c8ee82152165d244b98304",
name: "Flexner veggie tea",
ingredients: "cream of tartar, eggplant, cake, deer antler",
caffeineScale: 181,
price: 2445,
inStock: true,
rating: 1,
imageUrl: "http://s7d5.scene7.com/is/image/Teavana/32621_d?$cimg$",
__v: 0,
categories: ["summer"]
},
{
_id: "55c8ee82152165d244b98305",
name: "Topflighter malt",
ingredients: "botox, toast, cream of 'cream of 'cream of cream'', kitchen scraps, beef, aligator tongue, lawn clippings",
caffeineScale: 241,
price: 4486,
inStock: true,
rating: 3,
imageUrl: "http://s7d5.scene7.com/is/image/Teavana/31359_d?$cimg$",
__v: 0,
categories: ["dry","lucid","warm"]
},
{
_id: "55c8ee82152165d244b98306",
name: "Cooking mix",
ingredients: "flavorings, roasted mushrooms, toast, tumeric",
caffeineScale: 230,
price: 6973,
inStock: true,
rating: 3,
imageUrl: "http://s7d5.scene7.com/is/image/Teavana/32303_d?$cimg$",
__v: 0,
categories: ["summer"]
},
{
_id: "55c8ee82152165d244b98307",
name: "Cooking stew",
ingredients: "eggplant",
caffeineScale: 122,
price: 6003,
inStock: true,
rating: 2,
imageUrl: "http://s7d5.scene7.com/is/image/Teavana/31358_d?$cimg$",
__v: 0,
categories: ["dry","winter","lucid"]
},
{
_id: "55c8ee82152165d244b98308",
name: "Prevenient herb tea",
ingredients: "cream of tartar, cream of cream, kitchen scraps, flavorings",
caffeineScale: 196,
price: 1374,
inStock: true,
rating: 3,
imageUrl: "http://s7d5.scene7.com/is/image/Teavana/32174_d?$cimg$",
__v: 0,
categories: ["lucid","hot"]
},
{
_id: "55c8ee82152165d244b98309",
name: "Angular mix",
ingredients: "hot sauce, lawn clippings, fennel, parsley, quinine",
caffeineScale: 196,
price: 4158,
inStock: true,
rating: 2,
imageUrl: "http://s7d5.scene7.com/is/image/Teavana/32621_d?$cimg$",
__v: 0,
categories: ["spring", "warm","winter"]
}
]
sss