Published on

Build a Reactive Update User Form using Angular

These days website forms are everywhere in the form(no pun intended!) of login forms, registration forms, payment portal forms, user verification forms, and job application forms.

Ever wanted to create website forms that can instantly react to when their form input values change? Well, that’s what reactive forms in Angular are used for! Keep reading to find out how to implement a Reactive Styled Update User Form using Angular 10!

Demo of Reactive Forms in Angular.js

Figure 1: Demo of Reactive Forms in Angular.js

Prerequisites

Before starting this tutorial you will need the following:

  • An Angular project to implement this form in
  • Basic understanding of TypeScript programming
  • Angular application design fundamentals, click here to learn it from the official Angular Concepts page
  • Basic understanding of how to design forms, click here to learn it from the official Angular Introduction to Forms page

To elaborate on the first bullet point above: I will show how you to make the update user form component and for the sake of brevity, the integration of it into your own Angular project is beyond the scope of this tutorial.

Now without further ado, let’s get started!

Step 1: Create Update User Form Component

First, we need to create the Angular component that will hold the Update User form. So let’s run the command:

ng generate component update-user-form
View of update-user-form component file structure

Figure 2: View of update-user-form component file structure

Step 2: Add code to update-user-form.component.ts

Moving forward, we need to add code to our update-user-form component’s .ts file. So let’s open up the update-user-form/update-user-form.component.ts file and add the first portion of the code:

import { Component, OnInit } from '@angular/core';
import { UserService } from '../shared/user.service'
import { User } from '../shared/user.model';
import { FormGroup, FormBuilder, Validators, FormControl } from '@angular/forms';

@Component({
  selector: 'app-update-user',
  templateUrl: './update-user-form.component.html',
  styleUrls: ['./update-user-form.component.css']
})
export class UpdateUserFormComponent implements OnInit {
  selectedUser: User = new User;
  updateUserForm: FormGroup = this.formBuilder.group({});

  formReady:boolean = false;
  component = this;
  formErrors: { [key: string]: any }  = {
    'username': '',
    'emailAddress': '',
    'fullName': '',
    'age': ''
  };
  validationMessages: { [key: string]: any }  = {
    'username': {
      'required':      'Username is required.',
      'minlength':     'Name must be at least 4 characters long.',
      'maxlength':     'Name cannot be more than 24 characters long.',
      'forbiddenName': 'Someone named "Bob" cannot be a hero.'
    },
    'emailAddress': {
      'required': 'Email is required!',
      'email': 'Email is not valid!',
    },
    'fullName': {
      'required': 'fullName is required!'
    },
    'age': {
      'required': 'age is required!'
    }
  };

  constructor(
    private userService: UserService,
    private formBuilder: FormBuilder
    ) {
    }
}

Now for an explanation for the important sections in the above code:

  • FormGroup, FormBuilder, Validators, FormControl: we need to import these classes from the @angular/core library in order to build, validate and add input fields to the form
  • selectedUser is the User model object that is to be updated and the form will contain this user’s information as prepopulated value
  • updateUserForm: is the actual FormGroup object that is used to build the form
  • formReady: is a Boolean value that signals to the component of when the form is ready to be rendered
  • formErrors: is an object that is used to store any validation errors in this form
  • validationMessages: is an object that is used to fetch the validation error message descriptions so that the users/clients have a better understanding of what the form errors are and how to fix them the constructor instantiates the userService and formBuilder objects with the UserService(used to send the submitted data to the backend server) and the FormBuilder(used to build our form)

Now let’s add the second part of the update-user-form/update-user-form.component.ts file:

   setupForm(){
    this.updateUserForm = this.formBuilder.group({
      _id: new FormControl(this.selectedUser._id),
      username: new FormControl(this.selectedUser.username, [
        Validators.required
      ]),
      emailAddress: new FormControl(this.selectedUser.emailAddress, [
        Validators.required,
        Validators.email
      ]),
      fullName: new FormControl(this.selectedUser.fullName, [
        Validators.required
      ]),
      age: new FormControl(this.selectedUser.age, [
        Validators.required
      ])
    });
    this.updateUserForm.valueChanges
      .subscribe(data => this.onValueChanged(data));

  }

  loadUserData(){
    this.selectedUser = new User()
    this.selectedUser._id = '1';
    this.selectedUser.username = 'testing1';
    this.selectedUser.emailAddress = 'testing1@gmail.com';
    this.selectedUser.fullName = 'Testing1 Testing1';
    this.selectedUser.age = 10;
  }

  ngOnInit(){
    this.loadUserData();
    this.setupForm()
    this.formReady = true;

  }

  onSubmit(){
    this.userService.updateUser(this.updateUserForm.value).subscribe(x => {
      window.location.reload();
    },
      error => {
        console.log('Error encountered during form submission');
      });
  }

  onValueChanged(data?: any) {
    if (!this.updateUserForm) { return; }
    const form = this.updateUserForm;

    for (const field in this.formErrors) {
      // clear previous error message (if any)
      this.formErrors[field] = '';
      const control = form.get(field);

      if (control && control.dirty && !control.valid) {
        const messages = this.validationMessages[field];
        for (const key in control.errors) {
          this.formErrors[field] += messages[key] + ' ';
        }
      }
    }
  }

And here is the explanation for these methods:

  • setupForm(): instantiates the updateUserForm as a FormGroup instance that containing the data of the selectedUser User model. We also add some validations to each of the form’s fields, the most notable being Validators.email, which is a validator that makes sure that the provided string is in a valid email format. Finally, this methods assigns the onValueChanged() method to be fired every time the user/client changes any of the form’s input field values via the valueChanges Angular Forms event
  • loadUserData(): this is a temporary method used to manually load some dummy data into the selectedUser variable. It’s important to add some user data to selectedUser before rendering the form because the form needs this data in order prepopulate it’s form fields. I recommend deleting this method and passing the user data from a different component to the selectedUser variable after implementing this form
  • ngOnInit: Angular’s most common lifecycle hook method that will be executed after the constructor is finished it’s execution. This method calls the loadUserData() method to load the selectedUser object with some dummy data. Then it calls the setupForm() method to actually setup the form with the data from the selectedUser object using the FormBuilder class. Finally, it sets the formReady Boolean to true so that the template can render the form on the component’s html file
  • onSubmit(): This method will be executed when the user/client clicks the submit button in the form. It submits the form’s values to the updateUser() method in the userService class. Note: the implementation of the updateUser() class is beyond the scope of this tutorial but just know that when it is called it sends a PUT request to the backend server with the updated user data so that the backend server can reflect those changes in the application’s database
  • onValueChanged(): Whenever the user/client changes any of the form input field values, this method is triggered. It checks the form for any invalid form data, finds the matching error messages in the validationMessages object then appends each error to the formErrors object. From there, each error in formErrors will be displayed under it’s appropriate form input field

Step 3: Add code to update-user-form.component.html

Now that we’ve completed the update-user-form.component.ts file’s logic, let’s add the template code to the update-user-form.component.html file:

<form [formGroup]="updateUserForm" *ngIf="updateUserForm" (ngSubmit)="onSubmit()" class="update-user-form">
    <div>
      <div class="card-header bg-primary text-white">
        <h3>Update User</h3>
      </div>
      <div class="card-body">
        <div class="form-group row d-flex justify-content-center align-items-center">
            <label for="username" class="col-form-label col-sm-2 text-right">Username:</label>
            <div class="col-sm-5">
                <input type="text" class="form-control" name="username" formControlName="username"/>
                <div *ngIf="formErrors.username" class="alert alert-danger">
                  {{ formErrors.username }}
                </div>
            </div>
        </div>
        <div class="form-group row d-flex justify-content-center align-items-center">
            <label for="email" class="col-form-label col-sm-2 text-right">Email:</label>
            <div class="col-sm-5">
                <input type="text" class="form-control" name="emailAddress" formControlName="emailAddress" />
                <div *ngIf="formErrors.emailAddress" class="alert alert-danger">
                  {{ formErrors.emailAddress }}
                </div>
            </div>
        </div>
        <div class="form-group row d-flex justify-content-center align-items-center">
          <label for="fullName" class="col-form-label col-sm-2 text-right">Fullname:</label>
          <div class="col-sm-5">
              <input type="text" class="form-control" name="fullName" formControlName="fullName" />
              <div *ngIf="formErrors.fullName" class="alert alert-danger">
                {{ formErrors.fullName }}
              </div>
          </div>
        </div>
        <div class="form-group row d-flex justify-content-center align-items-center">
            <label for="age" class="col-form-label col-sm-2 text-right">Age:</label>
            <div class="col-sm-5">
                <input type="number" class="form-control" name="age" formControlName="age" />
                <div *ngIf="formErrors.age" class="alert alert-danger">
                  {{ formErrors.age }}
                </div>
            </div>
        </div>
      </div>
      <div class="card-footer">
        <button [disabled]="!updateUserForm.valid" class="btn btn-primary mr-2" type="submit">Update</button>
        <button [disabled]="isLoading" class="btn btn-danger mr-2" id="update-user-modal-close" type="button">Cancel</button>
      </div>
    </div>
  </form>

Let’s go through the note worthy sections of this template now:

  • The [formGroup]="updateUserForm" attribute in the <form> tag is how Angular binds the updateUserForm in the component’s .ts file to the template file’s <form> element
  • The *ngIf="updateUserForm" directive is used to indicate that only when the updateUserForm variable is instantiated can the template’s form content be rendered to the webpage
  • The (ngSubmit)="onSubmit()" form submission event handler binds the onSubmit() method to the form, so that upon form submission the onSubmit() method is executed
  • The formControlName directive that is present in the input tags of all the form fields binds those FormControl instances to the main FormGroup instance, which is updateUserForm in this case
  • The div elements with the *ngIf="formErrors.<fieldName>" directives and the {{ formErrors.<fieldName> }} text interpolations are used to display any form validation errors in the appropriate form group

Step 4: Add Update User Form to app.component.html

After finishing up the coding for the Update User Form component we now have to add a way to view this component. For the sake of simplicity we'll add the <app-update-user> tag to the app.component.html file like so:

<app-update-user></app-update-user>

Step 5: Run application

Finally, all that’s left to do is to run our application via:

npm start
Update User Form with pre-populated values

Figure 3: Update User Form with pre-populated values

Update User Form with empty fields triggers the 'required' validation

Figure 4: Update User Form with empty fields triggers the 'required' validation

Update User Form with invalid email triggers the 'email' validation

Figure 5: Update User Form with invalid email triggers the 'email' validation

Conclusion

Whew! You should have a fully functional User Update form now. If you do run into any issues, try analyzing the source code for this application in my GitHub repo by click here or post a comment about your issue in this post’s comments section and I’ll be sure to get back to you.