I choose to use Reactive forms. They give you the most control over your data as it is being entered from the code-side of things. Once I understood it, the answer is pretty clear. Even if you don’t want to choose the Reactive form structure, you need to realize that template forms in Angular work this way, just in the background. So, learning the Reactive method isn’t a waste of time; to the contrary, it is like opening up the hood of template forms and seeing how it works. In the end, it will make everything easier.
So, I took a FANTASTIC class on pluralsight: Angular 2: Reactive Forms by Deborah Kurata, a brilliant teacher. I then added reactive forms to my own sample project (a small library for a client of mine), and was quite pleased with the results. However, it seemed like there were a lot of things that would have been best handled with a quick “how-to” that dealt with the theory moreso than a “let’s build something from scratch”. Here’s what I learned, and I’m sure I’ll be refering back to this myself!
How does this work?
In classic javascript, we used to go and get the data from each textbox on the page, validate it, then post it to the server. With jQuery, we started attaching events and validation to the objects themselves, often using onblur or some other event to track what happened when people left the field. With AngularJS, we had 2way binding, and there was some form structures very similar to the A2 template module, were we used ngModel to track the data. Reactive takes it one step further (or at least exposes the structure so we can fiddle with it).
Rather than the jQuery method, where you go item by item creating the form, Angular makes an object called a FormGroup. This is a set of fields and all their form-related attributes, inside a single object. It even allows sub FormGroups and they have their own data. The primary purpose here is really “control” AND uber-easy validation. And I mean… really really nice… once you get the hang of it.
So, how does it work? Remember how I said that each field and all it’s “form-related” attributes were piled inside this FormGroup object? Well, the FormGroup class is just a nice clean class that has functions and it’s own constructor, etc… anything that you’d build yourself. In this case, what it does is finds the HTML object with FormGroup=[myGroupName] in it. That’s the base. Then it cycles through that and appends a new FormControl for each input,select, checkbox, etc… that you’ve added a formControlName=”someTextBoxName” identifier to. If it sees another FormGroup, it just appends another form group and starts doing the same thing, until it reaches the end of that initial FormGroup.
If you already know reactive forms, you’re probably saying, “NO NO NO NO! That’s not how it works!”. And you’d be right. 🙂
Nope. That’s template forms, and it’s a pain in the ass. You know why? Because it does all that cycling through the HTML and it also gets all the form attributes and validation and other stuff from custom directive tags in the HTML. You might be saying, “It’s Easy!!”. No. It isn’t. The minute you hit some need to validate a couple fields against eachother or want to do a bit of data-tweaking before you push your data into an object, you just lost any possible savings. And if your form is so simple that you don’t need it, then the time you’d spend on reactive forms is so negligable that it makes no sense not to start there in the first place
But, do you have to go cycling through the form and get all your items and build it dynamically? Nope. You make your FormGroup object and then go to your HTML and add those formControlName tags yourself to match. And they HAVE to match. That’s the entire point… and it’s a stickler. If you have a FormControl in your FormGroup object, then you HAVE TO have one in the HTML, otherwise what the hell is it going to be referencing. (seriously, this actually confused me initially while making it because I wanted to build the formgroup, test it and then do the html and it kept breaking.). They are attached from the get-go.
Ok, so you make this FormGroup object, you add all your controls to it, you make sure every control on your page has a formControlName (no brackets) so they’re all nice and matched up. When you run that code, you’ve basically made two-way-binding between this object and your form. So, if you stick
{{myGroupName.value | json}}
on your HTML template, you’ll see all the values of all the form object controls as you type. If you add
{{profileForm.valid }}
or touched or dirty, you’ll see the status of the form as a whole. Basically, if any of the fields have an error, the form group is invalid; which is pretty handy.
Once you’ve got this all set up, you’ve now got an object in code that perfectly matches (and watches) the form on your template. But why not just use ngModel and attach my data directly to it? Because that will screw you every time. Seriously, it’s a trap. If you’re prepopulating stuff or if you’re edting things and you want an Undo (for example, doing server-side-validation). Even in AngularJS, I became accostomed to adding all my fields into an object and then replacing my dataobject with the values in the field. It was easier than dealing with mistakes and kept the user input from screwing with my real data.
But it’s a pain to set all this up!.
Don’t be such a pussy, just code it out….. or… ahh, use a form builder like I do.
A form builder essentially gives you a little shortcut to initializing all your controls. You just declare your FormGroup, as you have to anyway for any public variable.
public profileForm : FormGroup
(you don’t need the “public”, but I do it out of habit). Then, in your constructor:
this.profileForm = fb.group({ FirstName: ['John'], LastName: ['Doe'], Phone: ['123'], Email: ['test@test.com'] })
(in this case, I’ve initialized a FormBuilder in my constructor and named it “fb”.)
That’s it. You just name your form and add those formControlName=”FirstName”, etc… to your html. Not hard. Seriously. That’s it.
Back to the how-it-works.
The cool thing is, at this point, you can validate the hell out of stuff without knowing a bunch of new directives and tags. In those arrays for, like
FirstName:['John']
you can jam in validators. Any function you feel like making up, exactly like a filter. So, you might turn it into
FirstName:['John',mySimpleFilter]
. That mySimpleFilter is really simple:
function mySimpleFilter(c: abstractControl){ if(c.value==='John'){return null}else{return true}})
Basically, you just made it invalid if the name is anything but John. (returning true means it is invalid). And that isn’t all, you can add as many validator functions as you want to the form builder, so you could have
['John',mySimpleFilter,andAnotherFilter,andYetAnother, andEvenAnotherFilter]
if you want to pursue it, there are even cool things you can do, but you get the idea.
Now, you need to do every single thing like this…. oh… no you don’t. 🙂
There is a class called “Validators”. You import that into your form and it’s got all the basics premade:
Validators.required, Validators.minLength(5), Validators.maxLength(25)
Lots of them. So, if you want FirstName to be required:
this.profileForm = fb.group({ FirstName: ['John', Validators.required], LastName: ['Doe'], Phone: ['123'], Email: ['test@test.com'] })
and so on. You get the idea. We’re back at uber simple, yet insanely flexible. The point being, you can slap it together and then, when your client wants you to do some crazy validation, you aren’t tying strings and coathangers all through the HTML, you just add a function and you’re done.
Enough about validation. Most of us hate it anyway. Damn users and their incorrect data!
Updating your real data.
So now, you’re bored as hell. I know I am, so let’s juice this up a bit. How the heck do I move everything over to my object? Easy, you grab each field by digging down to it’s object tree and assign it to the data object like this:
myDataObject.Firstname = myForm.controls.FirstName.value;
and if you have it in a subform:
myDataObject.city = myForm.controls.Address.controls.city.value; Easy right?Just kidding. That sucks. It's a lot easier. First of all, you can "get" any control in the form like this:
firstName = myForm.get('FirstName').value; console.log(firstName);So, basically, you can "get" anything, even if you have 30 layers of subforms. Still, that kind of sucks. So you can use the magic of Object.assign(). This will squish together 2 objects and make a third like magic.
let niceCleanProfileData = Object.assign({},this.myDataObject,this.myForm.value);Boom. You got it. Your data from the form is all nice and valid and it's copied over to the niceCleanProfileData so you can post it to the server. MOREOVER it doesn't delete important stuff. So, if you've preset some defaults, like "createDate" or an ID or something in your dataobject, it stays there, but if there's already something in myDataObject.FirstName, it will now get the form object.
So, you use your FormBuilder to slap together a fast form object, you make sure the HTML forms match and you're started. Then you connect the forms (submit)=[mySaveFormFunction()] and you can pop the form data into your dataobject and post it to the server. Seriously, that's not to hard.
Pre-Populating
Yeah yeah, I should have put this further up, but I didn't. You've got to first see how easy that start and end pieces are. Populating it isn't hard, but there are really 2 ways. One is for initializing it, which clears out everything and other updates. That's called setting the PatchValue. It's super easy. Let's say I just downloaded my user's profile into myDataObject. It's got a million items in it, but I just want to have the basics in my form. Remember, I'm using ObjectAssign, so I don't even have to worry about my prexisting data being messed with.
let profile = this.myDataObject; this.myForm.patchValue({ FirstName:profile.FirstName, LastName: profile.LastName, Phone: profile.Phone, Email: profile.Email })You "could" set these values right at the get-go when you construct the FormGroup, as you saw I did in the first example, but that's really not a good idea. Basically, sometimes you don't have the data in time, but you've got the HTML, so you might get an error. I also have a horrible issue with crappy old databases and other issues (since I live in the real world and not an endless series of perfect examples from PluralSight). So, initialize with defaults you know, add the validators you will need, get your data, patch the form with the new data, then when the user saves, do an objectassign to get it all back to the data object and post it to the server.
A Check List
- You need to include it in your app module.
import { FormsModule, ReactiveFormsModule } from '@angular/forms';- Make sure to put it in your app module imports
imports: [ …, ReactiveFormsModule, …]- and then, in your constructor, you need to import it like this:
import { FormGroup, FormControl, FormBuilder, Validators } from '@angular/forms';- and then, in your class, you need to define a FormGroup variable like this
public bookForm: FormGroup;- and then, in your constructor, you need to inject the FormBuilder like this:
constructor(private fb: FormBuilder) {}- and then, in your constructor, you need to define a FormGroup with the FormBuilder like this
this.profileForm = fb.group({ FirstName: ['SomeDefaultValue', Validators.required, SomeOtherCustomValidatorIfYouWant], LastName: 'McNoValidatorButHasADefault', Phone: ['',MyCustomPhoneValidator], Email: ['',Validators.email] })- and then, in your data load function, you need to update your form and populate the controls like this:
let profile = this.myDataObject; this.myForm.patchValue({ FirstName:profile.FirstName, LastName: profile.LastName, Phone: profile.Phone, Email: profile.Email })(remember, that doesn't overwrite other data, so you could even do a single patch statement for, say Phone, later on and it wouldn't clear out your form)
- Then, send your clean data in the form back to your dataobject which you started with
let niceCleanProfileData = Object.assign({},this.myDataObject,this.myForm.value);- FINALLY, reset the form.
this.myForm.reset()