Reusable Input and Select component with Control Value Accessor in Angular

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hello and welcome to this video guys today we're going to look into control value accessor CVA which is a design pattern used in angular to create custom form controls it is a way to extend the functionality of existing form controls allowing the developers to create new controls that can meet their specific needs in angular a form control is a component that manages the state of an input element such as a text box or a checkbox a CVA provides a way to create a custom form control that can be used in the same way as the built-in form controls to implement the cvas in your design you can start by creating a new component that extends the base Clause control value accessor so that's what we're going to start off with the first thing we want to do is to generate a new component so let's go ahead and generate a new component let's call that component input component so angular generate component input all right so obviously I tried to create it in the source folder should be created in the app folder so let's go ahead and correct that real quick so that now that the created component is here we can just jump into the component so there's one thing we want to do we want to do Implement control value access all right and we can also implement the interface of it because we're going to need to set a couple of different things so once we have this there's a couple of things more we want to do here we want to have a provider so here we're going to make sure that we override the the provider that we have for control value accessor so NGE value accessor and here we can just form our graph so we're going to forward whatever comes from the um this is a part of this NG value accessor to the input component that we have so if we would now for instance add it on a template called up input form control it will be forwarded to to the next comp to this component here so this is exactly what the forward ref is doing in this case a couple of more things we want to do we want to add a couple of things we want to add the the control here we're going to say the control is going to be either front control or undefined we can we can just give it this type for now and then we are going to say is required we want to standardize the way we set the required so that we don't have to pass it into the input component every time we want to set that we also want to have the is disabled here set it to false we have a subscription here so destroy it's going to be subject void in this case uh let's see let's continue this story no destroy all right so we also want to have the private um touch which is going to be of just a callback function like this that will take the type D also we can say we're going to take the type T because we do not know if the form control is going to be of type string number and so on so we can use the generic type T in this case all right so the first thing we want to do is use the con the we want to inject the injector sounds pretty fun right so this will make it possible for us to essentially receive the value um we'll get to that shortly so once this is injected we can go ahead and create uh you can add the engine in it also we need to on in it we can import it so now that we have the engine underneath here we can say we want to fetch the form control and we also want to see if the phone control is has a validator so yeah control has validator if it has the required validator then we know for a fact that it is going to be let's see here required so we don't really need to pass in an input here defining whether it's required or not we can just listen to the validators that's connected to the input field to determine whether it's it is required or not so we'll do it in this way so that we manually don't have to do any work all right so um the the first thing we really want to do now we're only listening for the form control so there's a couple of more things we want to do so we can go ahead and add a try and here we're going to say const from control is going to just be what we have up here we can just pass this in let's see this way and we can change form control to NG control in this case all right this is the the default anyway so also we can say switch we're going to check whether the form control uh Constructor if this is a form control name that's being defined then we want to be a sorry then we want to be able to say that all right this dot control equals to this injector in and we're going to fetch it from the form group directive because you can only use the form control name alongside with the form group otherwise this won't really work and let's import the phone control name as well and we also want to fetch our current control all right so the form control here it's it's the one we're up have up here so as form control name all right so once this is done this is just for using the form control names but we're not done here really we want to have let's add the capture so that the error is gone um so we want to be able to continue we want to say all right so in as the default case will be we don't need to have any other case so it's that we're passing it in as a form control so form control as form control directive so if it's of of the phone control type we can essentially just fetch it as in this manner so we can access the form and we can say that the phone control we have the item the element or the actual form control can be received or fetched in this one right so now on the error if in case we would receive an error let's just make sure that the code won't crash so here we can say form control will Define a new form control there with the undefined value so in this case we're essentially setting the component control so we can just create a function here called set component set form control and this is the logic we can have so we'll just on in it here we'll call set from control to set the value alright since the control is is essentially going to be this value or undefined we need to just make sure that we can also go ahead and say of course all right so if it does it's not set we're going to set it to false um all right so this is the way we can set the phone control now we'll be able to access control from here we can utilize it in the HTML and so on and the last thing we want to do is essentially we need to tweak these functions that we have right they're not implemented yet so we start from bottom so we say this and we're going to set it to our disabled value so it's disable true and here we register untouched it is our callback function from here we're only going to set it to be the function and we can give the function the same type as it has up here so it will be an empty result that will return a value in this case all right so to continue we have the most interesting one which is the register on change this is the the method will come to shortly so we'll get to the resistor on chain shortly we'll start with the right value here in this case we're going to start this dot control if the control is set we're going to just set the value if it's not set then we have an issue and there and then we need to do something maybe we need to set a we need to generate a new control value so we can do it in this way uh with a ternary and we'll just generate a sorry it should be object here maybe we should just change the object to Value because we're going to write the value and it's going to be able to type in generic type T all right so in this way if we have a control we'll set the value if we don't have a control we'll create a new controller set the that value to the control all right so we have some sort of a recovery from the error refill would have some issues all right so getting to the register on change here there's a couple of things it all depends on the requirements I would say from uh from from ux really so it all depends on if you want to have inline validations and and so on so there are a couple of ways to do this really I usually have a subscription here and I do value changes and I use the pipe and I start using I start here by using the um Let's see we will import the take until here we make sure that we're destroying it on component unmount so we also want to go ahead and use torque with so we want to make sure that we're not repatching values that already exist because that will trigger a change that we might not want to have all right so we can use the distinct linked until changed in this way let's go ahead and import that from arcgis and here we can say tap so we're going to do something with the value and then we're going to call the actual value in this one all right so we can import that the tab function so it will just forward it and set it to the function from here and we will only do this in cases where the function in this case would be um let's say for instance if you have a change an actual change in value so if the value would be same as previous it would not go over these two subscriptions so now we can also we need to use the subscriber and here we can determine the Logics that we want to have right so I usually set it up to be marked as touched sorry Mark has untouched the logic I usually set up with my phone controls to to get a certain um user experience is that for instance if you have an input field say say we have an input field I'm starting to type something I blur from the field I receive the error that something is wrong with the field I go back when I start typing again I want to get rid of the error message so putting these control markers untouched will make it possible for us to really get rid of the error messages as the user is typing you can determine whether you want to have it or not in my cases I will show you the logic in in a real use case and how it can work so we're going to keep it with this so all right so now we are pretty much complete the only thing I would say is that you would have to implement this Logic for all of your your own self-programmed components so input components select components and so on and that does not really make sense for us so what we really are going to do to generalize it even in a further level is to create I a directive which will implement this base logic so that all of our form controls would behave in the same way all right so we can create an an directive here so angular generate directive and we're going to call it control value accessor all right so we know for a fact this is going to be used for all of our from component components that we have and here we can just substitute a bit of the logic right so we can remove the all of this really put it here like this and we can just go back to the input component once again and we can just make sure that we we implements the control value X this is redirecting so now we're removing the this kind of logic to the control value accessor so we can essentially just remove everything from within the input component because we want this to be standardized amongst all of the form controls that we have so instead of creating one for each one of the phone controls we can do it in this way and just keep one for all of it obviously we need to import all of the missing Imports that we have and we also need to give it the generic type T and then we are pretty much set to reuse this this directive in all places so this is expecting the generic type T to be passed in from the input component and now we have some errors here which is uh extends when I'm blind it inherit the subclass yeah obviously we need to remove the the implements to extends in order for us to actually use this look and we can remove stuff from here so keep in mind this will be good in terms of testing we will not have to test all of the components logic when it comes to the behavior of the forms this can be tested for the control value accessories that we have which is quite awesome um so look at the amount of code we have in our input component it will be minimalistic so there's a couple of things we want to have in our front control we want to have the wrapper so let's call it I will call it Dev by Seb so DVS from control it's always good to wrap it with your own prefix I will have a label we'll have an input and we can have if we wanted to have errors are going to be removed from here now and we can see the input is going to receive the front control that we are generating from the directive so keep in mind by extending it we'll be able to access all of the values that come from here comes from here so we have the control here in this case we're going to set the control here and since we haven't imported the reactive forms module yet let's navigate to the app module and do that for now reactive forms module can be imported from angular forms all right so now that that is done this should not work and as you can see here the control might be undefined in some reason so we can just put an NG effort to make sure that if the control is not defined it can always wrap it in this way so now we always need to make sure that we have a control if it's not defined we'll it will be defined through the directive here with some some cases here if it would crash it will Define it if you don't have anything when we try to write a value it will create a new form instance so this will essentially will it will be handling itself you could say so now we have the the input so here instead we're going to go with input ID so this is good for us in order for us to bind how how the form and the labels will be connected we're also going to make the label uh optional so we can also say we're going to print it we'll do NG if the label is set we're going to have the label and here we probably want to have some type so input type or we could just go with type in this case all right so now we have three props so input label and input ID sorry input ID label and type that we want to have so we could just go ahead and and add that real quick to the TS file as inputs and you could argue whether you should have some of them as as generic ones from the control value maybe you want to have the label um as and and the input ID really to be something that comes from there but let's not do it for now this is something you need to argue with in your your when you do this with your team all right so we have the type type is going to be of a couple of different stuff so we can say type input type so we can say the input type is going to be either text it could be number email password I just remove a couple of them as you can see we have some stuff coming from the Google copilot which is quite awesome so we have text number email password I think this is pretty much enough for us so we'll go ahead and give it a type and sorry I have fat fingers sorry all right so input type here we can give it to type text so doing this we are essentially saying we have a default type text but it can be of any of these types so now typescript and and and the the com the CLI will be able to actually determine if we have some syntax errors and so on and build Stay face if you would give it another prop if you want to have other stuff you just add it here all right so now that we have the input component with the label and the type we we can just make sure that we don't have any errors in the HTML right so this means we're pretty much good to go to start with this reusable form element so let's put the the browser next to the screen so now that we have it up and running on the next screen let's go ahead and go to the app component where we're going to create a form control so first and foremost we're going to try it out test control is equal to new form control like this we're going to give it a default empty string value and we're going to pass this into the uh sorry if we go to HTML of the form app component we should be able to do it because we're importing everything in the app module you should not do this you should most likely have a input module that would be imported here we're doing this just for for uh being able to test it real quick and show you how it could look like so we're passing in the uh the test control where you need to give it a label hello we can also give it the input ID my ID so all right all right so we can see that we have some errors here in the console it just says that we need to have multi-true we're going to make sure that we add the multi-true for the NG value accessor so going to the input the input component here we can just give it the multi-true here and it should work eventually so now we can see that it's actually working now we can give it some styling so that it looks a bit better um DVS run control you can also say the label input display block and the label we want to have some sort of a margin button maybe you want to have 4px we want to have it font weight a bit bolder let's try 600 I don't have any fonts installed right now so it might look a bit strange so we'll go ahead and say input we want to have a padding of 8px let's go to the app component CSS file to add some margins uh so that we have some some jumping content so that it's easier to preview all right we also want to say the input field with 100 and we want to set border because now it's you see it wraps the the margin that we have you need to set sorry it's an easy box sizing Order Box this will tell the the CSS that to take the padding within consideration so that it won't jump and you won't have to have negative margins and so on for the width all right so now that we have our input component I think it looks quite good for for this small amount of time we have spent with it let's try to give it a default value here to see what would happen in the app component so let's go to the app component here file my default value it should be entered here so when we change it we should also be able to let's say validators that are required so let's import this and save so I'm shrinking the uh the rows here so that it's easier for us to see all of the code at the same time uh going to the E3 let's go to the input component really we can go here we can say we're going to print all of the errors we have so we can say control the errors and we can print it as Json to see how it would look like we don't have any errors now but removing it would have the required true so this means that the error management or the error things is working this is a custom uh validator coming from or coming with really the um reactive forms so the forms it's built in essentially you don't have to build it and also we want to say if it's required we want to add the asterisks here maybe I have seen many ux designs talking about that it might not be a good case to do it but let's go ahead and do it anyway um so doing so if it's required we want to add the class required and then we can go to the Cs file here and say if it's required we want to add the after um yeah let's let's give it a hard-coded value here with the content store and as you can see it's appearing here so now imagine we also wanna on the input field obviously we want to have set required is required here so that we have some sort of a a good way of showing it obviously it's not going to be shown in all here when we don't have any errors we're just printing it here to see whether it is a good way for us to actually create a a simplified way of logging the error so that we on each and single one of the cases we utilize this component have to specify NG if this error engine that and so on because that will be super annoying all right so now let's continue and try how it would look like with phone controls from group sorry so we create a new form group we have tested the phone control works quite nice already so we can test out the form group here we can say um name and we're going to give it a a new like new phone controlling this way and this is the default value we don't need any default value so we just want to try out how this would look like in case we have a form so we'd have a form we're going to specify the form group that we added so I don't really remember what we put the name to let's see if we have some intellisense no we did not so the app component yeah we just gave it the name for group and we'll wrap it in this way and instead of having the form control here we want to be able to write the um sorry the form control name and here we want to give it the the name obviously so this is the name of the front control so the name that we sorry I keep adding the then this one so this is the name of the form group element so this should also work so we can either use the front control name or from control if you go to the control value accessor directive here you can see here is for the form control it will behave in a way that it will fetch the form group and then it will fetch the actual control of the from group just like the way we're doing it so we can see that both cases essentially works for us which is quite awesome this is exactly what we wanted it to work in but let's remove it and just keep the form for now and we have the required here we could add some error outputs so the way we what we want to avoid is having to write let's say um all right we have an error here I've seen this many times I think this is super annoying but you could have front group dot controls dot name dot errors and here you can select the required let's see here required let's see what the complaints about oh my God it needs to accessing this way so let's see what it complains about now it's possible null okay so this field is required right I I think this is a very cumber cumbersome process to have to add at all places so if you would have 10 different errors for this field you would have to insert them in this way I think this is uh this is not something you want to do if you're going to work with Enterprise applications this is going to clutter down and take a couple of time like it will build up the the components that you have so an alternative way to do this is you can eventually just standardize it by creating a component that will just since you can see here it's key value based in this case in some cases there might be a key and then an object as the value which maybe has the max length and so on of a string it could be the different pattern that needs to be used but anyhow you could standardize this logic in in your component so you could create a new component that will receive this error and translate it in a certain way we are not going to do this in this video what we're going to do here is essentially just look through it so just to see how it would look like for the input component this should most likely as I mentioned you know what let's create that component it goes super fast so we'll generate a new component so NG generate validation errors so we'll generate the component with the validation errors logic so app validation errors here we're going to pass in the the control the errors okay so just as errors obviously like this so that we can handle this in the validation errors components because we're most likely want to have the validation errors for all input fields that we have so going here we can say all right so we're going to take the input errors which is going to be I think it's records training and validation error I'm not entirely sure about the typing really but let's do it like this you can investigate a bit to see what would happen so here it gives us it could be validation errors so awesome that we have some intellisense but also says that um null so it could also be no in this case all right so now we have given it a type here which is a record is saying that it's instead of having the cumbersome name that you had before when you had the man object and you would have a key which is a string and then that could be blah blah blah record is it's just a way of of doing this typing so that you won't have to do it in your code so it's quite awesome all right so con to continue with this we were receiving the research just and we as you can see we're having the error output here so let's go ahead and just print it like just like we did before so it's just going to be errors right and here we could say we're going to create a list which is going to list all of the errors that we have so ng4 let error of errors and then we're going to say key value and the reason to why we want to have key value is because we want to be able to we can say key error.value so in many cases the value will always be just be true and in this case the key will make more sense so in the TS file here imagine how we would have translations or something we could have for instance let's call it error messages messages in this way and here we can see the required blah blah blah so it's just a key value based um error message that we want to have we could also have an input here so that we could override it but let's not do that for now either so error messages now we can say all right we have the the object and we want to be able to to Really fetch the data from that object in this way so now it complains a bit that the string required has no index signature so what we need to do essentially go to the validation errors component TS file and say all right this is going to be of record string string so it's a key value based value and there will be gone and as you can see here this field is required prompting something remove it removing the stuff also so and as you can see when you come into a page or to a form you don't want the first thing to be that you see an error message but let's go ahead and give it the foundation errors we can just give it some some styling so that it looks better I will go ahead and say margin sorry margin zero padding zero we want the Allies to have a color which is going to be red um you can also remove the sorry you can remove the padding here and we can see maybe padding left 10 PX just to make it aligned that was too little so now we have it aligned in this way you can also say um margin top is going to be 4px in this way I can also say the font size is going to be decreased a bit let's say we're going to say 0.8 gram so now we have the error messages printed in this way if you have more than one table will be listed under under each other right so this is how you could standardize the way the errors are handled in here you could also add an input here saying custom error messages which is also going to be a record so if you want for some strange reason you want to extend it or add a custom message you could do this so this this is one way of doing it and then you could Implement implements and you earn changes so implementing their own changes here you could just add it usually put the lifecycle afterwards so you can say const um here we want to disrupt the custom error messages sorry messages and here we want to see from the changes let's go ahead and copy it so if if this has been changed what we want to do is we want to combine the error messages and you want to put what you had before at first and then you want to spread in the new value so that the new values would override the previous ones so this is one way of you actually being able to pass this in and obviously you need to add this within the input component as well so let's go to the input component TS file real quick we can just add it here it's just going to be a forwarder really we're going to forward it to the validation errors that we have so in this way we can save it saving it in this way now we can pass in whatever we want from the app component so let's say we have customer messages so error messages we can say if it's required um the name field is required instead of having it to be generalized if if you want to have it in in this way for some reason this is the way you should do it so error messages we go to the HTML here we can pass it in here we can say custom error messages is going to be received from our messages and as you can see here the name field is required this is one way of standardizing something you build a component which is kind of stupid it handles the base cases but you can always extend it and make it much better than it is so this is one way of actually utilizing the the for control value accessor and as you can see here it's minimalistic code that we actually have in our input component we could do the exact same thing just copy copying the code and creating a new component so let's go ahead and do that we're going to create a new generate so and NG generate component select we're going to create a solid component which is going to utilize the the default HTML behavior in this case we're not going to create a custom Logic for that so going to the TS file we want it to be able to to work in a similar way that the input will work so let's go to the input field real quick and we're going to just copy everything from providers we want it to be here so just paste it there we can import everything we can remove the input component here we just need to substitute to use existing here so that it actually utilizes the select component and the last thing we want to import is essentially some inputs that we have here so we can paste it we want to have not input ID but rather select ID labor can still be there and type is not really required here anymore but the other ones can still be heard all right so here we also wanted we want to make sure that we actually extend it we can use it with type t uh generic type T if you know the type already you can just give it a type but we do not know it at at this point it could be objects and so on but let's continue we have this select now we could should be able to add the select let's go ahead and and go to HTML here we could say all right we're going to use the select and here we have some options right and we can Loop through the options so we're going to take in options as an input as well all right so this is going to be a default value and empty array but it's going to be of type T because that's what we're essentially going to set to the front control so the options of T and let's go to the select component HTML file we have some errors here so it's going to be we're going just going to have a list in this case you could have key value base we're not going to have key value base right now you could or should probably have it because that makes more sense but in our case we're going to make it simple for us so we're just going to go with option so whatever we have as a value will also be whatever has an option so if you go to the app component now we could have for instance I'm not going to say gender because I think that's that's yeah it's widely talked about but let's go ahead and say age and the default value of the age will be c0 now we want to go to the app component HTML file we can just go ahead and just make a copy of this this is going to be select so we're changing it to the select and here it should not be named it should be age now we don't have any pre-filled value we don't have any stylings either for that so we could go to the let's go to the input really and and just make sure that we copy whatever we add in the HTML going back to the selector we could just replace the select with whatever we had in the in the input here and this should also be select ID and then we should place the ID here this is going to be select ID all right so we don't have any styling for it so you should probably have a globalized form that you can import that would style them so that they look similar um not going to do that right now we can just be a bit lazy I would say and do it in this way so now we're going to be able to pass in stuff from the here we're just going to create uh options and this is going to be an array let's just give it a couple of values that we have one two three so as you can see here it's you're going to be able to select the values here so we have the four reactive forms also working in our way here to make sure that it does work we could say form group that values the value and then you were just going to print it as a Json here so that we can see the value so the name is is an empty string and the age is not changing at all so we need to check why the age is not changing all right so let's jump into the HTML here I'm pretty sure we forgot to add the form control here so we just need to attach the form control here just like we did in the input component so now it should work as expected as you can see it's zero and the name is empty string here so entering something here will be presented here changing the value here you can see the age one two three and so on so this my friends is how you can utilize the the value accessor control value accessor creating a custom directive in order for you to reuse it amongst different form controls and create a custom library that behaves in the same way thank you for watching guys all of the best bye
Info
Channel: Sebastian Persson
Views: 7,992
Rating: undefined out of 5
Keywords:
Id: N2nOUBwBwyU
Channel Id: undefined
Length: 36min 26sec (2186 seconds)
Published: Thu Apr 13 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.