Nesting Reactive Forms in Angular tutorial

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
oh hey welcome back it's time for another angular tutorial so in today's video we're going to be building this lovely purple contact list it allows us to dynamically add or remove up to three contacts and then submit now to achieve this we're going to be learning about nesting reactive forms we're going to be getting those forms to talk to each other from different components we're also going to be using form control getters then we'll be sprinkling just a little bit of form validation on for good luck so open up your laptops spin up your favorite text editor and let's get right in some codes so before we get started let's have a little look around our project as always there's a link to the github repository down below in the description check that out and you can follow along with me if we have a little look i've got some markup in the template file here and i've also already added all of the styles that we'll need for this project just because i don't want to focus too much on that i want to look more at the actual business logic um if we jump into the ts file you'll see there's absolutely nothing in here this is just a default file that was created when i created the component if we switch across to chrome now we can see that the form looks quite different to in a demo that i showed at the beginning we've got errors always visible on screen our delete button is visible and also doesn't work also the add another contact button is visible but also not working so let's jump back to our project and let's have a little look at the markup we've got this form element at the top and then if we go down here we've got a list with only one item in it at the moment and that item is this contact form row that we can see here and if i scroll down it's only one item in this list then we've got the add another contact button and the submit button now first things first because we've got all of our form row logic here in this parent component what i actually want to do is i want to take all of this i want to cut it and then we're going to create a child component in here um so i'm actually going to use the angular cli for this so ng generate new component and then use a contact uh we can call this um component whatever we want i just like to make it clear that it's a child of this form so if i run that it will generate me a new component in here and then we're going to go in here and we are going to paste the logic that we just cut from the parent form save this i need to neaten this up a little bit i don't know why it's broken my indent in here we save this we switch across we can now see that our form row is no longer in our parent form that's all right what we want to do is we want to jump back to our parent component and before we forget we want to add our component back in here so that'll be app user contact we save that we switch back and it's back in here now the reason that we want to have this in a child component is we want to be able to dynamically add these and so it's much cleaner just for us to chuck that in a child component and rather than duplicate logic and markup it's all neatly tucked away there so the next thing that we need to do is we need to create our parent form so if we go into our parent component and then we'll create a new method in here we're going to call that generate user contact form it's going to take no parameters it's going to return nothing then we're going to call it in our ng init just to make sure that the form is created when the component is created so we need to give this form a variable so we're going to declare it up here and we're just going to call it user contact form and that is of type form group sometimes people like to be implicit with their type declarations but for me i've always lent towards being explicit so that looking at my file i know exactly what type everything is and it leaves a little bit less guesswork for my text editor so we want to declare in here our form so user contact form equals and then this is going to be a new form group we pass an object into here then we need to add our contacts to here and this is actually going to be a form array the former a does exactly what it sounds like it allows us to have an array of abstract controls which can be a form control or a child form or whatever now before we go any further i want to allow us to be able to debug a little bit easier so what i'm going to do is i'm going to create a submit method so if i go submit user contacts form and all we're going to do in here is we are going to use the dreaded console log and log out our form we could just as easily use the built-in javascript debugger or any debug tool but for the purposes of this tutorial i want to be able to hit submit on the form and see what the form actually contains we're going to do dot value here just so that we can see the actual values in the form and not just the form now let's jump back into our template file what we want to do is we want to declare the form that we've just created in here so if we go form group and we pass it the form that we've just created and then i also want to add in that submit method we just created so when the form is submitted we call that method let's give ourselves a little line break under here we save that and if we jump back to here when we hit submit we get this little console log out and it actually says that the array is empty now you might be asking yourself how comes the array is empty we've got a form row in here but we actually haven't wired up this child component we've just added it to the template so the next thing we want to do if we jump back to our project here is we want to look at our child component this user contact component that we just literally created now we're not going to use um on init so we can remove that out of here and because i just created this using the cli it's actually indented with spaces not a big fan of that now first off you might be thinking hey we need to create a method here that generates the child form in the same way that we generated the parent form and i can't fault you for thinking that but what we're going to do that's slightly different is we're going to create a method in here that creates the form that can be called in the parent component there's a few different ways that you can do this but the way that i like to do it is by creating a static method now if you're not familiar with what static methods are it's a method that can be called from any other component without actually having to have this component as a dependency so i'm gonna call this method add user contact and then i'm gonna say item now i quite like the word item i feel like it implies that this is one in a list of many but once again you can call this method whatever you want now what we're gonna be returning from this method is a form group and as you can see i get my little red underline and that's because we're not returning anything yet so if i go return new form group we give it an object again and then we need to tell it what fields are in this form so if i look in the template file again we can see there's two fields we've got a name field and we've got an email field so if we jump back into here so first off we've got our name field this is going to be a new form control and at the moment we're just going to pass it a blank string because we don't have the initial state for it then we comma a new line and we want to go with our second field which is an email field once again it is a form control that we're adding and we're giving it a blank string as its initial form state so now that we have this static method if we jump back to our parent component and we go down to here we can actually call it and we call it by using dot notation so we go user contact component this is the new child component we've just created then dot and then add user contact item now if we save this we switch across to our page in chrome now if we hit the submit and we look in here we have indeed got one item inside our array but things still aren't working how you'd expect because if i add my name in here and then i clear this and submit again we open this up it doesn't actually have my name in here this might be a little bit confusing but the reason for this is because if we switch back to here and we look at our child component we actually haven't created a form in here which means that if we look in the template of our child component which you don't have a child form that all of these inputs are linked to so what i'm going to do is i'm going to create a form in here we don't need this i'm going to take my closing tag tab all this in and add my closing tag to the end so this is now in a form we need to create a form in our child component let's put that there to make it clearer and so we go we want a public variable so we can use this in the template file and for now i'm going to call this child form now i want to make it clear that usually i wouldn't use naming conventions like child form or form but because we're going to be jumping backs and forwards between the parent and the child component here quite quickly i want to be very clear what form we're dealing with and when so this is of type form group and we can now take the name child form we can jump back into the template like we did in the parent form component i'm going to add a new line here and i'm going to go square bracket form group and i'm going to make that equal child form one other little thing i'm going to do here is i'm going to set auto complete to off just so that when we're testing this form it doesn't go through and try and pre-populate with my email address now because we've declared our form here it means that the inputs within here are assumed to be part of that form and so all we have to do is we have to go form control name and we have to give it the same name that we used when we were declaring the form same thing here this is the email input we have form control name and we go email this works a similar way to how the name attribute would work with a normal html form if we save this and we jump back to our form we see that we've got ourselves a little console error if i make that a bit bigger what it's basically telling us is that we're trying to tell it that we've got form controls there but our form actually hasn't been declared and that's because in this child component it hasn't we've obviously declared the form fields in our template but there's nothing in here that sets what this child form is so this next step might seem a little bit higgledy-piggledy but bear with me it'll make sense what we're going to do is we're going to give this an input decorator make sure that was imported at the top we can delete the on init because i removed that earlier on now what this allows us to do is to pass through the instance of this form from our parent component and what i mean by that is if we jump across to our parent component remember we've got this form array now if we call this static method twice we're telling angular that we want two of these child forms inside this form array but if we look at our template we've actually got this disconnect because there's nothing mapping this child component up with each of those child forms that we just created so what we can do here is we can use an ng4 loop and we can say let child form of and then add our parent form in here then we want to tell it to get our contacts which is our form array we were just looking at and then we want to loop through the controls in there now we take that input that we just created we go child form and we pass it the child form that we just declared here now what this is saying is that we're going to loop through each of those and pass each one into our child form as this input and then the template in this child component uses that child form to generate these inputs so in short if we jump across here we now don't have a console error and if i add some names here and then i submit and we look in here we now have two items the first one's name is ryan and the second one's name is tom if i zoom in here we can see that a lot clearer let's jump back to our codes now this would be perfectly fine but there's actually a little cleanup bit that i like to do because in our next steps we're actually going to be accessing this contacts array a lot rather than keep using this get method what i'm going to do is i'm going to come in here i'm going to create a getter and i'm going to call it contacts array so type form array and then i'm going to tell it to return this stop user contact form dot get contacts and then i'm going to use optional training which is available in angular 10 to null check this form now we still have this red underline and it's because we're telling it we're returning a form array when really it's an abstract control now very rarely do i say that we should cast things but in this case so that intellisense gives us a lot more freedom we'll tell it that this abstract control is actually a form array i'm actually going to change the name of this to contact array because i don't like to pluralize my variables and then we're going to jump back into the template and instead of doing this here gonna use our getter and you'll see that webstorm's no longer kicking up a fuss about this because it knows it's not an abstract control it's actually a form array we save this and we switch across everything should still be fine we fill in our names here we submit and this is still working now next up we want to be able to allow the user to dynamically add another contact row this is really really simple if we jump into the parent component again we're going to add another method here and we're going to call this one add user contact item it's going to return nothing then all we're going to do is we're going to use our getter that we just created here so we go this dot contact array we're going to tell it to push and then we're going to take our static method that we're using above here and we're going to pass that in there now we need to call this method from our add another contact button that we've got in the template so if we scroll down here we're going to give this a click event and we're going to call our new method from here if i save this and then i switch across we've only got one contact in here because we removed the second static method from where we generated our form but now if we click this button it adds new form rows and once again if we fill this in and we submit the third item in this array should now have the name of tom awesome so now that we can add contacts the next step we want to do is to be able to delete them so if we jump across here and we find our delete button it's actually in the child component at the bottom here now from this child component if we look in the ts file here we don't actually have access to the parent form and so what we're going to need to do similar to our input we're going to need to use an output we're going to call this delete contact event and this is of type event emitter so we'll import that now event emitter is a generic type which means we need to tell it what type of value we are emitting now in our case we need to delete something from an array which means we need to know its index so its index is going to be of type number and we are going to make this a new event emitter now we need to create a method in here to actually fire that event emitter we're going to call this method delete contact and we're going to pass it an index and we've already said that's a number now all we want to do in here is we want to grab our event emitter we want to next it which means fire it with a value and the value we're going to fire it with is our index now because we've used this output decorator it means that we can hook into this event wherever this component is called so if we jump over to our parent component again and we go to our for loop underneath where we use our input we can use our output here as well but we need to tell it what to do when this event fires so if we go into our parent ts file here and we're going to create another method this one's going to be called delete contact once again we pass in an index which is of type number remember we're emitting that from the child component now if we use our getter here which remember gives us access to our contacts array we can use a method that's built into angular reactive forms arrays and that is remove at and then we just pass it our index now on the off chance that this form doesn't exist when i try to remove an item from it i'm going to use optional chaining i better do that here again in this case it doesn't really matter because we know the form is always going to be there but it's good practice now if i take this method name and i pass it to our event and then i tell it that it's got an event this is our index that is being passed back from our event emitter and let's not forget to go through to our child component find the delete button and call our delete contact method now we've got a little issue here this method is expecting us to pass an index which is completely correct but our child component is what we call a dumb component it doesn't really have any knowledge of anything outside itself so what we need to do is we need to once again pass in a value and this time it's array index and this is going to be of type number we want to add this to here we then want to jump across to our parent component template and we're going to need to pass in the index here so it's array index and there's a built-in way that we can get an index here if we go up to the ng4 and we go let i equal index and then here we just pass i in we save this we can jump across to here and now when we add these we can also remove them which is awesome and now that we know the index we can make use of this title a little bit more if we go back into the child component markup we can change this it's actually a typo it should be contact and we want to use the array index plus one because we don't want contact zero we want contact one two three now if we jump across to here and it says contact one and then contact two contact three i've just realized my styling is a bit strange there let's take a little look at why that is ah it's because i've chucked my ng4 on the child component rather than on the list item so if we save that and we jump across should fix our styling a little bit here there we go now we've got line dividers between each awesome so next up now that we've got our form adding and removing contacts we want to add some logic that tells it when to show the add and remove buttons now in my mind you shouldn't be able to add another contact unless the contact information you've added is valid so we need to add some validation to our form if we jump back across to here we go to our child component here and we look at where we're generating our form we don't currently have any validation at all so if we add a new line here when i add a comma on here we want to add an object and then we can go validators this takes an array and for now the only validation i want to add is required now obviously in real life we'd probably want to validate to make sure that the email's in the right format and that we don't add too many characters and such but for now i think this is going to do for our validation and now that we have validation if we jump through to our template we can actually say that we only want these errors to show when that validation is triggered so ng if then we can go child form dot get name dot has error and the error will be just required once again it's perfectly valid to do this but i personally like to use getters so if we cut this out here we want to create ourselves a little getter get name field subtype form control and then we want to return this and once again we cast it to tell it's a form control now we know we're going to need the exact same thing for the email in a minute so i'm going to go here and change this to email fields and email now if we jump back to our template i can go name field then we just copy this ng if and chuck it down here with this error as well obviously making sure to change this to email field now this is great but if we jump across you'll see that our errors are always visible until you type i don't really like that i only want the error to show up once the field is dirty so we can jump back across here and we can make this a little bit more specific so we can say name field also has to be dirty let's add that on a new line and let's do the same thing here email field has to be dirty coolio now if we switch back across here and wait for it to refresh now nothing shows up until i type in it that's fine and then when i remove they know the field is now dirty and so it shows the errors and the same thing with the email field it's now dirty so it shows the error now that we have some validation we can tell it to only show this button when this form is valid now this is also really really simple if we jump into our parent component template and we go find our button which is here what we're going to use is we're going to use the disabled attribute and we tell it to be disabled if our contact array is not valid it's as simple as that because that validity will bubble up to the top so at the moment this is disabled the minute that i fill these out it's no longer disabled now on a similar note this form is useless without any contacts so i probably don't want to show this button unless there's more than one contact in the list if we jump across to our components again we're going to go to our child component we're going to be passing in another value and that's going to be total contacts and that's a number we're going to use that total contacts we're going to go look for the delete button and in this case instead of disabling i just want to hide it so i'm going to go ng if and i'll say if total contacts equals one now we need to pass this in so we go up to our parent level again we're gonna go total contacts here and we're gonna pass the child form array dot length i'm gonna save that we're going to jump back across to here our button is still visible let's have a look to see what we've done ah just as i thought let's actually change this to if it's more than one item then we show the button so currently there's only one item if i fill these in and add another one we now have delete buttons for both but once again once we remove one we don't have the delete buttons anymore now at the moment we can't add other contacts because currently the form is not valid but we can still submit it and there's no real use to submit in this form if the form is not valid so because reactive forms has our back we can actually do this really easily if we jump back across to the parent component template we scroll down to submit button we want to use our disabled attribute again and we want to say that when the contacts form is not valid submit is disabled if we switch back across to here wait for this to refresh and there you go my submit button is disabled when i fill in these fields i can then submit but when i add another contact once again it's not valid anymore and i can't submit now as a final little step in my form that i demoed at the beginning you could only add a maximum number of three contacts but you can see here that i can just add as many as i want there's a couple of ways that we can fix this what i could do is i could jump across to the parent form and we could use a validator on here and we could control it that way but for me i'm going to opt for a really really simple way what i'm going to do is i'm going to add a span down here that basically says 3 is the maximum number of contacts you can add at this time and then what i'm going to do is i'm going to add an ngf on here and say that if the contact array.length is less than three then we're going to show this button else max contacts else we're going to take this template variable name i'm going to use nge template here we're going to slot this inside and we're going to give it its template variable name we save this and we jump back to here we should now be able to add our three items but once we've met our three items we then got our message at the bottom maybe add a little bit of styling just to make it look a little bit nicer and with that that's about everything that i wanted to cover today if you like this video please give it a thumbs up and subscribe it really means a lot if there was anything that i went over a bit too quickly or you just didn't understand leave me a comment below and i'll get back to you within a day or so don't forget the repo is also in the description so you can check that out and follow along and other than that just take care of yourselves
Info
Channel: TheRyanSmee
Views: 4,712
Rating: undefined out of 5
Keywords: Angular, Reactive forms, Nested forms, Child form, Parent form, Tutorial
Id: DEuTcG8DxUI
Channel Id: undefined
Length: 24min 39sec (1479 seconds)
Published: Sun Oct 18 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.