Angular 6 Format date from datepicker in Reactive form

3 min read 04-09-2024
Angular 6 Format date from datepicker in Reactive form


Formatting Dates from Datepickers in Angular 6 Reactive Forms

This article will address a common issue when working with Angular 6 reactive forms and datepickers: how to format the date value retrieved from the datepicker and maintain that format throughout the form's lifecycle.

The Problem

As described by user Anonymous on Stack Overflow https://stackoverflow.com/questions/67190210/angular-6-format-date-from-datepicker-in-reactive-form, the issue is that while the date value is initially formatted correctly after selection from the datepicker, subsequent edits in other form fields revert the date back to a Moment Object, only displaying the correct format for the last selected date.

Understanding the Issue

The core problem lies in the way Angular handles form updates and the inherent nature of Moment objects. When a Moment object is assigned to a form control, Angular automatically tracks changes to its underlying value. However, since Moment is mutable, any changes made to the object (like formatting) don't trigger a new value emission for Angular to recognize.

The Solution

To address this, we need to ensure that any date changes are reflected as distinct values that Angular can track. The most effective solution is to work with plain JavaScript Date objects or string representations of the formatted date. Here's a modified version of the provided code:

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, FormArray, Validators } from '@angular/forms';

@Component({
  selector: 'app-user-form',
  templateUrl: './user-form.component.html',
  styleUrls: ['./user-form.component.css']
})
export class UserFormComponent implements OnInit {
  userForm: FormGroup;

  ngOnInit() {
    this.createUserForm();
  }

  createUserForm() {
    this.userForm = this.formBuilder.group({
      users: this.formBuilder.array([this.createEachUser()])
    });
  }

  createEachUser(): any {
    return this.formBuilder.group({
      name: this.formBuilder.control(null, [Validators.required]),
      start: this.formBuilder.control(null, [Validators.required]),
      end: this.formBuilder.control(null, [Validators.required]),
    });
  }

  // Format date on date change
  formatDate(date: Date, startend: string, i: number) {
    const formattedDate = new Date(date).toISOString().split('T')[0];
    // Access the form control directly for the specific date field
    this.userForm.get(`users.${i}.${startend}`).setValue(formattedDate);
  }

  submit() {
    console.log(this.userForm.value);
  }

  constructor(private formBuilder: FormBuilder) {}
}

Explanation

  • formatDate Function: This function takes the selected date from the datepicker (date), the field name (startend), and the array index (i) as parameters. It then formats the date to the desired YYYY/MM/DD format and assigns it directly to the specific form control using setValue.
  • dateChange Event Binding: The dateChange event of each datepicker is now bound to the formatDate function to ensure the date is formatted and updated correctly within the form.

HTML Changes

The HTML remains largely the same, but we will remove the convertDate function call and directly bind the dateChange event to formatDate.

<form (ngSubmit)="submit()" [formGroup]="userForm" novalidate>
  <div formArrayName="users" *ngFor="let user of userForm.controls.users?.value; let i = index;">
    <div class="col">
      <label class="label">Name:</label>
      <input class="form-control" type="text" id="name" name="name" formControlName="name" required>
    </div>

    <div class="col">
      <label class="label">Start:</label>
      <input class="form-control" [matDatepicker]="startpicker" (click)="startpicker.open()" id="start" name="start" formControlName="start" (dateChange)="formatDate($event, 'start', i)" required>
      <mat-datepicker-toggle matSuffix [for]="startpicker"></mat-datepicker-toggle>
      <mat-datepicker #startpicker></mat-datepicker>
    </div>

    <div class="col">
      <label class="label">End:</label>
      <input class="form-control" [matDatepicker]="endpicker" (click)="endpicker.open()" id="end" name="end" formControlName="end" (dateChange)="formatDate($event, 'end', i)" required>
      <mat-datepicker-toggle matSuffix [for]="endpicker"></mat-datepicker-toggle>
      <mat-datepicker #endpicker></mat-datepicker>
    </div>
  </div>

  <button type="submit">submit</button>
</form>

By directly assigning formatted dates to the form controls and avoiding the use of Moment objects within the form, we ensure that Angular correctly tracks the date values and avoids the issue of the date reverting to a Moment object.

This approach ensures that the date format is maintained throughout the form's lifecycle and avoids unexpected behavior during form submission.