DEV Community

Daniel Marin for This Dot

Posted on • Updated on

ReactiveForms: Don't die trying.

Intro

This article is the first of a 3 part series about Reactive Form, and its usage in complex scenarios.

From now on, "AngularJs" refers to Angular version 1, and "Angular" refers to Angular v2+.

The article consists of three major points:

  • Introduction to AngularJs forms.
  • Transitioning to Angular Template Driven Forms.
  • Building blocks of Reactive Forms.

The form you will build if you follow this article


This is how your first ReactiveForm will look.

Without going into further detail on how forms worked in AngularJs,you used ngModel in your template to build a form. Although it worked, was it easy, testable and scalable?

Template Driven Form

Before Angular arrived, everybody was scared, because there were many breaking changes. In response to that, the Angular team created strategies that are similar to the ones from AngularJs. One good example is the Template Driven Form.

Although many things changed, you can use forms in almost the same way. You just need to use ngModel in your template to build a form.

Lets build the form shown above using Template Driven Forms. You will start in the name field, but you will repeat the same steps for every field.

Declare name property in the component.

// app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.scss' ]
})
export class AppComponent {
  name: string
}
Enter fullscreen mode Exit fullscreen mode

Add name to ngModel.

<!--app.component.html -->
<div class="form-control">
  <label>Name: </label>
  <input name="name" type="text" [(ngModel)]="name">
</div>
Enter fullscreen mode Exit fullscreen mode

Now that the first input is ready, do it for the other fields.

By now, you should have your form ready. Now, lets try to print the form as you change it. You will work with ngModelChanges from ngModel, which calls a method when an input changes. Lets see how that would work in the example.

Create a nameChanged method.

// app.component.ts
nameChanged(name) {
  this.person = { ...this.person, name };
  console.log(this.person);
}
Enter fullscreen mode Exit fullscreen mode

Add nameChanged to ngModelChanges.

<!-- app.component.html -->

<div class="form-control">
  <label>Name: </label>
  <input name="name" 
         type="text" 
         [ngModel]="name" 
         (ngModelChange)="nameChanged($event)">
</div>
Enter fullscreen mode Exit fullscreen mode

It's done! After doing it for every input, you should see something like this.

Finished form using Template Driven Form


After wrapping it up.

Reactive Form

ReactiveForm provides a model-driven approach to handling form inputs with values that change over time.
Angular Docs

Is important to notice that it is based on RxJs streams, and manages its internal state in an immutable way. By allowing you to define the form programmatically, you are able to unit test it. It comes with a set of helper methods to make your life easier.

AbstractControl: This is the base abstract class for FormControl, FormArray and FormGroup. Thanks to this, you are able to compose forms with any combination of those.

FormControl: This is the main building block for a Reactive Form. You will see a lot of these, if you haven't already.

name = new FormControl('');
Enter fullscreen mode Exit fullscreen mode

You have a lot of properties and methods available in name, allowing you to play with the FormControl.

Enable and disable methods in use


Using the enable and disable methods.

FormArray: If you need a list of values, it will help you a lot.

phones = new FormArray([
  new FormControl('')
]);
Enter fullscreen mode Exit fullscreen mode

FormGroup: If you have complex data structures, you are going to need this.

car = new FormGroup({
  brand: new FormControl(''),
  model: new FormControl(''),
  year: new FormControl(null)
});
Enter fullscreen mode Exit fullscreen mode

FormBuilder: FormBuilder is a service that allows you to create ReactiveForms more elegantly. It's almost always the best choice in complex scenarios.

It is totally up to you which alternative to use. This is how it would look in both cases. car and carFb behave the same way

import { FormGroup, FormControl, FormArray, FormBuilder } from '@angular/forms'

@Component({...})
class ExampleComponent {
// Using Controls directly 
  car = new FormGroup({
    brand: new FormControl(''),
    model: new FormControl(''),
    year: new FormControl(null),
    availableColors: new FormArray([])
  });


  // Using FormBuilder
  carFb = this.fb.group({
    brand: [''],
    model: [''],
    year: [null],
    availableColors: this.fb.array([])
  });

  constructor(private fb: FormBuilder) {}
}
Enter fullscreen mode Exit fullscreen mode

The main difference is that you have to inject the FormBuilder service. Also, instead of using the constructors directly, you use FormBuilder.

Now, the moment you were waiting for: how to use this. You are going to build the same form you did with Template Driven Forms, but while using Reactive Forms.

Inject FormBuilder, and create personForm with it.

// app.component.ts
import { Component } from '@angular/core';
import { FormGroup, FormBuilder } from '@angular/forms';

interface Person {
  name: string;
}

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.scss' ]
})
export class AppComponent {
  personForm = this.fb.group({
    name: ''
  });

  constructor(private fb: FormBuilder) {}

  submit(form: FormGroup) {
    console.log(form.value);
  }
}
Enter fullscreen mode Exit fullscreen mode

Add personForm to formGroup, and name to formControlName.

<!-- app.component.html -->
<div class="container" [formGroup]="personForm">
  <h1>Reactive Form</h1>

  <div class="form-control">
    <label>Name: </label>
    <input type="text" formControlName="name">
  </div>

  <div id="submit-control">
    <button (click)="submit(personForm)">Submit</button>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Use valueChanges observable to log form changes.

ngOnInit() {
  this.personForm.valueChanges.subscribe(
    (person: Person) => console.log(person)
  );
}
Enter fullscreen mode Exit fullscreen mode

You did it! Both of your solutions should do the exact same thing. Go and finish adding the missing form controls.

Conclusion

By now, you should be convinced of the power of ReactiveForms. In future parts of this series, you'll learn how to test and validate them, which is where it starts to shine. Whether or not you like this form's implementation, it came to stay, and you should if you haven't already, start using it in your day to day as a Modern Web Developer.

Code Snippets:

References:

This article was written by Daniel Marin who is a Software Developer at This Dot.

You can follow him on Twitter at @danm_t.

Need JavaScript consulting, mentoring, or training help? Check out our list of services at This Dot Labs.

Top comments (1)

Collapse
 
maryvorontsov profile image
Mary Vorontsov

Hey, Dan! Do you think you'll be interested in a paid freelance writing opportunity for blog.soshace.com?