Creating custom form controls in Angular (Control Value Accessors)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey welcome back to my channel in today's video we're going to look at minimizing boilerplate markup in angular forms by implementing custom form controls so open up your laptop spin up your favorite text editor and let's get writing some code now before we get started i want to take a minute just to familiarize ourselves with the project we're using today as always if you want to follow along there's a link in the description below to the github repository that i'm using feel free to check that out and follow along so if we look across in the browser i've got this project running we've got a really simple form here with four inputs three of them are required and one of them is optional the first two are just simple text inputs they've got a little bit of validation a bit of error handling on them if i go to the email one it tells me that i'm not typing an email in the correct format and if i clear the box it tells me that it's a required field with our third field it's slightly more complex i'll start typing here it's a password field it's got similar validation to above and it's also got this little show and hide button once all the required fields are filled in i can submit the form once i submit it i get a little console log of the form values and then obviously this last field is just a little bit of fun here that allows us to type in our favorite hex code and it gives us a little preview of what that color would be so now if we jump back to our project at the moment everything that's happening is all happening in this register form component it's super simple we've got a method in here that basically generates our form for us we've got a method so that when we submit our form it outputs this console log with the form values and then finally we've got this little toggle password visibility method to show and hide our passwords now if we scroll up and we open up the template file we start to see the issue that we're trying to solve here now each of these form rows is one of our form inputs from here what you can see as we scroll down is there's a lot of duplication so we've got this label that's got our errors in here we've got this input that's got our has error class on it and then when we scroll down to email it's almost identical except for we've just got one slightly different error that we're handling here then when we scroll down to our password once again it's pretty similar there's a few differences obviously we've got this show hide button which is meant that we've got another wrapper div here but other than that it's pretty similar then scrolling down to our favorite color input once again is more the same now this file is 130 lines which in the grand scheme of it for a form isn't really that ludicrous but let's remember we've got four inputs here this is a very very small form if we decided that we wanted to have a few more inputs let's say we wanted instead of a name input we wanted a first name and a last name and instead of our password we want a password one and a password too so we can check them against each other this file starts to grow exponentially and most of the code in there is just repeated it's just copied and pasted with a few little values changed so to start off with we're going to create a component that encapsulates our standard text input so first of all i'm going to comment this out and then i'm going to create our new component so i've actually got a folder here you can put this wherever you want in the project but i tend to build little form component libraries so i end up with quite a few of these inputs so i like to group them all together here so i'm going to open a terminal here and we're just going to use the angular cli if i go ng generate now this project is angular 10 and i know that going forward with angular 10 we're going to be relying less and less on modules but if i'm honest i actually haven't taken the time out to look into all that stuff yet so i'm going to be a bit slap dash with modules here as i always have been so we create that and then i want to do the same but i want to create a component there as well if i click here the ide will update and show us our new module and then i'm going to open up my new files here and put these in a better order so first things first i'm going to change this to using tabs not a big fan of spaces and i'm going to remove ng on init because we're not going to be using that at all in here so we've got ourselves a little empty component that we actually need to import into our registration form here at input add that in and then we obviously have to import that into our module if we save this and then we switch across we see that we got our input works message here so we know our component's been added successfully we're going to jump back across and we're going to start adding our markup into our input template file now there's quite a lot of stuff going on with our text input with our validation and error handling and there's a lot of styling in there we don't want to get bogged down with that at first so to start off with i'm going to add my wrapper div that has the class form row and then inside of that we're going to add our input that has the class of form element if i save that and then we switch across that looks more like it we've now got all of our inputs here and now to save us some time from filling in this form every time i'm actually going to jump back across go into our registration form and i'm going to give us some default values in our form to get started with so i'll add my name in here add an email address add a password and then add just any old hex code in here right we save that we swap back across here all of our inputs are filled in with the exception of our new one and this makes a lot of sense because we actually haven't told our form yet that our new component is part of our form so let's go back into our registration form template file i'm going to close the rest of these menu so that we've got a bit more space here and then i'm just going to add our form control name name that's the same way that we've told angular that all of our other inputs are part of our form as well so if i save that and we switch across we're going to get our first error now the error says no value accessor for the form control with the name name now what this error is telling us is that angular is expecting because we've put form control name on an element that's going to be one of its known html form inputs but obviously it's not because we've just created this component so what we need to do is we need to register our new component with the angular form api and by doing this we bridge our html element with the angular forms api so if we switch across to our new input component we go here i'm going to type first and then i'll explain afterwards so what we're doing here is we're registering a new ng value accessor and as i said a minute ago that's basically what angular uses as a bridge between our html element and the api out of the box angular already has these for a normal input or radio buttons or any other html form element now because this gets registered before our component is created we have to tell it what component we're going to be registering and that's what i've put here and then this multi-bit you don't really have to worry too much about that we're basically just saying that this isn't the only value access so this is just part of a group of value accesses now i don't expect you to remember all this i certainly don't i forget this more often than not but that's something you don't really have to worry about once you add one of these to your project every time you want to create a new custom form control it's basically just copying and pasting with this anyway so if we save that and then we switch back to the browser we've got ourselves our second error slowly but surely we're getting there so this time it's saying that our value accessor doesn't have a method called right value and this is our first indication that there are certain methods that are required in a component for angular to accept it as a value accessor now i could just add this one method in here switch across see our next error and keep doing that until we've sorted it or what i could do is i can go implements control value accessor now what this is is an interface that angular has created for us if you don't know anything about interfaces basically what they are is a blueprint for what your class should look like and in this case we can see from our red underline this interface or blueprint requires a certain amount of methods to be added to be valid now i've got a smart ide which means that if i click here and i press alt or option and enter it gives me the option to just automatically add these methods in i get a list here of the methods and i click ok and it adds them now i know this works out of the box with vs code i'm using webstorm here i think it works with most ides but don't worry if your text editor doesn't do this you've got a couple of options you can either go to the angular documentation where they list out all the methods you need you can command or control click on the interface and it will take you through and tell you kind of all of the methods and what they're used for or you can just pause the video for a second and take a note of the methods that i've got here so first thing that i'm going to do i'm going to take this right method and i'm going to add it to the top i think it makes a bit more logical sense coming first and basically this has two types of triggers that means that it gets called it's first called when the form is initialized and then going forward it's called every time that set value or patch value is called on this form control in simple this is fired whenever our value changes so next up we've got register on change and this will return a function that gets called whenever our value is changed in the ui and so for me the easiest way to think about this is as the angular api passing us through a method from our control up in our form that we can use to fire the angular change detection next up we have our register on touch it's very very similar to on change the only difference is this isn't to do with our value changing it's actually to do with our control losing focus so on an input that would be blur when that action is triggered it then updates our form to say hey this input's been touched we actually leverage this to only show our validation errors once a user has focused and then blurred a field and then finally this one's pretty simple to understand this is basically a way for our input to be disabled when either the form is disabled or our form control is disabled so before we get adding the logic for these i need to add a few variables at the top first of all i want to add a public value we're going to say that this is a type string for now to keep things simple we're just going to make our input a text input but we could do some clever stuff down the line to make it also a number input if we wanted to then as i mentioned before i'm going to want to alias my on change and on touch methods so i'm going to create changed list of type method that takes a value which we've said above is of type string and it actually returns nothing and then we want to do similar for our touched methods although this one doesn't take a value but still returns voids and then finally we want to have an is disabled boolean which we're going to use to set our input to disabled or enabled so all we want to do with our right value method let's make this more specific and it's a type of string because we want to map our value that's been returned from this method to our value variable then as i said before we just want to alias this returned method and we want to do the same here and then finally we just want to say this dot is disabled equals is disabled now that there in 40 odd lines is all we need in the ts file to get our new custom form control up and running now we have to jump across to our template and we need to implement these new methods that we've created so first of all we're literally just going to use the value attribute and we're going to set that to value so basically whenever our value in our ts file is set it updates our input now i see people do this in lots and lots of different ways i've seen people use an ng model here i've seen people use forms in their ts files and lots of other things but i find this is a simple way that works well next up we need to implement our touched and our changed for our change we're going to leverage the input event and we know that we have to pass this a value so we go event target value then for our touched you might have guessed it we're going to use blur and that doesn't take a value and then finally for our is disabled we just use the disabled attribute so if i save that and i switch across we can see that our name value has been added from the form if i remove that we now can't submit so the validation is working and if i add it back in we can submit again then when i submit we have a look and the correct value is being passed out of the form so in its simplest terms that's everything we need to do to have a custom form field so now we're going to extend that and make it a lot richer like our form fields below so if we jump back here first things first this is annoying me a little bit now the reason that it's telling me that there's an error here is because although webstorm is very smart a lot of the time sometimes it's a bit dumb and all it knows is that i'm trying to pass an event in and of type event doesn't actually have target value now in an ideal world it would infer that it's of type html input element and then sort of know that it does have a value and if you're fine with this error ignore this next step but for me i'm not so i'm going to go through and change this so i'm going to create a new method here i'm going to call it on change we know that it takes an event of type event because that's what's given us our error in our template and we know it doesn't return anything because we set out our type up here now i'm going to create a new constant here and call it value and we're going to give it a type of string because we know what that's what our value from our input is going to be and then this bit is going to look a little bit gross so i'm going to tell it it's type html input element i'm going to say event dot target and then value now what we're doing here is we're basically telling typescript this is of type html input element so don't give me any errors and then obviously what we want to do is from there we want to fire our changed methods with our newly typed value here then we want to jump back into our template file we can mug that off and we can change this to on change now as i say that's not a necessary step and it does add a little bit of muddiness to our ts file but it means when i'm building this i don't get a false error muddying up my logs oh it's me again as i was editing this video i took issue with a little thing that i said there yes if you're doing nothing else with the value this step might seem a little bit unnecessary but i've actually demonstrated something you can do here say you have a number input and every three zeros you want to put a comma what i can do here is i can intercept the value between the dom and the form api i can do that formatting and then i can trigger change detection so just keep that in mind whenever you're doing anything slightly more clever than this with your inputs bye for now so if we save that and we switch back across just to make sure this is still working we can submit there's a value in here we remove the value we can't submit and if i put another name in here and we submit we check our form and it's outputting the correct value awesome so now what we want to do is we want to add the rest of the stuff back in we want to have our label and we want to have our errors and we want to have our styling when our field is erroring so if we go back to our registration form component and we have a little look here the first thing that we want to take is we want to take this label so we copy that we're going to jump back into our input template we're going to paste that in there comment it back in and sort out the formatting next thing we want to do is we want to jump back across here and have a look and we actually need this error class we can just slot that in there comment back in fix formatting and then finally we've got a few attributes here to make everything a bit more semantic so let's slot those on here now straight away we can see some issues here we're referencing this name field if we look in our ts file for the registration form and we scroll up we can see that name field is just a getter to kind of give us a shorthand of getting our name control from the form so that tells us instantly that what we actually need to do is we need to let our input component know about our form so what i'm going to do is i'm going to add an input in here we're going to call this parent form and that is of type form group we need to import this and then it's no good just having the parent form we actually need to let our form know what control it is so if we add another input in here and we're going to call this field name this is just going to be a string and then now that we've got that information we can do exactly what we've done in the other file we can create ourselves a little getter in here we'll call it form field it is of type form control then we need to return this start parent form get then we use our form fields and if you watch my other videos you know that we need to cast this as a form control because this get method just says that it's returning an abstract control whereas we need to be more specific and say that it's a form control now if we jump into our template file we can change all of these to form field i'm actually going to copy that and then just paste that over all of these so if i paste that and then hit save now we have one more thing we have to do we actually need to pass this information in from our registration form so parent form is just registration form and field name is just name we save that and we switch across to our form we can now see if we remove this we've got our validation and our styling working there's just a couple more things that we need to do if we jump back to our input template and we have a look we can actually see that the name is hard coded and so are all these values here now with our name in our id we can just use our field name because we know that they're going to be unique and then i just need to fix the formatting for both of those and we can remove this we can do the same up here for the four and change that to field name now we could do similar with our label text and if i was using a translation library that would probably be fine but the issue is if i put that in there and save it it's completely unformatted if my label has spaces in it it's probably not going to have the same spaces and formatting in the field name so what we need to do is we need to pass in one more value so if i go input public and we're just going to pass in a label and we're going to jump back to here and change this to label then in our registration form component we are going to pass in our label which is name with a capital letter we switch back across everything looks great now obviously there's some other stuff we could do we could pass in a value for is required that's definitely what i'd do if this was a real world project for me with that aside i'd say that we're pretty much done with our first custom form control but i do have one more thought as mentioned earlier the errors here are very very similar on every control with some errors even being shared the only difference is is there's occasionally an extra error on each of these so what i'm going to do and once again this isn't strictly necessary i'm going to create a new component to handle all of our field errors so once again we are going to go into this folder we're going to open up a terminal i'm going to use the cli generate myself a module we're going to call this one field errors and then the same again but with a component i'm going to whiz through this because this one's really really simple we jump into our ts file i'm obviously going to make this tabbed we don't need any of this all we need to do is pass it in our form field so we're going to add an input again we're going to make this public and we're going to say form field and we know that this is of type form control i need to make sure that i import that get rid of that and now we're going to get our first errors from here we're going to add in our new component here import that into our component then pass our form field now let's move this here and let's take a look in our template i'm going to paste that in here we'll sort out the format in a minute we see there's something duplicated here every single one of our errors is checking to make sure if the form is dirty first so what we do here is we say ngcontainer and on that we can shove an if we'll say if our form field is dirty that means we can strip this off of each of those and we can plop those in here and as i say fix the format in a little bit but that's us away now if i save that and we switch back across we should still have our error handling here that's great and we're ready to jump onto our second form control let's close off this field errors component for now and jump into our registration form now we can obviously delete this out of here we don't need that anymore and looking at it we can see that this is almost identical to our other form control so what we can do is we can just take our input that we created before copy and paste it make a couple of minor changes to it and comment this out and actually the only thing that's different is we have an error here that is not already in our error file so if i jump back into our field errors and we add our new error in here let's comment that back in we can remove this dirty because we've already added that up the top here and then we can just change this to be looking at form field now if we save that and we switch back across here we don't have to make any other changes this is already working type in here we get the correct errors if i add my information back in here once the form is valid we submit and everything is working correctly now we can just switch back to our registration form here and we can already just delete this out that's done that's two form fields down now if we jump into our ts file up here we've got stuff that we're not going to be using i can delete both those out they're no longer used and this registration form file has already become a lot simpler now next up let's take what we've just learned and we'll apply it to this password field now looking at it we can already see it's slightly different to our input field so it's not going to work just to take our input component put that there and comment this out we're going to have to create a new input which makes a lot of sense a password field works quite differently to a normal input so once again we're going to use our cli i'm going to generate another module and we're going to call this one password input i'm going to do that and then we're going to do the same again with a component i'm going to click up here so the ide recognizer has been created is here so remember what i said about having to remember everything in this file and how most of it is copying and pasting well we're going to go ahead and do that right now i'm going to take our providers here we're going to jump back into our password file i'm going to chuck this in here obviously remembering to tell it that we're registering the password component here not our input component then i'm going to jump back to our ts file here i'm going to grab from implements all the way down to the bottom here i'm going to copy that and then i'm going to replace everything in here now if i save that i'm mostly good to go in our ts file i've now just got to take the markup from our registration form file so if i take all of this we're going to copy that we're going to close these out and we're going to look at our password template we can get rid of that for now and we're going to paste our template in here fix that format a little bit now first thing we need to do is we need to go through and make all the changes that we made in our input file first of all this is no longer password field but this is form filled and we're going to copy that across all of these paste that this is no longer just a hard-coded password this is label again for this we use our field name and the same for the name and the id cool let's fix this format in a little bit and actually i'm going to move that down now if you remember we have to add our value in here and then we obviously have to add in our event callbacks so we use input for our unchanged and remember we pass in the event and then we use blur for touched and let's not forget that we also need to handle disabled now we can see we still got some red bits of code here uh this one is just telling us that we haven't imported font awesome into our password module that's fine we're using that for the eye icon that we're using on the toggle button but we can also see here that we've got a variable that's being referenced and a method that's being referenced that we don't have in our password input that makes a lot of sense if we jump into our registration form component and scroll up here is the variable that we're missing we can now cut that it's not used in this anymore and we can paste it in here and then we need to grab the method to toggle that so if we jump back to here go down to the bottom we can cut that as well and into our password input we can paste this down the bottom here save that have a little look in here is that everything we need i think it is we need to obviously add our new input in here we go app password input all of this is the same and we need to import this into our module this can be password password with a capital p and password again we save this and we can switch across we've got our field here looks a little bit wrong but it seems to be working correctly i think this is just a style that we need to move into our new password component so if we jump back across let's have a look what style we're missing ah here it is yep if we take this toggle button style and we look into our password component and we can slot that in there if i save that and switch back everything's working correctly now if i update this value and go submit we can see that the password value has been updated we've got our validation in here and we've got our styling once again we've got duplicate validation code so what i'm going to do is jump back here we're going to close some of these files that we're not using anymore i'm going to have a look in my template file for the password we're going to grab the validation that doesn't already exist in our field error component we're going to add our field errors component in here and remember we have to import that into our module and then we have to pass through our form field if we jump through to this template we can then add our new error into here and we can remove this because we're doing this at the top once again if i save this and switch across everything should be working we've got our errors at the top here we've actually got two errors because i have left the other one in here let's get rid of that save that i obviously meant to do that i was just testing everyone if we remove that we've now got one error and we've got our error about spaces if i try to add spaces in here as well so what we can actually see here is in a matter of minutes we've gone from one custom form control to having two custom form controls that little bit of overhead we had creating our first one really pays us back going forwards and as i alluded to at the beginning i can now use our custom components to add form fields to this with almost zero effort let's go clear up some of this that we've left over let's delete that let's go into our ts file we no longer need this and as i said if i decided i wanted to add a check password in here i can add this i'm just going to change this to password 2 and we give it a new default value then i'm going to look in my template here and i can just take this paste that there change this to password two save it we go back to our form and we now have two independent password controls here that were added by literally just copying and pasting four lines of code and with that that's everything that i wanted to cover in today's video if you liked it please give it a thumbs up and if you're not already subscribed make sure to do so because i've got loads more videos coming your way if you have any questions leave them below and i'll make sure to get back to you as soon as possible and other than that just take care of yourselves
Info
Channel: TheRyanSmee
Views: 6,924
Rating: 4.9633026 out of 5
Keywords: Angular, Reactive forms, control value accessor, controlValueAccessor, tutorial, custom form control, ControlValueAccessor, Angular form API
Id: xTcJQaWiJ2c
Channel Id: undefined
Length: 29min 22sec (1762 seconds)
Published: Mon Nov 09 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.