Create your account

Already have an account? Login here

Create account

Angular 4 Reactive Forms Tutorial

By Gary simon - May 10, 2017

So, you have to build a form and you stumble upon this stuff about Reactive forms in Angular.  Sounds confusing, right? Well, we're going to solve all of that in this step-by-step tutorial.

Reactive forms give you the ability to control validation from the component code, plus you're able to unit test them. These are the couple advantages that reactive forms have over template-driven forms, which we won't be covering in this tutorial.

Let's get started!

First, if you prefer watching video on this stuff..

Be sure to subscribe to the official Coursetro youtube channel for more awesome videos.

Setup a project

Just so we're all on the same page, let's use the Angular CLI to start a new project. If you need help with this process, check out our Free Angular 4 From Scratch course.

At the console:

> ng new ng-forms

Once installed, run:

> cd ng-forms
> ng serve

Alright, great!

Integrating Reactive Forms

Before we can construct any type of form, we need to first import the ReactiveFormsModule into the /src/app/app.module.ts file as an import:

import { FormsModule, ReactiveFormsModule } from '@angular/forms';

@NgModule({
  // Other properties removed to keep this short
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    ReactiveFormsModule  // Add this!
  ],

})

Save that and proceed to the next section.

Component Code

First, we need to import some necessary form-specific functions in the component that will house the form. So, in /src/app/app.component.ts at the top, we add:

import { FormBuilder, FormGroup, Validators } from '@angular/forms';

Each form is bound to an instance of FormGroup. We will use the FormBuilder to handle form control creation, and Validators will help setup validation on each form control.

Next, in the AppComponent class, let's add the following code:

export class AppComponent {
  
  rForm: FormGroup;
  post:any;                     // A property for our submitted form
  description:string = '';
  name:string = '';

  constructor(private fb: FormBuilder) { 

    this.rForm = fb.group({
      'name' : [null, Validators.required],
      'description' : [null, Validators.compose([Validators.required, Validators.minLength(30), Validators.maxLength(500)])],
      'validate' : ''
    });

  }

}

Our simple form is going to have a name and description text input fields. We will also have a checkbox called validate which will demonstrate how you can change validation requirements within the component.

First, we're defining rForm as a FormGroup. Inside of the constructor, we're using dependency injection for the FormBuilder

Then, we reference rForm and call the FormBuilder to control the validation of each form field we're going to create in HTML.

As you can see, you can simply pass Validators.required alone in the case of the name field, or you can pass multiple validation requirements with Validators.compose as is the case with the description.

Let's add a custom method addPost for handling the submitted form, so beneath the constructor:

  addPost(post) {
    this.description = post.description;
    this.name = post.name;
  }

Constructing the HTML Form

Before we structure the form, let's import the frontend framework Foundation into the /src/index.html file just before the closing </head> tag:

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/foundation/6.3.1/css/foundation.min.css">

This will allow us to easily style our form layout.

Let's hop into the /src/app/app.component.html file to set up our simple form:

<div *ngIf="!name; else forminfo">
  <form [formGroup]="rForm" (ngSubmit)="addPost(rForm.value)">
    <div class="form-container">
      <div class="row columns">
        <h1>My Reactive Form</h1>
        <label>Name
          <input type="text" formControlName="name">
        </label>

        <label>Description
          <textarea  formControlName="description"></textarea>
        </label>

        <label for="validate">Minimum of 3 Characters</label>
        <input type="checkbox" name="validate" formControlName="validate" value="1"> On
        
        <input type="submit" class="button expanded" value="Submit Form" [disabled]="!rForm.valid">
      </div>
    </div>
  </form>
</div>

<ng-template #forminfo>
  <div class="form-container">
    <div class="row columns">
        <h1>{{ name }}</h1>

        <p>{{ description }}</p>
    </div>
  </div>
</ng-template>

Whew, okay, a lot is happening here. Let's cover the major stuff, starting at the top.

<div *ngIf="!name; else forminfo">

Here, we're saying that if the name property does not exist, then show everything inside of this div. Otherwise, show the template (which is defined at the bottom as a local variable within an ng-template) defined by the local variable forminfo.

We're doing this because we're going to show the submitted form information.

<form [formGroup]="rForm" (ngSubmit)="addPost(rForm.value)">

We bind rForm as a formGroup, which lets our component code know that this is the form that's associated with rForm. Then, when the form is submitted, we call our method addPost and pass in all of the form values.

<input type="text" formControlName="name">>

<textarea  formControlName="description"></textarea>
        
<input type="checkbox" name="validate" formControlName="validate" value="1"> On

Then, we have our 3 inputs. Each has a formControl with the name, which must match the associated name in our component code.

<input type="submit" class="button expanded" value="Submit Form" [disabled]="!rForm.valid">

Finally, we have our submit button. We've added a disabled attribute through property binding. This button will not be clickable if the form has not been filled out correctly. 

We will add a bit more to this template, but for now it's good.

Styling the Form

Paste in the following CSS within /src/styles.css:

@import url('https://fonts.googleapis.com/css?family=Muli:400,700');

body {
    background:#e8e8e8;
    font-family: 'Muli';
}

And also this CSS within /src/app/app.component.css:

.form-container {
    display:block;
    width:90%;
    padding:2em;
    margin: 2em auto;
    background:#fff;
}

.alert {
    background: #f2edda;
    padding: 7px;
    font-size: .9em;
    margin-bottom: 20px;
    display: inline-block;
    animation: 2s alertAnim forwards;
}

.button {
    margin-top: 3rem;
}
h1 {
    margin-bottom: 2rem;
    font-weight:bold;
    font-family:'Muli';
    font-size: 2em;
}


@keyframes alertAnim {
    from {
        opacity:0;
        transform: translateY(-20px);
    }
    to {
        opacity:1;
        transform: translateY(0);
    }
}

Ignore the animation stuff, that will come in handy a little bit later.

Let's give it a go!

Ensure ng serve is still running, and visit http://localhost:4200 in your browser.

You will see that the submit form button will only be displayed if the validation requirements are met on each of the inputs.

This is fine and all, but we should show the user a message regarding the validation requirements.

Adding Validation Messages

Back in the /src/app/app.component.html file, let's add the following code underneath the Name and Description labels:

<!-- This goes underneath the Name label -->
<div class="alert" *ngIf="!rForm.controls['name'].valid && rForm.controls['name'].touched">{{ titleAlert }}</div>

<!-- This goes underneath the Description label -->
<div class="alert" *ngIf="!rForm.controls['description'].valid && rForm.controls['description'].touched">You must specify a description that's between 30 and 500 characters.</div>

We're using *ngIf to say that if the name field is not valid and the form input field has been touched, then show the contents of this div.

In the first div, we're using interpolation to show a property called titleAlert. This is because we want to make the validation message of this particular field dynamic, based on our checkbox.

So, in our component file, add the property near the top of the class code (underneath the name:string property):

titleAlert:string = 'This field is required';

Save the file and then visit the project in the browser.

You will see that if you click inside of either input, and then click out of it (or you don't meet the validation requirements), our validation messages will now animate in!

Let's do one more thing, and see how we can change the validation requirements on the fly.

Changing Validation Requirements

Sometimes your form validation requirements might change, based on user input. 

When our checkbox is checked to on, let's assume this will mean that our name input now needs to not only be required, but also at least 3 characters long. I know, it doesn't make sense, but let's just assume it did for a second!

In the component class underneath our addPost() method, add:

  ngOnInit() {
    this.rForm.get('validate').valueChanges.subscribe(

      (validate) => {

          if (validate == '1') {
              this.rForm.get('name').setValidators([Validators.required, Validators.minLength(3)]);
              this.titleAlert = 'You need to specify at least 3 characters';
          } else {
              this.rForm.get('name').setValidators(Validators.required);
          }
          this.rForm.get('name').updateValueAndValidity();

      });
  }

Each form field has a valueChanges method that we can subscribe to. So, when someone clicks on our validate checkbox, we can make certain things happen.

If the validate checkbox is checked, then the value becomes 1, because that's what we bound the value to in the HTML template.

If it is equal to 1, then we use setValidators to define new validation rules on the name input. Here, you can see we add the minLength rule and set it to 3. We also change the titleAlert property to match the new validation requirements.

If it's not equal to 1 (unchecked), we'll set it back to the original validation.

Save the project, and now try it out in your browser!

Conclusion

As you can see, Angular Reactive Forms are very powerful and versatile. Hopefully, you learned a lot from this tutorial, and I will see you next time!


Share this post

Chat with us




Say something about this awesome post!