Back to Index

AngularJS end-to-end web app tutorial Part III


Chapters

0:0
5:0 The Javascript Keyword
8:40 Animation
9:6 Unsuccess Callback
11:28 Creating the Button
11:40 Add Item Button
13:29 Create Controller
14:37 Create a Label and a Field
20:51 Success Callback
24:1 Editor Controller
24:15 Edit Controller

Transcript

Welcome back to part three of this tutorial on creating a CRUD application using AngularJS. In parts one and two, we created an application which is able to display a list of to-dos which is taken from the database and also gives us the ability to search them, to sort them, and also to paginate them.

The data is coming out of a SQL Server database. It goes through a REST API built with C# using the Web API framework and is then displayed using AngularJS templates and controllers and directives. We also showed how to do this with a Python and SQLite backend. Today we're going to add the ability to create, delete, and update to-dos.

After that we will have a basic working CRUD application. Today's tutorial is written up on my blog, as per usual, and we'll be looking at part three. To get started, let's add the ability to delete. In order to delete a to-do, we're going to have to have some button that we can click in our interface to delete a particular to-do.

So we'd like an icon next to each of our rows that we'll be able to click on to delete. In order to add an extra cell to our table, we'll need to add it to D and then we want to put an icon in there. Let's see what we have.

There's no delete by the looks of it. Is there a remove? There is a remove. Okay, so we'll add an icon called remove. That's going to add an icon to every row. Now we need that to be clickable, so we're going to have to surround that with an "a" tag.

So that's going to make it clickable, and when you click it, what's going to happen? Well, we're going to need ng-click. That's the thing that lets us call a method in $scope, and we put the method name here. So let's call it delete. So that should be enough to get us a clickable icon, although it won't do anything yet, but let's just make sure that's working.

There we go. So we now have a little x at the end of each row, and we click on it, it's going to delete something. Now what's it going to delete? Well, we're going to delete by ID. That's the way our web API works, our REST API. Now one approach to this would be to actually pass in that ID here, so we could actually say delete and pass in our ID here.

But that sounds, and of course that's going to have to be like so. But I actually want to put as little code in the template as possible and really have that working on the back end. So let's see if we can figure that out in the controller. So to start with, let's add this delete method to our controller.

So here's our controller here. Let's add a new function called delete. And as we discussed, it's not going to take any parameters. So somehow we need to get the ID of the current to do. Now how are we going to do that? Well, as we go through this ng-repeat, each of the items ends up in this object called todo.

And interestingly, this is actually going to be inside its own scope. So ng-repeat actually creates a whole separate scope for every single row. And inside that scope, there will be something called todo. However, that scope is not accessible normally in the controller just by typing $scope. Because that scope actually refers to this list controller's $scope.

So somehow we have to get access to the particular ng-repeat $scope. How do we do that? Well, the trick is to use the JavaScript keyword this. If you've used JavaScript much, you'll know that this is a bit of a mysterious beast in some ways until you understand exactly how it works.

So let me explain first exactly what this means. When you are inside an object, this actually refers to whatever is before the period sign, before the full stop in the thing that's calling you. Now in this case, the thing that's calling us is delete. And delete actually, because it's inside an Angular directive, actually refers to $scope.delete.

And in fact, because we're inside a repeater here, inside ng-repeat, it's actually referring to the repeat scope. So it's not referring to the list controller scope, it's actually referring to the repeat scope. So that means that since this is $scope.delete inside a repeat, that means that inside that method, this will be referring to the ng-repeat scope.

That means that we can actually type this.todo inside here and get access to this particular object. So if that all sounds a bit confusing in terms of why it's the case, then you can just take away the main message, which is when you're working with a ng-repeat, you can get the object that you're currently working with by simply using this inside any method that you call from that repeat.

So we want to grab hold of the ID from there. So let's do so now. So now that we've got the ID, we need to actually do the deletion. So to do that, we use our todo object as per usual, that thing that comes out of $resource. And in that, we can then call delete.

And we need to tell it that we want to delete a particular ID. The one that we want is the one we just stuck inside ID. So that should be enough to actually get the deletion to happen. Let's test it, shall we? So let's try deleting the one from April 22nd.

Now, when I click on it, nothing appeared to happen, but let's try refreshing. That nothing did happen. Oh, here's probably the problem. My ID is capitalized incorrectly. We could have checked that in the debugger easily enough otherwise, but let's see if this now works. So click here. Okay, that's good.

So I didn't get any console error. We still actually see it here. But if I F5 to refresh, you can see it's now gone. So in other words, what happened is we have successfully deleted it from the database, but I had to do a refresh to get that deletion to actually appear.

Now that's of course not ideal. We would actually like it to disappear from this view as soon as I click it. And ideally, I'd quite like it to actually fade out. I think that's a good user experience to actually observe that deleting in front of us. Now, in order to get something to fade out, we're going to have to do some animation.

And the best way to do that probably is with jQuery. In general, it's considered a bad idea to use things like jQuery inside your controller, because your controller really shouldn't be working with the DOM, shouldn't be working with your HTML. That's really for directives. But in this case, I only have a single line of code, and I think it's really the easiest way to do it.

So why don't we try that here? So to do that, we're going to need to have a onSuccess callback. So function, just like we've had before in our onSuccess callbacks. In this case, we're going to go we're going to use jQuery, and there's going to be something that we're going to fade out.

So all we need to do now is figure out what that something is. So that something is going to have to be the id, we use a hash here, of the particular role that we want to get rid of. So that means that we need to add an id to each row.

So in this case, I've just added an id, which is todo_ and then the id. So now that we have an id on each row, we can now refer to that here. It's going to be todo_ and then our id. So let's try that, shall we? Refresh. Let's try getting rid of the one from September the 8th, 2012.

Click here, and you can see it has in fact disappeared. And let's try pressing F5 to refresh and make sure it's still gone. Yep, it's still gone. June the 11th. Let's try refreshing. Still no June the 11th. Okay, great. So that is working as we expected. So now that we have delete working, let's try and get add working.

So in order to add additional todo, we're actually going to have to create another template for that, because we want a whole other screen where you can add that information. Now, to get to that template, to get that additional template, we're going to have to have another route which we can go to.

It's going to need another controller, and it's going to need another template. So these are all the things that we're going to have to add in order to get this new functionality working. And we're going to have to then be able to actually call that from our list.html. So why don't we start, as usual, with creating the button that people are going to have to click.

So one nice easy way to do that would be to simply add it to the top of our remove items list. We'll have a add item button. Sounds pretty straightforward. So we add a table header, and again, we're going to have to add an icon. And let's see if there's a plus sign.

Yes, there is a plus and a plus sign. I don't know what the difference is. And so as before, we're going to have to pop an A around this. Now, this time we don't want an ng-click. ng-click is to call a method to make something happen, not to go somewhere else.

To go somewhere else, you have to use href. Where do you want to go? Well, all Angular hrefs are going to start with hash, because hash, as far as your browser is concerned, means that you're staying on the same page. And when you're building a single page application, such as with Angular, effectively, as far as the server is concerned, it's all coming from the same page.

It's all coming from index.html. So that's why we always start with hash. And then after the hash, we have the route that you want to go to. So we can create whatever route we like. So let's call it new. Okay, so now that we've created this type link here to go to new, we can see what that looks like.

And you can see here in the bottom left that it's going to go to index.html hash slash new. So we now need to add that route to Angular. We know how to do that because we've done it before. Slash, so we're now going to add slash new. So we need both a controller and a template.

So we could create a create controller and a details template. So first of all, we're going to have to create the create controller. Create controller. Let's just leave it empty for now and try and get to a point where at least we have a basic functionality working that we can actually at least go to that template and see it.

So the next thing we need to do is create our details.html template. So this is going to be a form. The form will have a standard bootstrap form set of classes and kind of layout. Since that's pretty standard, I have a template for that. So I'll just create a new form and we're going to have an add button on the form.

So this is a standard bootstrap form. If you go to the bootstrap documentation, you can see what each of these classes does. And in my bootstrap form, I need to create a label and a field for each of my text, GDate and priority. So once again, we're going to use a standard kind of bootstrap set of classes and elements to do that.

So let's use the template I've created for that. So we're going to first of all do the text. So here we have the label for that. Let's just say to do. Let's create another one then. Which will be due date. And that let's change the label just to be due.

And then finally, we're going to have priority. Now one thing to point out with priority is that we don't need to use just input type equals text. There's actually all kinds of other types we can use here as defined by HTML5. And for example, there's one which is input type equals number.

That's going to ensure that we actually put in some more appropriate data in here so that somebody doesn't insert something that will not be a valid priority. As you'll see when we run it, it also gives us some nice number specific functionality. So let's have a look at what else is going on here.

So as well as our standard bootstrap classes and the usual labels and so forth, we have a couple of extra things. The first is interesting. It's ng-class. We haven't seen this before. And this is really neat. ng-class takes a JavaScript object where the keys are the classes that you want to add.

And the values are things that return true or false where if it's true, then that class will be added. So in this case, error will be added if form.priority.dollar invalid is true. We'll look more at validation in a future tutorial. But for now, just recognize that in your form, the field name dot invalid property will be set to true if your input does not validate correctly.

So for example, in this case, if you didn't have a number in it, it won't validate correctly, which is going to cause the class error to be added. So let's try this out. That's looking pretty good at this stage. So we'll refresh this. And we'll try clicking this plus sign and that should take us to our new template.

And yes, indeed it did. And you can see here that the special type equals number has added up and down arrows. I can type a 2 into here. And if I type something that's not a number, you can see it goes red. That's that error class being added to it.

And I can also increase and decrease. That's all the functionality we have so far. One other thing I might do for looks is to just add a header on top like so. There we go. So the next thing we need to do is make it so that the add button actually does something.

And we know how to make that happen. We do that by using ngClick. So let's use that to save this new item. Okay, so let's go and create save inside our create controller. So save doesn't take any parameters. And what we want to do is we want to call to do that's in dollar.

That's our dollar resource object. Remember, that's going to have a save method. And what do we want to save? Well, here's where something pretty amazing just happened. Look here at the ngModel for each of our fields. It's item.text, item.dueDate, item.priority. What is item? When we first load this up, item is not anything.

We never set it to anything. But as soon as I put anything into one of these text boxes, it then tries to set item.text. And Angular is smart enough that if item doesn't exist, it will just create it for us as an empty object and then immediately set.text. So the neat thing is that once this form starts getting filled out, dollar.scope.item is automatically created for us.

We know it's in dollar.scope because everything inside an Angular directive value is always considered to be inside dollar.scope. Dollar.scope is the way that we share information between our template and our controller. So actually, all we need to do is save our item. So after we save it successfully, then obviously we want to go back to our list in order that we can see it there.

So we need to now add our onSuccess callback. And although this is a single page application, there are different routes or different templates that we need to be able to navigate. And Angular handles all of that complex history management and everything for us. And it's all abstracted away inside this special service called dollarLocation.

So you can look in the Angular documentation to see exactly how that works. In this case, we're just going to use the path method and tell it what route we want to go to. We're just going to go to slash. So once we've saved it successfully, we're going to go back to slash.

So let's see if that works, shall we? So we add a new todo. We're not going to worry about the due date for now because that really needs a date picker. In a future tutorial, we'll learn about how to add a date picker to this. It's actually as simple as using a pre-existing directive and adding a single attribute to our import.

We'll see that in practice later. We'll give it a priority of zero. We'll press add. And nothing happened. Let's try it again. Add. Nothing continued to happen. Okay, let's find out why not. So create control has a dollar scope dot save. Oh, here's the problem. Details to HTML has not been saved.

Silly me. So let's refresh. And try again. All right, item is not defined at object dot dollar scope dot save. And I think I know exactly what the problem is here. It's not called item. Of course, it is dollar scope dot item because it was created inside our template.

Let's refresh that. Try again. There we go. So that is successfully added this with a priority we requested, and then we can delete it again. So now we have delete and add working correctly. In order to create the ability to edit these to-dos, we first of all, of course, need a icon that we can click next to each one.

So let's simply copy the delete one and replace it with there's no update. Is there an edit? There is an edit. So let's replace it with an edit. Now, of course, in this case, we actually have to go to a whole new screen to do this editing. So we're going to need href for that.

And before it starts with a hash. And this is where we can now put in any route we want to. So we'll call it edit. And of course, that means that inside app dot JS, we need that route to be added in here. So edit. So in this case, we're going to have a new controller, edit controller, and we can use the same template.

However, details to HTML should be fine. It's got basically the same structure as what we want. So let's now create our edit controller. So there we've got our edit controller. And in this case, the template actually, the template actually needs to be pre filled in with the item we clicked on.

So what that actually means is we need to do more than just say, href equals edit, we actually need to say, which idea we editing and let's put that into the actual route or the actual URL. So of course, the idea we're editing is simply to do to ID.

So let's put it there. So that means that if we have a look at how that looks on the page now, we can see now in the bottom left, that there's now the ID is being appended to each one. So what we now need to do is we need to tell Angular that we actually want to save the value of that to use it later.

So to tell it that we want to put something from the URL into a location to use later, we can start with colon, and then we give it the name of the parameter. So this is going to now say, okay, there's going to be an extra thing in the URL, and I want you to save it as something called edit ID.

So we now need something that allows us to find information from those root parameters. And in fact, Angular has a service for that. It's called $root-per-ems. So remember, there's this really neat thing about dependency injection, which is this idea that anytime you want access to some service, which Angular provides, you can simply add it to your parameter list of your controller, give it the appropriate name, and it will automatically be created and passed to you.

And in order to find out exactly how any of these services work, you can pop into the documentation and look it up. Here is root-per-ems. And there's a nice example here of how to use it. And as you can see, it's just simply going to create a standard JavaScript object with the key value pairs of whatever we named each parameter along with the value that it was given.

So in this case, our ID is going to be equal to root-per-ems. And we call it edit ID. So that's going to be our ID. So we now need to set our dollar scope.item to be that object. And that's really easy to do, isn't it? You just go scope.item equals.

And this is where we can use our toDo class, this dollar resource object. And we can call get and pass in an ID to be set to the ID parameter. And it should be as easy as that. So that should be enough at this point to be able to display whatever we want to inside our details page.

So let's try it, shall we? So we'll click on, let's say, toDo number 9. And we can confirm here is toDo number 9. The date does not look very nicely formatted. We're going to leave that for another time, both in terms of time zone issues, formatting issues, and using a date picker to be able to edit this nicely.

And we're going to look at using directives for that. But for now, observe that we're at least able to display all of these. Something I notice here is that this add text here and here is not appropriate for what we're now doing, which is updating. So let's change it.

So in our details.html, rather than saying add, let's add a parameter. Remember, it's going to be $scope.something. And let's call this action. So $scope.action is going to be where we're going to stick the name, add, or update. And we're going to change it in the button text as well.

So now that we've done that, we can go back and say, all right, I want you to set scope.action. for here to be update. And in our create controller, oopsie dozie. Let's copy that and paste it. And in our create controller, we'll use add. Let's see how that looks now.

There we go, update to do and update button. So finally, we need our, what's the button going to actually do when we click on it? It's going to call something called save in our controller, which means in our edit controller, we need a save function. Now, previously, our save function called todo.save.

What does todo.save do? Well, because todo is just the return of a $resource function, we can see in $resource what that does. $resource, if we scroll down, tells us that save is going to call the post method in our web API. And if we go and have a look at our web API, so we can see what's going on.

In our API, post is the thing that actually creates a new todo. But in this case, we want to use put because put is the thing which saves an existing todo. And we get passed in the ID and the todo to save. So how do we call put? Interestingly enough, there is no put in the default list of stuff that $resource provides.

And that's why we've actually added an additional thing here, which we've passed to our $resource call. And this is a list of any additional methods we want to add and what HTTP verb they correspond to. So we now have added something called update. So that means that we can call todo.update.

And so first of all, we need the ID that we want to update. And then we need the item that we want to update it with. So that should be enough to get that working. The only other thing we want to do is that if it's successful, we want to go back to our list of todos where we can see the new one being added to that list.

So we know how to do that already. Remember, it was just $location.path. And where do you want to go? We want to go to the root, the list controller and the list template. So let's see how we're going now. So we'll refresh this. And let's update this priority one todo number nine and change it to priority zero update.

And we can see, yes, indeed, there is todo nine is now priority zero. And you can see that it's automatically sorted this appropriately for us. This is one of the nice things about using an MVC framework is you don't have to worry too much about the particular order that things happen.

You can just describe how you want things bound, what data you want bound to what HTML elements. And it handles all this kind of dependency stuff automatically for you. You can see here how easy it was for us to add our create, update and delete functionality to our application.

In general, when you're writing an Angular application, if you find yourself using kind of an unloaded method or manually updating the view from the controller or vice versa, that generally suggests that you're doing something in a way that's not the way Angular is designed. When you're using Angular as designed, really, you should be doing everything pretty declaratively as we have been here.

Angular attempts to make it easy for you to do everything in that way. However, in order to do so, it does require becoming familiar with all of the stuff that Angular provides for you. And it is quite a substantial API, as you can see. But all of these things are there to make your life easier.

So as you learn more and more of the system, it becomes easier and easier. So now that we have a complete working CRUD application where we can sort, search, paginate, create, delete, update and view, we're now at a point, I think, where we should be thinking about getting this into a version control system so that we can keep track of the changes that we make.

And we should be trying to get it up on the web so other people can use it. So those are the two things that we'll be looking at in our next tutorial. Thanks very much for keeping with me this far.