How to Use Angular Material Datepicker Custom Date Format

In the previous article,  we have implemented server-side pagination. Now we are gonna add a date field and use the Angular Material Datepicker with custom date format. Then, we will store the selected date in the backend, retrieve and format the date to display it on the list page.

What You’ll Build

Angular Frontend

Add/Edit Page

Angular Material Datepicker

List Page

Spring Boot Angular Custom Date Format

Spring Boot Backend

Modify the Product and ProductRequest classes to include the new date field as java.time.LocalDate type.

What You’ll Need

Run the following checklist before you begin the implementation:

Angular Client Implementation

Add Angular Material Datepicker Dependencies

Angular Material Datepicker requires the material-moment-adapter library in order to customize the date format. This library in turn depends on the moment library. So let’s install both of them

Install Angular Material Moment Adapter

ng add @angular/material-moment-adapter

Output

i Using package manager: npm
√ Found compatible package version: @angular/[email protected].
√ Package information loaded.
The package @angular/[email protected] will be installed and executed.
Would you like to proceed? Yes
√ Package successfully installed.

Install Moment

ng add moment

Output

i Using package manager: npm
√ Found compatible package version: [email protected].
√ Package information loaded.
The package [email protected] will be installed and executed.
Would you like to proceed? Yes
√ Package successfully installed.

Modify Product Model Class

Lets add a date field validFrom as type string.

product.ts

export interface Product {
	id: number;
	name: string;
	version: string;
	edition: string;
	description: string;
	validFrom: string;
}

Modify Product Add/Edit Component

add-edit.component.ts

import { Component, OnInit } from '@angular/core';
import { ProductService } from '../_services/product.service';
import { Router, ActivatedRoute } from '@angular/router';
import { FormGroup,  FormBuilder,  Validators } from '@angular/forms';
import { first } from 'rxjs/operators';
import { DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE } from '@angular/material/core';
import { MomentDateAdapter, MAT_MOMENT_DATE_ADAPTER_OPTIONS } from '@angular/material-moment-adapter';

export const APP_DATE_FORMATS = {
	parse: {
		dateInput: 'DD/MM/YYYY',
	},
	display: {
		dateInput: 'DD/MM/YYYY',
		monthYearLabel: 'MMMM YYYY',
		dateA11yLabel: 'LL',
		monthYearA11yLabel: 'MMMM YYYY'
	},
};

@Component({
	templateUrl: './add-edit.component.html', 
	providers: [
			{ provide: MAT_DATE_FORMATS, useValue: APP_DATE_FORMATS },
			{ provide: DateAdapter, useClass: MomentDateAdapter, deps: [MAT_DATE_LOCALE, MAT_MOMENT_DATE_ADAPTER_OPTIONS]},
			{ provide: MAT_MOMENT_DATE_ADAPTER_OPTIONS, useValue: { useUtc: true } }
		]})   
export class ProductAddEditComponent implements OnInit {
  
	id: any;
	form: FormGroup;
	errorMessage;
   
	constructor(public productService: ProductService, private router: Router, private fb: FormBuilder, private route: ActivatedRoute) {
		this.createForm();
	}
  
	ngOnInit(): void {
		this.id = this.route.snapshot.paramMap.get('id');
		console.log(this.id);
		if(this.id){
			this.productService.find(this.id).subscribe(x => this.form.patchValue(x));
		}
	}
	
	createForm() {
		this.form = this.fb.group({
		name: ['', [
			Validators.required,
			Validators.minLength(3),
			Validators.maxLength(20),
		] ],
		description: ['', [
			Validators.required,
			Validators.minLength(3),
			Validators.maxLength(100),
		] ],
		version: ['', Validators.required],
		edition: ['', Validators.maxLength(20)],
		validFrom: ['', Validators.required]
		});
	}
	
	onSubmit(){
		var response = this.id ? this.productService.update(this.id, this.form.value) : this.productService.create(this.form.value);
		
		response.subscribe(
		data => {
			console.log('Product created / updated successfully!');
			this.router.navigateByUrl('products/list');
		}
		,
		err => {
			this.errorMessage = err.error.message;
		}
		);
	}
}
Let’s break it down to understand what each change does:

To create a Datepicker, we need a DateAdapter that can be provided by MatNativeDateModuleMatMomentDateModule, or a custom implementation.

The MatNativeDateModule uses native JavaScript Date and One of the biggest shortcomings of the native Date object is the inability to set the parse format.

The MatMomentDateModule uses moment from moment.js. So the MomentDateAdapter provided by this module or a custom DateAdapter can set parse format. It is recommended to use an adapter based on a more robust formatting and parsing library. 

The MAT_DATE_FORMATS object is a collection of formats that the datepicker uses when parsing and displaying dates. These formats are passed through to the DateAdapter so we need to make sure that the format objects we’re using are compatible with the DateAdapter used in the app.

If you want to use one of the DateAdapters that ships with Angular Material, but use your own MAT_DATE_FORMATS, you can import the NativeDateModule or MomentDateModule. These modules are identical to the “Mat”-prefixed versions (MatNativeDateModule and MatMomentDateModule) except they do not include the default formats. This is what we have done now:

  • Imported MomentDateModule since we don’t want the default formats provided by MatMomentDateModule
  • Defined our own APP_DATE_FORMATS with DD/MM/YYYY format for parsing and displaying by Datepicker and configured it as MAT_DATE_FORMATS provider.
  • Configured MomentDateAdapter as the DateAdapter provider.
  • By default the MomentDateAdapter creates dates in our time zone specific locale. So we have changed the default behaviour to parse dates as UTC by providing the MAT_MOMENT_DATE_ADAPTER_OPTIONS and setting it to useUtc: true.
  • Added the date field into the form group.

add-edit.component.html

The Angular Material datepicker allows users to enter a date either through text input or by choosing a date from the calendar. It is composed of a text input and a calendar pop-up, connected via the matDatepicker property on the text input.

<div class="row">
				<div class="col s12">
					<label for="validFrom">Valid From</label>
					<mat-form-field appearance="outline"> <mat-label>Choose a date</mat-label> <input matInput [matDatepicker]="datepicker"
						formControlName="validFrom" name="validFrom"> <mat-datepicker-toggle matSuffix [for]="datepicker" style='width: 1em'></mat-datepicker-toggle> <mat-datepicker
						#datepicker> <mat-datepicker-actions>
					<button mat-button matDatepickerCancel>Cancel</button>
					<button mat-raised-button color="primary" matDatepickerApply>Apply</button>
					</mat-datepicker-actions> </mat-datepicker> </mat-form-field>
					<div class="alert-danger">
						<div *ngIf="form.controls.validFrom.touched && form.controls.validFrom.errors?.required">* Required</div>
					</div>
				</div>
			</div>

Modify Product List Component

list.component.html

The date is retrieved from the backend in ISO format. So we are formatting the date in dd/MM/yyyy and displaying in the list here.

<table class="table table-bordered p-0 m-0">
			<thead>
				<tr>
					<th scope="col">#</th>
					<th scope="col">Name</th>
					<th scope="col">Description</th>
					<th scope="col">Version</th>
					<th scope="col">Edition</th>
					<th scope="col">License Valid From</th>
					<th scope="col">Action</th>
				</tr>
			</thead>
			<tbody>
				<tr scope="row" *ngFor="let product of products">
					<td>{{ product?.id }}</td>
					<td>{{ product?.name }}</td>
					<td>{{ product?.description}}</td>
					<td>{{ product?.version }}</td>
					<td>{{ product?.edition }}</td>
					<td>{{ product?.validFrom | date: 'dd/MM/yyyy'}}</td>
					<td><a href="#" [routerLink]="['/products/', product.id, 'view']" class="btn btn-info m-1">View</a> <a href="#"
							[routerLink]="['/products/', product.id, 'edit']" class="btn btn-primary m-1">Edit</a>
						<button type="button" (click)="deleteProduct(product.id)" class="btn btn-danger m-1">Delete</button></td>
				</tr>
				<tr scope="row" *ngIf="products.length === 0">
					<td colspan="6" class="text-center">No record found</td>
				</tr>
			</tbody>
		</table>

Define Module

app.module.ts

Import and add MatDatepickerModule, MatInputModule, and MomentDateModule in the module declarations

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatInputModule } from '@angular/material/input';
import { MomentDateModule } from '@angular/material-moment-adapter';
import { AppComponent } from './app.component';
import { LoginComponent } from './login/login.component';
import { RegisterComponent } from './register/register.component';
import { HomeComponent } from './home/home.component';
import { ProfileComponent } from './profile/profile.component';
import { BoardAdminComponent } from './board-admin/board-admin.component';
import { BoardModeratorComponent } from './board-moderator/board-moderator.component';
import { BoardUserComponent } from './board-user/board-user.component';
import { TotpComponent } from './totp/totp.component';
import { OrderComponent } from './order/order.component';
import { TokenComponent } from './register/token.component';
import { ProductListComponent } from './products/list.component';
import { ProductViewComponent } from './products/view.component';
import { ProductAddEditComponent } from './products/add-edit.component';


import { authInterceptorProviders } from './_helpers/auth.interceptor';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

@NgModule({
  declarations: [
    AppComponent,
    LoginComponent,
    RegisterComponent,
    HomeComponent,
    ProfileComponent,
    BoardAdminComponent,
    BoardModeratorComponent,
    BoardUserComponent,
    TotpComponent,
    OrderComponent,
    TokenComponent,
	ProductListComponent,
    ProductViewComponent,
    ProductAddEditComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    FormsModule,
	ReactiveFormsModule,
    HttpClientModule,
    MatPaginatorModule,
    MatDatepickerModule, 
    MatInputModule,
    MomentDateModule,
    BrowserAnimationsModule
  ],
  providers: [authInterceptorProviders],
  bootstrap: [AppComponent]
})
export class AppModule { }

Spring Boot Backend Implementation

Add Date Field into Entity and DTO Classes

Product.java

package com.javachinna.model;

import java.time.LocalDate;

import javax.persistence.Entity;
import javax.persistence.Table;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@Entity
@Table(name = "product")
@Getter
@Setter
@ToString
@RequiredArgsConstructor
@NoArgsConstructor
public class Product extends BaseEntity {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1493867573442690205L;
	@NonNull
	private String name;
	@NonNull
	private String version;
	@NonNull
	private String edition;
	@NonNull
	private String description;
	@NonNull
	private LocalDate validFrom;
}

ProductRequest.java

package com.javachinna.dto;

import java.time.LocalDate;

import javax.validation.constraints.NotBlank;

import lombok.Data;

@Data
public class ProductRequest {
	@NotBlank
	private String name;
	@NotBlank
	private String version;
	private String edition;
	@NotBlank
	private String description;
	private LocalDate validFrom;
}

Run Spring Boot App with Maven

You can run the application with mvn clean spring-boot:run and the REST API services can be accessed via http://localhost:8080

Run the Angular App

You can run this App with the below command and hit the URL http://localhost:8081/ in the browser.

ng serve --port 8081

Source Code

https://github.com/JavaChinna/angular-spring-boot-datepicker

Conclusion

That’s all folks. In this article, we have added a date field with datepicker support using Angular Material in our CRUD application.

Thank you for reading.

Leave a Reply