Talk given at CakeFest 2014
AngularJS is the fastest growing Javascript framework and for good reason. Come learn why and watch while we build a simple application (no it won't be the standard To Do app) right before your very eyes!
If you haven't used AngularJS, this talk is definitely for you. If you do any front end work at all, you need to know about AngularJS. If you're using AngularJS already I may be able to answer some of your questions and we'll talk about best practices when building an Angular app. We may even touch on some more advanced features.
16. Requirements
➔ Provide and edit a name for the ranch
➔ Show all the animals on the ranch
◆ Load from server
◆ Show the gender name for the type of animal based
on species, gender and age
➔ Search the animals on the ranch
32. Step 7: Show gender names
Horse:
Female Male Altered (M)
> 3 years Mare Stallion Gelding
< 3 years Filly Colt Gelding
Dog:
Female Male
Unaltered Bitch Stud
Altered Spayed Female Neutered Male
Chicken:
Female Male Altered (M)
> 1 years Hen Rooster Capon
< 1 years Pullet Cockerel Capon
39. Simple HTML page -> Dynamic HTML page
Angular Concepts
Scope variables ($scope)
Use <input> with ng-model
Include templates with ng-include
Create a directive (new HTML element)
Loaded data from the server - ngResource
ng-repeat - loop through an array
search by applying a filter to ng-repeat
Create a custom filter
Extended our application with new modules
Linked all the parts of it with ngRoute
40. Resources
● Angular Website:
http://angularjs.org
● Code School: Shaping up with AngularJS
https://www.codeschool.com/courses/shaping-up-with-angular-js
● Yeoman Angular Generator:
https://github.com/yeoman/generator-angular
● Angular Best Practices: http://blog.angularjs.org/2014/02/an-angularjs-
style-guide-and-best.html
● Angular-UI:
https://angular-ui.github.io/
In other words it lets you simplify your code
The less you have to write and test the better, right?
AngularJS does a lot of the hard work for you - that’s pretty much the definition of a framework.
Provides Templating (click), Routing (click), and synchronizes the model and the user interface (iow Data Binding) (click) and a host of other things that make your life easier
Now if we look at our HTML we have lots of messy code and extra stuff that tells us how to display information, not just what to display,
We have the header area (click). It’s only in there once, but it is just plain messy and what if we wanted to add it again, perhaps as a footer?
And we have many copies of the animal code that shows each individual animal. (click)
Let’s see what we can do to clean this up.
The simplest way to do templating is just to include a partial. This is useful especially when you have static pieces you want to include multiple times.
Let’s use ng-include for the header.
First, let’s create a file just to contain the header code. We’ll put it in the partials directory.
Now we can replace the header in index.html with an ng-include. (click)
Our HTML is looking better already! (And we still haven’t written one line of javascript)
This works for us at the moment. We can do it better, but this will work for us for now.
Now we are faced with (click) the animals. Which is repeated 17 times on the page but with a different animal name, species, gender, age and image.
We could use 17 ng-includes…
But there’s a better way. Wouldn’t you like to write something like (click)
I know I would. It’s declarative, it’s easy to tell what it is putting on the page but we don’t have to worry about implementation details. And if we change how it’s implemented, we don’t have to change 17 versions of it
So now we do have to write some javascript.
First let’s name our application. We do that by giving the ng-app attribute a value. (click) We’ll call our application ‘ranch’. Now when the ng-app directive is processed angular will know to look for an angular module called ranch.
But, we haven’t told angular what this ranch module is. So… let’s do that
First we’re going to create a new file called app.js. (click)
This is where we tell angular how to bootstrap our application.
Let’s start at the end. (click) angular.module tells angular “Create a new module and inject these other modules in this array into it”. So here we are telling it create a new module called ranch (click). And we’re going to use another module called ranch.animal so inject this dependency called(click) ranch.animal into ranch.
Angular uses Dependency Injection heavily, so it’s a good idea to understand what it is. Dependency Injection (DI) is a software design pattern that deals with how components get hold of their dependencies. The Angular injector subsystem is in charge of creating components, resolving their dependencies, and providing them to other components as requested.
Now we could do everything in one module. But it is just good practice to set up your application from the start to be extensible. We have an animals list so anything to do with animals we will put in the ranch.animal module.
So, moving up the line above that creates the module ranch.animal with no dependencies.
The way I’m doing this is just good practice..
Next let’s define the ranch.animal module more….
First let’s tell angular how to interpret an animal tag when it sees it in HTML. (click)
We’ll create animal/animal-directive.js .
It attaches a directive called animal to the ranch.animal module ‘animal’. The directive which is described by this object.
* Restrict tells it that this directive can be used as an element (which is the E) or as an attribute (which is the A). So we can either write it as <animal …> or <div animal …> - it’s flexible and helps with non-compliant browsers like IE (you can also use a shim to make IE recognized new element tags)
* Replace says, replace whatever element you see with what is in this directive
* templateURL: read this template and use it
* scope: defines an isolated scope (it can’t see or modify the parent scope). Our new scope has one attribute, animal, which it gets from the element’s attribute called info. the = means that it is from the parent scope and allows us to modify it within the scope of this element. This would be absolutely important, if we were editing the animal (which would be likely but not something we will implement today). We could just use an @ and get a copy of the value of the parent scope. But we will stick with =.
I strongly suggest you spend time learning about directives!
Now we need to define the template that the directive is looking for (click)
You can see this looks very much like our original HTML source for Gypsy except (click)that I’ve replaced all of the individual animal information with references to the animal object.
(click) Notice that the Notes section is surrounded with a span that has an ng-show directive on it. ng-show says only make this element visible if the expression evaluates to truthy. If animal.notes doesn’t exist then it will be falsy. If animal.notes has 0 length it will also be falsy and so it won’t be visible. It will only be visible if animal.notes has a value
There’s one other difference, can anyone spot it? (chocolate to someone who does)
Hint: take a close look at the image tag.
(click) You’ll notice ng-src instead of src for the image. ng-src is a directive that can be used as an attribute on the image tag. When you put an image tag on the page, the src field is only looked at once, the image is loaded and that’s it. But what if animal.img changes? It will no longer reread it and you’re stuck with the same image. ng-src solves this problem and lets you change the image dynamically.
Finally (click) Let’s modify index.html.
Now we tell angular which application to use (the ranch app) and add the scripts that we just wrote.
Then we can replace all divs for the animals of the animals with animal tags. I won’t bore you with the details of all that, suffice it to say I’ve done it and here’s the result. (show in browser)
That’s great! Now we can show our prototype to our user and they can tell us how it doesn’t look anything like they wanted and we can go back and easilly change it to their heart’s content.
So far so good, but let’s make it DO something! (click)
Now let’s get to the meat of the matter!
The animals aren’t going to be static. They will probably change all the time as animals come and go and some get eaten (well the chickens anyway the horses and dogs probably won’t be eaten). So we need to be able to read the list of animals probably from some database on the server. (click)
Typically angular apps will get data from some kind of RESTful service that usually sends data in JSON format (any data source is useable, but JSON is by far the most prevalent atm and makes the most sense for our purposes so we’ll use that). I’m sure when you are writing Cake apps, you have some way to make a RESTful service. (In fact we made one during the workshop)
We also want to be able to search the animals that we know about and only show the ones matching the search string.
Finally, the user wants to see the correct gender name for the animals based on the species, gender, and age.
So let’s get going.
Data (especially when it comes from outside your application - e.g. from a server) should always come from a Service. Services know how to fetch the data, modify it, save it if appropriate, etc. In fact anything that interacts with data should be in a service and completely encapsulate it.
We’re going to write a very simple service to fetch our animal data. Then we’ll loop through it and display each animal in the data.
To create a service (click) , we tell our module about it with .service (if anyone brings it up, .service is just a shorthand for .provides)
Give it a name, tell it what to inject and write the function that defines what the service does.
We are using an angular service $resource to fetch the data via ajax. $resource simply wraps $http which in return wraps jquery $.ajax and makes it easy to use. I actually prefer Restangular and use it for all my RESTful APIs but I stayed with the standard here (I leave research of Restangular as an exercise for the reader:).
The call to $resource is going to return an object with the method called query that when called will get an array of objects from data/animals.json
By referring to $resource in the function we are asking angular to find that and inject into our service. So now we need to tell angular about this dependency.
$resource happens to be in the module ngResource, so let’s inject that into our module that we created in app.js. (click)
Next we have to tell our app about the data. Now we need a controller, so let’s write one (click)
Controllers wire things up and provide controls to the view.
So here, we tell angular, here’s a controller called animalCtrl. Please inject the service Animal and $scope. $scope is the current scope when the controller is executed. Then we define a function for our controller.
As I said, controllers wire things up, so here we are telling it to add animals to the $scope and where we get the animals is from Animal.query(). This is also where other controller type functions will go. What happens when the user pushes a button, etc. For now, this is all we need.
So now we need to inject our controller into the view in the right place. Remember in our index.html, we have a div that surrounds all the animals. We only need the animal controller on something that controls the animals, so let’s put it on that div. (click)
ng-controller is a directive that tells angular, “Hey, start a new scope and inject this controller right here”.
That’s great, but so far what we’ve done it won’t look like anything different - there’s nothing here that puts anything in the view, our animals are just sitting out there unused …
So let’s use them.
Remember how we told HTML to show the animals (click) And we had to repeat it for each animal that we had. We had 17 copies of this code one for each different animal.
Let’s change that to show the animals from our scope and show however many there are. (click) It only takes one HTML element to do that.
That’s sure a lot shorter than 17 copies of the first one!
ng-repeat says “repeat this DOM element I’m on for each element of the array”.
animal in animals says use the array animals and call each element of it animal for the duration of this DOM element.
So now we have a new scope for this DOM element, and it has an animal variable (if we were in the controller for this element it would be in $scope.element). Now we pass the animal variable to our animal directive and we get this result (show browser)
Wow that was easy.
So The next thing on our list is searching? How hard do you think it will be?
Searching is actually trivially easy.
First we need to get a search string from the user. (click)
There’s nothing here we haven’t seen before. We put an input box on the page and attach it to a model on our scope called animalquery, so whatever the user types will be placed in animalquery and we can use that in our search
Then we limit our results to just what matches the string. (click)
What does the magic here is the filter. ng-repeat takes each of the animals and passes it to the filter to determine whether it is included in the set. If the value of the filter is true, it shows on the page, if it’s false it doesn’t. filter is a filter called filter it accepts a string, object or function to use to filter on and returns true or false for each item in the animals array.
Here’s the result (browser) (Type various search strings and show how the results are limited immediately) Can you imagine doing that with a round trip application?
Filters are actually incredibly powerful. So, let’s use it to do something else. Our client wants to show the correct names for each animal based on species, age, and gender. These are technically called Gender Names
Who knows what a Horse is called if it is a female older than 3 years old? (Mare), how about a male? (Stallion), what if they are 3 years old or less (Filly, Colt). What about a gelded horse less than 3 and older than 3 (Gelding, Gelding). What about an altered female? (it’s so rare it doesn’t have a name)
So what about dogs? They don’t really care about age with dogs, it is (and I say this in the female dog term, not the swearing term) Bitch or Stud, but they do care about altered status, then it’s Spayed Female or Spayed Bitch and Neutred Male.
Now how about chickens? Anyone know what age the names change? Females: Pullet (< 1 year), Hen (> 1 year or started laying), Males: Cockerel (< 1year), Rooster or Cock (> 1 year). Altered Male: Capon (any age)
See you thought you were going to learn about AngularJS in this talk! No you’re really going to learn the correct names for animals :)
Okay okay, back to Angular, let’s see how we can show those names.
First Let’s see how we might call a filter to show the gender name (click)
Let’s place the gender name in parenthesis right after the animal’s name. So just like we did on the ng-repeat, we pipe the object to be filtered to the filter, in this case genderName. Filters can work on any object not just arrays like we did with ng-repeat. And they can transform the input in any way that you want. In this case we will filter the animal object, returning the correct gender name for the animal. You could do the same type of thing to convert between miles and kilometers or ounces and pounds or US$ and Euros.
So how do we write the gender Names filter?
I’ve cut out the parts that do the real work here :). But you can see what’s important for our purposes. We define a filter on our module, give it a name, then provide a function that does the actual work. A filter takes the object plus optional additional inputs and returns an output. In this case it is taking the animal object as input and returning a string.
Since we have put this in {{ }} in our template, the expression will be replaced with the value of the filter after it is evaluated.
One thing to note about filters. They should be fast! Filters may be called hundreds of times per digest cycle and a digest cycle happens anytime anything changes. So absolutely make them fast.
Now I think we are about done, we’ve met all the requirements we’ve been given.
Excuse me a second, I think I hear my phone ringing…. (pick up phone) “Hello?” (Cover mouthpiece and whisper) “It’s our client”. (normal voice) “What’s that? … You want to track equipment on the ranch too and a task list, not just the animals? “ (Cover mouthpiece and whisper) “Why didn’t they tell us that in the first place!” (normal) “Oh you have found more money in the budget? … Well of course we can do that, we’ll get right on it.”
Well it’s a good thing we made this thing extensible.
While we’re not going to build all of this right now, we’ll lay the foundation and leave it to any interested party to finish the details.
Our new requirements include two more modules, equipment and tasks.
So what we need is three different views on our page. One for animals, one for Equipment and one for tasks.
I think what we should do is put a menu across the top that let’s the user click a link to go between the 3 lists. So let’s create a partials/menu.html. There’s actually a bit more in it than that but the rest is just just gives it some formatting.
And we can put it on our page under the header with an ng-include.
That’s nice… but how does it know what to do when you click on the links? We need to wire it up with ngRouter. So let’s go to app.js.
First we really have three very different things that we are doing. Remember how we made a module for the animals? Well now we are going to make two more, one for equipment and one for tasks. These two new modules will handle all the work for equipment and tasks.
We’re not done with app.js yet. Next we need to inject those new modules into our application. And we need a new module that angular provides called ngRoute. (cclick)
ngRoute basically gives us the ability to move between routes within the page without reloading. First we need to configure it so that it knows what to do when we click on a link. (click)
Remember that in our modules we have added controllers, directives and services. We can also add configuration with the config method. So here we are telling angular, configure the $routeProvider (from the ngRoute module), and tell it when it sees /animal use the template animal/animal-list.html and use controller animalCtrl, when it sees /equipment use template equipment/equipment-list.html and equipmentCtrl controller. When you see /tasks use templateURL task/task-list.html and the taskCtrl controller. Otherwise, if you don’t recogized anything, then use /animals by default.
What you say, but we don’t have any of those things? Well let’s write them. For now we will just stub the equipment and tasks so let’s start with them, they will be easy.
Some simple stubs for Equipment and task templates. (click)
And some simple stubs for the Equipment and task Controllers (click)
Now for the animal list. When we click animals, we want to show the list that we have been showing with the search box, so let’s just take that code out of index.html and put it in our new template. We’ll also add a header, so that it looks like this. (click)
There’s nothing new here, we have just taken it from index.html and put it in a template. You may notice that I have also removed the ng-controller directive. That’s because we have configured the route provider to automatically use our controller. We can use the same controller we’ve been using.
Great, we’re almost done. We’ve done all this work, but now we have nothing in our index.html that shows the Animals, Equipment or Tasks! (click)
Index.html is pretty sparse at the moment. We include the header, we include the menu… and that’s it, where’s our Lists?
There’s a directive that works with ngRoute called <ng-view> . Ng-view tells ngRoute where to place the template for the route. So let’s add that to index.html and we get… (click).
Wow, that was simple, let’s see what it looks like. (switch to browser) We can click on each of the links and show our stubs for Equipment and Tasks and see the animals we started with. We can search the animals and change the name of the ranch. What great work and look how little we actually had to write! Once we finish the Equipment and Tasks (which can be done in very similar ways) we’ll have one very happy customer.
So what have we been able to do?
* We took a simple HTML template and made it dynamic by adding angular.
* We used the scope to keep track of the ranch name
* We edited an input field and linked it to our scope with ng-model so that it and control the name of the ranch
* We simplified the HTML by using templates and include them with ng-include
* We also used directives to create more complicated templates that can also have actions and interact with the scope
* We used ngResource to load our data from the server
* We showed the data dynamically on the page using ng-repeat to loop through an array of items
* We added searching by applying a filter to the ng-repeat
* We displayed proper gender names by writing a custom filter.
* Finally we extended our application by creating two new modules and linking it all together with ngRoute.
Wow that’s a lot! We’ve successfully built a full application only a few lines of actual javascript code. And we did it in under an hour from start to finish
Angular iis pretty amazing.
You’ll want to keep learning about Angular so here are some excellent resources to get you started.