back to indexAngularJS 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
00:00:02.000 |
Welcome back to part three of this tutorial on creating a CRUD application using AngularJS. 00:00:12.800 |
we created an application which is able to display a list of to-dos which is taken from the database 00:00:22.320 |
and also gives us the ability to search them, to sort them, 00:00:30.480 |
The data is coming out of a SQL Server database. 00:00:36.960 |
It goes through a REST API built with C# using the Web API framework 00:00:43.840 |
and is then displayed using AngularJS templates and controllers and directives. 00:00:54.480 |
We also showed how to do this with a Python and SQLite backend. 00:00:58.640 |
Today we're going to add the ability to create, delete, and update to-dos. 00:01:07.600 |
After that we will have a basic working CRUD application. 00:01:20.640 |
Today's tutorial is written up on my blog, as per usual, and we'll be looking at part three. 00:01:28.160 |
To get started, let's add the ability to delete. 00:01:38.960 |
we're going to have to have some button that we can click in our interface to delete a particular 00:01:48.000 |
to-do. So we'd like an icon next to each of our rows that we'll be able to click on to delete. 00:01:53.920 |
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 00:02:04.080 |
icon in there. Let's see what we have. There's no delete by the looks of it. Is there a remove? 00:02:15.520 |
There is a remove. Okay, so we'll add an icon called remove. 00:02:18.880 |
That's going to add an icon to every row. Now we need that to be clickable, 00:02:25.760 |
so we're going to have to surround that with an "a" tag. 00:02:28.560 |
So that's going to make it clickable, and when you click it, what's going to happen? 00:02:35.280 |
Well, we're going to need ng-click. That's the thing that lets us call a method in $scope, 00:02:42.560 |
and we put the method name here. So let's call it delete. So that should be enough to get us 00:02:48.800 |
a clickable icon, although it won't do anything yet, but let's just make sure that's working. 00:02:52.960 |
There we go. So we now have a little x at the end of each row, and we click on it, 00:03:00.960 |
it's going to delete something. Now what's it going to delete? Well, we're going to delete by ID. 00:03:06.560 |
That's the way our web API works, our REST API. Now one approach to this would be to actually 00:03:16.080 |
pass in that ID here, so we could actually say delete and pass in our ID here. 00:03:24.080 |
But that sounds, and of course that's going to have to be like so. 00:03:32.800 |
But I actually want to put as little code in the template as possible and really have that 00:03:38.080 |
working on the back end. So let's see if we can figure that out in the controller. 00:03:43.920 |
So to start with, let's add this delete method to our controller. So here's our controller here. 00:03:57.840 |
Let's add a new function called delete. And as we discussed, it's not going to take any parameters. 00:04:02.720 |
So somehow we need to get the ID of the current to do. Now how are we going to do that? 00:04:10.160 |
Well, as we go through this ng-repeat, each of the items ends up in this 00:04:19.280 |
object called todo. And interestingly, this is actually going to be inside its own scope. 00:04:26.080 |
So ng-repeat actually creates a whole separate scope for every single row. 00:04:30.640 |
And inside that scope, there will be something called todo. However, that scope is not accessible 00:04:37.120 |
normally in the controller just by typing $scope. Because that scope actually refers to 00:04:44.400 |
this list controller's $scope. So somehow we have to get access to the particular 00:04:55.040 |
ng-repeat $scope. How do we do that? Well, the trick is to use the JavaScript keyword this. 00:05:03.360 |
If you've used JavaScript much, you'll know that this is a bit of a mysterious beast in some ways 00:05:10.160 |
until you understand exactly how it works. So let me explain first exactly what this means. 00:05:17.520 |
When you are inside an object, this actually refers to whatever is before the period sign, 00:05:27.520 |
before the full stop in the thing that's calling you. Now in this case, the thing that's calling 00:05:34.320 |
us is delete. And delete actually, because it's inside an Angular directive, actually refers to 00:05:41.760 |
$scope.delete. And in fact, because we're inside a repeater here, inside ng-repeat, 00:05:48.480 |
it's actually referring to the repeat scope. So it's not referring to the list controller scope, 00:05:55.680 |
it's actually referring to the repeat scope. So that means that since this is $scope.delete 00:06:02.160 |
inside a repeat, that means that inside that method, this will be referring to the ng-repeat scope. 00:06:10.480 |
That means that we can actually type this.todo inside here and get access to 00:06:18.560 |
this particular object. So if that all sounds a bit confusing in terms of why it's the case, 00:06:26.720 |
then you can just take away the main message, which is when you're working with a ng-repeat, 00:06:32.000 |
you can get the object that you're currently working with by simply using this inside any 00:06:39.920 |
method that you call from that repeat. So we want to grab hold of the ID from there. So let's do so now. 00:06:54.800 |
we need to actually do the deletion. So to do that, we use our todo object as per usual, 00:07:02.800 |
that thing that comes out of $resource. And in that, we can then call delete. And we need to 00:07:12.480 |
tell it that we want to delete a particular ID. The one that we want is the one we just stuck 00:07:16.960 |
inside ID. So that should be enough to actually get the deletion to happen. Let's test it, shall we? 00:07:30.560 |
So let's try deleting the one from April 22nd. 00:07:33.680 |
Now, when I click on it, nothing appeared to happen, but let's try refreshing. 00:07:41.120 |
That nothing did happen. Oh, here's probably the problem. My ID is capitalized incorrectly. 00:07:51.280 |
We could have checked that in the debugger easily enough otherwise, but let's see if this now works. 00:07:57.200 |
So click here. Okay, that's good. So I didn't get any console error. 00:08:05.760 |
We still actually see it here. But if I F5 to refresh, you can see it's now gone. So in other 00:08:14.160 |
words, what happened is we have successfully deleted it from the database, but I had to do 00:08:17.920 |
a refresh to get that deletion to actually appear. Now that's of course not ideal. We would actually 00:08:23.600 |
like it to disappear from this view as soon as I click it. And ideally, I'd quite like it to 00:08:29.200 |
actually fade out. I think that's a good user experience to actually observe that deleting 00:08:36.480 |
in front of us. Now, in order to get something to fade out, we're going to have to do some animation. 00:08:41.120 |
And the best way to do that probably is with jQuery. In general, it's considered a bad idea 00:08:48.720 |
to use things like jQuery inside your controller, because your controller really shouldn't be working 00:08:54.320 |
with the DOM, shouldn't be working with your HTML. That's really for directives. But in this case, 00:08:59.600 |
I only have a single line of code, and I think it's really the easiest way to do it. So why don't 00:09:04.080 |
we try that here? So to do that, we're going to need to have a onSuccess callback. So function, 00:09:09.840 |
just like we've had before in our onSuccess callbacks. In this case, we're going to go 00:09:14.880 |
we're going to use jQuery, and there's going to be something that we're going to fade out. 00:09:26.160 |
So all we need to do now is figure out what that something is. 00:09:33.440 |
So that something is going to have to be the id, we use a hash here, of the particular role that 00:09:41.360 |
we want to get rid of. So that means that we need to add an id to each row. So in this case, 00:09:48.960 |
I've just added an id, which is todo_ and then the id. So now that we have an id on each row, 00:09:55.680 |
we can now refer to that here. It's going to be todo_ and then our id. So let's try that, shall we? 00:10:10.240 |
Refresh. Let's try getting rid of the one from September the 8th, 2012. Click here, 00:10:16.240 |
and you can see it has in fact disappeared. And let's try pressing F5 to refresh and 00:10:21.680 |
make sure it's still gone. Yep, it's still gone. June the 11th. Let's try refreshing. 00:10:30.480 |
Still no June the 11th. Okay, great. So that is working as we expected. 00:10:38.240 |
So now that we have delete working, let's try and get add working. 00:10:43.680 |
So in order to add additional todo, we're actually going to have to create another template for that, 00:10:58.560 |
because we want a whole other screen where you can add that information. 00:11:03.840 |
Now, to get to that template, to get that additional template, we're going to have to 00:11:08.480 |
have another route which we can go to. It's going to need another controller, and it's going to 00:11:13.920 |
need another template. So these are all the things that we're going to have to add in order to get 00:11:18.320 |
this new functionality working. And we're going to have to then be able to actually call that 00:11:23.600 |
from our list.html. So why don't we start, as usual, with creating the button that people are 00:11:30.880 |
going to have to click. So one nice easy way to do that would be to simply add it to the top of our 00:11:37.920 |
remove items list. We'll have a add item button. Sounds pretty straightforward. So we add a table 00:11:45.680 |
header, and again, we're going to have to add an icon. And let's see if there's a plus sign. 00:11:54.400 |
Yes, there is a plus and a plus sign. I don't know what the difference is. 00:11:59.920 |
And so as before, we're going to have to pop an A around this. Now, this time we don't want 00:12:07.600 |
an ng-click. ng-click is to call a method to make something happen, not to go somewhere else. To go 00:12:14.400 |
somewhere else, you have to use href. Where do you want to go? Well, all Angular 00:12:24.720 |
hrefs are going to start with hash, because hash, as far as your browser is concerned, means that 00:12:30.560 |
you're staying on the same page. And when you're building a single page application, such as with 00:12:35.840 |
Angular, effectively, as far as the server is concerned, it's all coming from the same page. 00:12:41.600 |
It's all coming from index.html. So that's why we always start with hash. And then after the hash, 00:12:48.880 |
we have the route that you want to go to. So we can create whatever route we like. So let's call it 00:12:53.360 |
new. Okay, so now that we've created this type link here to go to new, we can see what that looks like. 00:13:02.320 |
And you can see here in the bottom left that it's going to go to index.html hash slash new. 00:13:10.080 |
So we now need to add that route to Angular. We know how to do that because we've done it before. 00:13:18.800 |
Slash, so we're now going to add slash new. So we need both a controller and a template. 00:13:26.480 |
So we could create a create controller and a details template. So first of all, 00:13:37.440 |
we're going to have to create the create controller. Create controller. Let's just leave it empty for 00:13:47.120 |
now and try and get to a point where at least we have a basic functionality working that we can 00:13:53.760 |
actually at least go to that template and see it. So the next thing we need to do is create 00:13:58.320 |
our details.html template. So this is going to be a form. The form will have a standard bootstrap 00:14:07.920 |
form set of classes and kind of layout. Since that's pretty standard, I have a 00:14:16.800 |
template for that. So I'll just create a new form and we're going to have an add button on the form. 00:14:26.000 |
So this is a standard bootstrap form. If you go to the bootstrap documentation, 00:14:30.560 |
you can see what each of these classes does. And in my bootstrap form, I need to create 00:14:39.600 |
a label and a field for each of my text, GDate and priority. So once again, 00:14:50.080 |
we're going to use a standard kind of bootstrap set of classes and elements to do that. So let's use 00:15:04.320 |
the template I've created for that. So we're going to first of all do the text. 00:15:17.200 |
the label for that. Let's just say to do. Let's create another one then. 00:15:37.200 |
And that let's change the label just to be due. And then finally, we're going to have priority. 00:15:53.760 |
Now one thing to point out with priority is that we don't need to use just input type equals text. 00:16:00.720 |
There's actually all kinds of other types we can use here as defined by HTML5. And for example, 00:16:07.440 |
there's one which is input type equals number. That's going to ensure that we actually put in 00:16:12.160 |
some more appropriate data in here so that somebody doesn't insert something that will 00:16:19.280 |
not be a valid priority. As you'll see when we run it, it also gives us some nice number specific 00:16:25.360 |
functionality. So let's have a look at what else is going on here. So as well as our standard 00:16:30.880 |
bootstrap classes and the usual labels and so forth, we have a couple of extra things. 00:16:37.120 |
The first is interesting. It's ng-class. We haven't seen this before. 00:16:43.120 |
And this is really neat. ng-class takes a JavaScript object where the keys are the classes 00:16:53.760 |
that you want to add. And the values are things that return true or false where if it's true, 00:17:00.960 |
then that class will be added. So in this case, error will be added if form.priority.dollar 00:17:09.120 |
invalid is true. We'll look more at validation in a future tutorial. But for now, just recognize 00:17:16.080 |
that in your form, the field name dot invalid property will be set to true if your input 00:17:29.440 |
does not validate correctly. So for example, in this case, if you didn't have a number in it, 00:17:35.760 |
it won't validate correctly, which is going to cause the class error to be added. 00:17:40.320 |
So let's try this out. That's looking pretty good at this stage. 00:17:46.560 |
So we'll refresh this. And we'll try clicking this plus sign and that should take us to our 00:17:59.760 |
new template. And yes, indeed it did. And you can see here that the special type equals number has 00:18:07.280 |
added up and down arrows. I can type a 2 into here. And if I type something that's not a number, 00:18:14.160 |
you can see it goes red. That's that error class being added to it. 00:18:18.880 |
And I can also increase and decrease. That's all the functionality we have so far. 00:18:27.280 |
One other thing I might do for looks is to just add a header on top like so. There we go. 00:18:47.840 |
So the next thing we need to do is make it so that the add button actually does something. 00:18:56.880 |
And we know how to make that happen. We do that by using ngClick. 00:19:08.640 |
Okay, so let's go and create save inside our create controller. 00:19:15.440 |
So save doesn't take any parameters. And what we want to do is we want to call to do 00:19:26.960 |
that's in dollar. That's our dollar resource object. Remember, that's going to have a save 00:19:30.480 |
method. And what do we want to save? Well, here's where something pretty amazing just happened. 00:19:36.400 |
Look here at the ngModel for each of our fields. It's item.text, item.dueDate, item.priority. 00:19:47.040 |
What is item? When we first load this up, item is not anything. We never set it to anything. 00:19:53.920 |
But as soon as I put anything into one of these text boxes, it then tries to set item.text. 00:20:01.920 |
And Angular is smart enough that if item doesn't exist, it will just create it for us as an empty 00:20:08.320 |
object and then immediately set.text. So the neat thing is that once this form starts getting 00:20:14.160 |
filled out, dollar.scope.item is automatically created for us. We know it's in dollar.scope 00:20:20.480 |
because everything inside an Angular directive value is always considered to be inside 00:20:27.920 |
dollar.scope. Dollar.scope is the way that we share information between our template 00:20:31.840 |
and our controller. So actually, all we need to do 00:20:35.680 |
is save our item. So after we save it successfully, then obviously we want to go back 00:20:47.200 |
to our list in order that we can see it there. So we need to now add our onSuccess callback. 00:20:52.800 |
And although this is a single page application, there are different routes or different templates 00:21:01.360 |
that we need to be able to navigate. And Angular handles all of that complex history management 00:21:07.360 |
and everything for us. And it's all abstracted away inside this special service called 00:21:12.880 |
dollarLocation. So you can look in the Angular documentation to see exactly how that works. 00:21:18.080 |
In this case, we're just going to use the path method and tell it what route we want to go to. 00:21:22.560 |
We're just going to go to slash. So once we've saved it successfully, we're going to go back to 00:21:29.360 |
slash. So let's see if that works, shall we? So we add a new todo. We're not going to worry about 00:21:36.560 |
the due date for now because that really needs a date picker. In a future tutorial, we'll learn 00:21:42.160 |
about how to add a date picker to this. It's actually as simple as using a pre-existing 00:21:47.200 |
directive and adding a single attribute to our import. We'll see that in practice later. 00:21:54.480 |
We'll give it a priority of zero. We'll press add. And nothing happened. Let's try it again. Add. 00:22:04.320 |
Nothing continued to happen. Okay, let's find out why not. 00:22:09.680 |
So create control has a dollar scope dot save. Oh, here's the problem. Details to HTML has not 00:22:14.560 |
been saved. Silly me. So let's refresh. And try again. 00:22:23.280 |
All right, item is not defined at object dot dollar scope dot save. 00:22:32.160 |
And I think I know exactly what the problem is here. It's not called item. Of course, it is 00:22:38.160 |
dollar scope dot item because it was created inside our template. Let's refresh that. Try again. 00:22:44.880 |
There we go. So that is successfully added this with a priority we requested, 00:22:53.920 |
and then we can delete it again. So now we have delete and add working correctly. 00:23:06.000 |
In order to create the ability to edit these to-dos, we first of all, of course, 00:23:11.200 |
need a icon that we can click next to each one. So let's simply copy the delete one and replace it 00:23:21.440 |
with there's no update. Is there an edit? There is an edit. So let's replace it with an edit. 00:23:28.480 |
Now, of course, in this case, we actually have to go to a whole new screen to do this editing. 00:23:35.120 |
So we're going to need href for that. And before it starts with a hash. And this is where we can 00:23:43.280 |
now put in any route we want to. So we'll call it edit. And of course, that means that inside app 00:23:49.440 |
dot JS, we need that route to be added in here. So edit. So in this case, we're going to have a new 00:24:00.320 |
controller, edit controller, and we can use the same template. However, details to HTML 00:24:07.040 |
should be fine. It's got basically the same structure as what we want. 00:24:27.440 |
And in this case, the template actually, the template actually needs to be pre filled in 00:24:36.720 |
with the item we clicked on. So what that actually means is we need to do more than just say, 00:24:42.480 |
href equals edit, we actually need to say, which idea we editing and let's put that into the 00:24:48.080 |
actual route or the actual URL. So of course, the idea we're editing is simply to do to ID. 00:24:56.560 |
So let's put it there. So that means that if we have a look at how that looks on the page now, 00:25:03.360 |
we can see now in the bottom left, that there's now the ID is being appended to each one. 00:25:14.480 |
So what we now need to do is we need to tell Angular that we actually want to save the value 00:25:22.240 |
of that to use it later. So to tell it that we want to put something from the URL 00:25:28.160 |
into a location to use later, we can start with colon, and then we give it the name of the 00:25:36.000 |
parameter. So this is going to now say, okay, there's going to be an extra thing in the URL, 00:25:40.720 |
and I want you to save it as something called edit ID. So we now need something that allows us to 00:25:49.200 |
find information from those root parameters. And in fact, Angular has a service for that. 00:25:57.840 |
It's called $root-per-ems. So remember, there's this really neat thing about dependency injection, 00:26:06.960 |
which is this idea that anytime you want access to some service, which Angular provides, you can 00:26:13.200 |
simply add it to your parameter list of your controller, give it the appropriate name, 00:26:17.440 |
and it will automatically be created and passed to you. And in order to find out exactly how any 00:26:22.960 |
of these services work, you can pop into the documentation and look it up. Here is root-per-ems. 00:26:34.480 |
And there's a nice example here of how to use it. And as you can see, it's just simply going to 00:26:41.040 |
create a standard JavaScript object with the key value pairs of whatever we named each parameter 00:26:48.080 |
along with the value that it was given. So in this case, our ID is going to be equal to 00:26:57.040 |
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 00:27:08.320 |
scope.item to be that object. And that's really easy to do, isn't it? You just go scope.item 00:27:18.880 |
equals. And this is where we can use our toDo class, this dollar resource object. 00:27:29.360 |
And we can call get and pass in an ID to be set to the ID parameter. 00:27:39.600 |
And it should be as easy as that. So that should be enough at this point to be able to display 00:27:47.520 |
whatever we want to inside our details page. So let's try it, shall we? So we'll click on, 00:27:53.680 |
let's say, toDo number 9. And we can confirm here is toDo number 9. The date does not look very 00:28:01.840 |
nicely formatted. We're going to leave that for another time, both in terms of time zone issues, 00:28:06.400 |
formatting issues, and using a date picker to be able to edit this nicely. And we're going to look 00:28:11.920 |
at using directives for that. But for now, observe that we're at least able to display all of these. 00:28:18.080 |
Something I notice here is that this add text here and here is not appropriate for what we're 00:28:26.160 |
now doing, which is updating. So let's change it. So in our details.html, rather than saying add, 00:28:35.280 |
let's add a parameter. Remember, it's going to be $scope.something. And let's call this 00:28:42.000 |
action. So $scope.action is going to be where we're going to stick 00:28:47.680 |
the name, add, or update. And we're going to change it in the button text as well. 00:28:54.240 |
So now that we've done that, we can go back and say, all right, I want you to set scope.action. 00:29:04.960 |
for here to be update. And in our create controller, 00:29:12.320 |
oopsie dozie. Let's copy that and paste it. And in our create controller, we'll use add. 00:29:22.320 |
Let's see how that looks now. There we go, update to do and update button. So finally, we need our, 00:29:33.520 |
what's the button going to actually do when we click on it? It's going to call something 00:29:36.960 |
called save in our controller, which means in our edit controller, we need a save function. 00:29:45.520 |
Now, previously, our save function called todo.save. What does todo.save do? 00:29:55.600 |
Well, because todo is just the return of a $resource function, we can see in $resource 00:30:04.000 |
what that does. $resource, if we scroll down, tells us that save is going to call the post 00:30:15.040 |
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. 00:30:27.600 |
In our API, post is the thing that actually creates a new todo. But in this case, we want to use 00:30:41.600 |
put because put is the thing which saves an existing todo. And we get passed in the ID 00:30:51.760 |
and the todo to save. So how do we call put? Interestingly enough, there is no put in the 00:31:00.240 |
default list of stuff that $resource provides. And that's why we've actually added an additional 00:31:09.280 |
thing here, which we've passed to our $resource call. And this is a list of any additional 00:31:15.200 |
methods we want to add and what HTTP verb they correspond to. So we now have added something 00:31:21.760 |
called update. So that means that we can call todo.update. And so first of all, we need the 00:31:34.640 |
ID that we want to update. And then we need the item that we want to update it with. 00:31:45.840 |
So that should be enough to get that working. The only other thing we want to do is that if 00:31:50.960 |
it's successful, we want to go back to our list of todos where we can see the new one being added 00:32:00.720 |
to that list. So we know how to do that already. Remember, it was just $location.path. 00:32:05.680 |
And where do you want to go? We want to go to the 00:32:12.320 |
root, the list controller and the list template. So let's see how we're going now. 00:32:20.960 |
So we'll refresh this. And let's update this priority one 00:32:27.840 |
todo number nine and change it to priority zero update. And we can see, yes, indeed, 00:32:34.400 |
there is todo nine is now priority zero. And you can see that it's automatically 00:32:39.280 |
sorted this appropriately for us. This is one of the nice things about using an MVC framework 00:32:45.680 |
is you don't have to worry too much about the particular order that things happen. 00:32:49.280 |
You can just describe how you want things bound, what data you want bound to what HTML 00:32:54.800 |
elements. And it handles all this kind of dependency stuff automatically for you. 00:32:59.760 |
You can see here how easy it was for us to add our create, 00:33:04.720 |
update and delete functionality to our application. In general, when you're writing an 00:33:12.880 |
Angular application, if you find yourself using kind of an unloaded method or manually updating 00:33:20.960 |
the view from the controller or vice versa, that generally suggests that you're doing something 00:33:26.800 |
in a way that's not the way Angular is designed. When you're using Angular as designed, really, 00:33:31.760 |
you should be doing everything pretty declaratively as we have been here. 00:33:35.760 |
Angular attempts to make it easy for you to do everything in that way. 00:33:40.880 |
However, in order to do so, it does require becoming familiar with 00:33:48.560 |
all of the stuff that Angular provides for you. And it is quite a substantial API, as you can see. 00:33:55.680 |
But all of these things are there to make your life easier. So as you learn more and more 00:34:00.080 |
of the system, it becomes easier and easier. So now that we have a complete working 00:34:07.360 |
CRUD application where we can sort, search, paginate, create, delete, update and view, 00:34:15.760 |
we're now at a point, I think, where we should be thinking about getting this into a version 00:34:19.840 |
control system so that we can keep track of the changes that we make. And we should be trying to 00:34:25.760 |
get it up on the web so other people can use it. So those are the two things that we'll be looking 00:34:30.240 |
at in our next tutorial. Thanks very much for keeping with me this far.