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

Whisper Transcript | Transcript Only Page

00:00:00.000 | [Music]
00:00:02.000 | Welcome back to part three of this tutorial on creating a CRUD application using AngularJS.
00:00:09.840 | In parts one and two,
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:28.320 | and also to paginate 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:34.320 | In order to delete a to-do,
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:47.600 | So now that we've got the ID,
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:13.600 | So here we have
00:15:17.200 | the label for that. Let's just say to do. Let's create another one then.
00:15:27.760 | Which will be due date.
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:01.440 | So let's use that to save this new item.
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:12.240 | So let's now create our edit controller.
00:24:17.120 | So there we've got our edit controller.
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.