2.3 Form Field Directive: The Directive
1.Getting Started7 lessons, 55:14
2.Core Features6 lessons, 1:04:12
3.Finishing Touches3 lessons, 42:11
4.Conclusion1 lesson, 02:02
2.3 Form Field Directive: The Directive
In this screencast, we're going to finish off creating the directive that we started in the previous screencast.This is the template that we created for our directive in the previous screencast. It's a little more complex than just an input element, but it will give us all of the functionality that we need for both our new contact form and dot edit contact form. So now that we have template, we have to go to our directives dot js file which we created in the last screencast. And in this file, we're going to actually create the brain if you will, behind our angular directive So we have to create a directive block here. And the meaning of our directive is going to be form field. Now this function will need to take two values, first is timeout, which is the angular way to use the set timeout function. You might recall that I mentioned in a previous screencast, we'd be using set timeout when we call this update method. And so we're going to need to require that. The other thing we're going to need to require is the field types. And this is this angular value that we just created right up above this. Now, an angular directive block like this returns an object with a bunch of properties that we're going to fill in now. The first value is restrict. And this is restricting it to the ways that we can allow this directive to be used. An angular directive can be used either as a new element, as an attribute on that element, as a class on that element, or as a comment. We're gonna restrict this to just being used as an element or an attribute. Now, you might recall in our new .html form, we're using it as an element here, but. Because we're allowing it to be used as an attribute, we could go ahead and create say a div with a form field attribute instead if we'd wanted to. The next and another very important property is the template url views slash form-field.html, and then we'll have replace, which we'll set to true. And what this means is that the html that we have here in form field will actually replace this attribute here. If we did not have replace, it would default to false and that would mean that this html would be placed inside this form field element. So it'd all go in there. Next, we need to add the scope property, which is actually an object of itself. And this is a very important property. This is where we choose what attributes that are placed on this element here are going to be available inside of the actual directive. Now, as you know, all four of these are important. Record, field, live, and required. We need to be able to access these from inside of our directive. But how do we get those values, and also what exactly are those values. Because if you think about it, contact here is reference to an object that we have at our controller, scope dot contact. However, first name here is just a string, it doesn't actually reference anything. We just need it as a string, so that we can get the value within contact and use it to create our label, and that type of thing. So, these are gonna have different type of scope bindings, and this is how we do this. First of all, we wanna have record. And record, we're just going to set with an equal sign. Now, this is important. What this means is that this is a two-way binding. So any changes that we make to the record object from within this directive are actually being made to this contact object out here, to the record object, or as it's called outside of this director, the contact object out here. So, that means when we update the first name attribute inside a directive it will update out here in this controller. That's very important. Now, for the other three, field, live and required, these don't actually have to update or change outside. We're just, need to be able to read these. And so, we'll use a one way binding and this is, we'll have field. And we show this with the at sign. So we have field, we have live, and we also have required. And these are all just at signs, to show that we don't need to be able to save back to them. Now, the last value here is a link function. And this function takes this scope for this directive. It also takes the element for this directive. And, any attributes that were on this directive as well, although we're only gonna be using the scope in here. And basically, this link function is run during the creation of this directive and it allows us to modify exactly what goes on when the directive is created and replaces the content that is here. Now, we're only gonna be using this link function to add some other values to the scope here. So, for example, the field types that we have up here, these need to be available within the directive template, right? So that we can create our messages down below. So, that means we need to add them to the scope. So, we'll scope and you know what? I'm gonna change this to dollar sign scope so that it feels more like working with inside a controller cuz that's kind of what we're doing here. We're using this link function like a controller inside this directive. So we can say Scope.types = FieldTypes and, of course, that's available because we imported it right here. Now let's look back here. We also need a remove method, right? Because we want to allow our users to remove fields that are not required. So, let's go ahead and do scope.remove, and this takes the field name as a property. And we can go ahead and say $scope.record, which references this right up here, and then we can say square bracket field. And actually, what we want to do is use the delete in front of it there. And then, we're also going to do dollar sign scope dot blur update, and we're going to write that function next, because blur update is the function that actually performs the update if our directive is not a live directive. So, what we can do is, say if dollar sign scope dot live is not equal to false. Now, unfortunately, we have to say is it not equal to false because this is a string. It's not actually a boolean value, so we have to say scope dot live is not equal to false. We'll assume it's then supposed to be true and we can go ahead and proceed with the updates. We'll do dollar sign scope dot record. Remember this is the contact value and we can say $update and we'll take the call back function here and when we call the update function. What this does is send the send our record back to the server and update it and we'll get the updated value back, so we can have our updated record in return. And we'll just go ahead and reassign that so we have scope.record = updatedRecord there we go. So this is how we will actually go ahead and perform our update. This blur update function will also be called with the ng blur directive on our output element here. So the last thing we have to write is the update element which will be called whenever the input element is changed. And so let's come down here. I'll write that down here. First of all need to create a save timeout variable here which will be used amongst multiple columns of this function. So, we'll say scope.update. So we're going to begin by doing $timeout which you might recall the at the beginning of our directive here. This is how we can keep track of the timing methods with an angular, so we'll say timeout.cancel and we're cancelling save.timeout and you'll see why in a second. Because right after this, we're gonna set savetimeout equal to $timeout and we're gonna pass it our $scope.blurt update and one thousand for one second. This function will be called every time our user types a character into a field. So, if you think about it, if they're writing their name, they're going to write A and then right away this will be called, and a timeout will be set, and in one second, the timeout will be called. But then, they go ahead and write N, and then this function is called again, so that the first time it is cancelled, and a new one is called for one second after the N was typed. And so, that way, if our user goes, when a user types their name, so, for example, Andrew, they're going to type each letter probably less than a second between each letter. And so, once they finish those, they'll stop typing, the second will happen, and then, the update will occur. All right, well believe it or not, that is all we need to do for our directive. So, we have written the entire template that our directive requires. We have written the code behind our directive. We have put our directive into our form field here. There's one last step that we actually need to do, and that is go to public/main and we need to add our directives file here. So, I'll copy this bottom one here and change this to src/directives. So, now, we can go local host/contact/new and you can see we have our first name element showing up here just fine. That's great. And so, we should actually be able to see our validation in practice if we go ahead and begin to type my name, and then if I remove it all notice it turns red and we have it in here first name is required but then if I start to type again. The validation error is removed. Now, actually, now that I think about it, one thing we forgot to do. If I refresh the page here and if I click create contact, notice nothing happens. If I open up the console here, now you can see we have no errors, we can create a contact and nothing's going on. Now you might recall if we look back at our code here in our controllers. The problem is new contact is invalid because first name is required, but there's nothing being filled in. And so, we're broadcasting this record invalid message. But we forgot over here in our directive that we have to listen for that and mark our element as invalid. So back in our link function here, let's do this right at the top. We'll say $scope.$on and we're listening for the record invalid function. When that happens, then what we need to do is set our $scope square bracket $scope field. And this here refers to this ng-form attribute, or the fake form element that we're creating inside of this directive. And we wanna go ahead and do .$setDirty. It will mark our first name field here as dirty, and since its dirty, it's. It's already invalid because it's empty. But since the user didn't try to type anything in, it's considered pristine and not dirty. And so, we'll set it to dirty and that will mark it so that the user can then see the problem. So if we refresh the page now, and I go ahead and click create contact, notice that first name is required. However, if I type in the name, the error disappears. Okay. So, let's go ahead and add the rest of our attributes here. Let's go back to our new template here. I'm going to go ahead and copy this again and we're going to have this for last name, and last name is going to be exactly the same because last name is also required. The last thing we want to do is do form-field for record contact. But actually, before record here, let me do ng-repeat. And we wanna repeat this for every key and value in record, because remember, record comes with all of these valid keys and values, and we want to actually show these. So, and actually, that should not be record. That should be contact because we are now outside of this form field directive code. For every key and value in contact, we'll set the record to contact, we will set the field to the key. And there we go. So, now if we come back and refresh this page we should see exactly what we want. Now, notice two things here. First of all, all of these values here are not required. Only first name and last name have the required attribute, and so they do not have our x button at the end. So notice how our ng-switch attribute is coming in handy here. If it's required, we only show the input element, first name and last name here. But if it is not required, we show our, this input group here which is both the input element and this button at the end. Now, there's one more thing to take note of here and that is that first name and last name are showing up twice here. And actually, they are linked up. So, if I write Andrew up here, notice that Andrew is written down here. And if I go ahead and edit it down here, it's being edited up there. So that's good that it's being changed in both places at once. But even better would be first name and last name not showing up in this field down here. And so, what we have to do is where we loop over our contact here, we have to filter out first name and last name. Now unfortunately, Angular does not have a built in way for us to filter objects by their keys. However, this will not be a hard filter to write. So let's open up our public source filter.js file, and let's write another filter here. We're gonna call this filter we'll call it key filter. And the way this works, as you know, we return a function here. And this function is gonna take an object and a query. And, the way we're going to use this is we're gonna say, contact, pipe it through key filter, and the key filter will take the query, for example, first name. Right? And so firstName here is that query, but the object is contact, the one that we're piping into the keyFilter. And so, basically, what we to do is return a new object that does not have the key that was set here. What we can do is say var our result = and empty object. And then, we can do angular.forEach. And we'll loop over the object and function when passing can take, it takes the value first and then it takes a key. And so, we can say if the key does not equal the query then we go ahead and assign it to result. So, we say result[key] equals the value. And so, we'll loop over like that. And then, at the bottom here, we just return the result. In here, we can go ahead and say key filter by first name, and then, let's go ahead and do that again. Key filter for last name. So, we're filtering for both first name and last name. And now, if we come back to our form, and refresh it, you can see that first name and last name at the top, but none of those values are beneath. Let's make sure our remove buttons are moving. If I click it on Address, you can see it disappears. That's just fine. Let's go ahead and create an actual contact here. I'll say John Doe and we'll give him an email of email@example.com. Notice the validation working as we type. And now, let's go ahead and click Create contact. Excellent. So that contact was created. We were sent back to our /contacts page. And notice right here we have John Doe, a new contact. So, there you go. We have successfully created the new contact form.