I am trying to add a manual date range input field in an Angular calendar picker. Specifically, I would like to have two date input fields above the calendar that can allow the user to type in the date range instead of scrolling through months on the calendar.
My current solution was to edit the default calendar header by defining a template and inserting my custom date input pickers. However, as soon as I added this default header component, all functionality was lost.
How can I keep the date range functionality from the default date range Angular component, but also add a manual date picker above the calendar?
Below is my custom header component, but does not have any functionality
Below is a working angular calendar component without the custom input range picker
calendar.html
@Component({
selector: 'calendar',
templateUrl: '.calendar.component.html',
styleUrls: ['.calendar.component.scss'],
providers: [
{
provide: DateAdapter,
useClass: CustomDateAdapter,
deps: [MAT_DATE_LOCALE, Platform],
},
],
})
export class DateSearchFilterComponent implements OnInit {
calendarHeader = CalendarHeaderComponent;
numberOfReschedules: number | null | undefined;
selectedDateOptions: DateFilterOptions = {
[DateFilterType.issueDateFilters]: [],
[DateFilterType.actionDateFilters]: [],
};
min: Date = new Date('January 1, 1980');
today: Date = new Date();
oneYearFromToday: Date = new Date();
dateFields: FilterGroup[] = [
{
id: DateFilterType.issueDateFilters,
label: 'CAP DATES',
fields: [
{
name: 'Origination Date',
dateType: IssueDateType.OriginationDate,
max: this.today,
},
{
name: 'Discovery Date',
dateType: IssueDateType.DiscoveryDate,
max: this.today,
},
{
name: 'Completion Date',
dateType: IssueDateType.CompletionDate,
max: this.today,
},
],
},
];
constructor(
private activatedRoute: ActivatedRoute,
private queryParameters: QueryParametersService,
public featureFlags: FeatureFlagsService,
private advancedSearch: AdvancedSearchService
) {
this.oneYearFromToday.setFullYear(this.oneYearFromToday.getFullYear() + 1);
if (this.featureFlags.isFeatureEnabled('action-dates-filter')) {
this.dateFields.push({
id: DateFilterType.actionDateFilters,
label: 'ACTION DATES',
fields: [
{
name: 'Origination Date',
dateType: ActionDateType.OriginationDate,
max: this.today,
},
{
name: 'Due Date',
dateType: ActionDateType.DueDate,
max: this.oneYearFromToday,
},
{
name: 'Completion Date',
dateType: ActionDateType.CompletionDate,
max: this.today,
},
{
name: 'Due Date Extension Count',
dateType: IssueDateType.CompletionDate,
max: Number.MAX_SAFE_INTEGER,
},
],
});
}
}
ngOnInit(): void {
this.subscribeToServiceOnInit();
}
subscribeToServiceOnInit = (): Subscription => {
return this.activatedRoute.queryParams
.pipe(
map((data) => this.queryParameters.deserializeQueryParams(data)),
untilDestroyed(this)
)
.subscribe((res) => {
let { issueDateFilters, actionDateFilters } = res;
if (!issueDateFilters) issueDateFilters = [];
if (!actionDateFilters) actionDateFilters = [];
this.selectedDateOptions = { issueDateFilters, actionDateFilters };
this.numberOfReschedules = res.numberOfReschedules;
});
};
/** Custom header component for datepicker. */
@Component({
selector: 'app-calendar-header'
],
template: `
<div class="calendar">
<div class="calendar-header">
<button (click)="previousClicked('month')">˂</button>
<span>{{ periodLabel }}</span>
<button (click)="nextClicked('month')">˃</button>
</div>
<div class="date-inputs">
<div>
<span>From</span>
<input type="date" />
</div>
<span class="date-inputs--dash">—</span>
<div>
<span>To</span>
<input type="date" />
</div>
</div>
</div>
`,
// changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CalendarHeaderComponent<D> implements OnDestroy {
private destroyed = new Subject<void>();
constructor(
private calendar: MatCalendar<D>,
private dateAdapter: DateAdapter<D>,
@Inject(MAT_DATE_FORMATS) private dateFormats: MatDateFormats,
cdr: ChangeDetectorRef
) {
calendar.stateChanges
.pipe(takeUntil(this.destroyed))
.subscribe(() => cdr.markForCheck());
}
ngOnDestroy() {
this.destroyed.next();
this.destroyed.complete();
}
get periodLabel() {
return this.dateAdapter
.format(this.calendar.activeDate, this.dateFormats.display.monthYearLabel)
.toLocaleUpperCase();
}
previousClicked(mode: 'month' | 'year') {
this.calendar.activeDate =
mode === 'month'
? this.dateAdapter.addCalendarMonths(this.calendar.activeDate, -1)
: this.dateAdapter.addCalendarYears(this.calendar.activeDate, -1);
}
nextClicked(mode: 'month' | 'year') {
this.calendar.activeDate =
mode === 'month'
? this.dateAdapter.addCalendarMonths(this.calendar.activeDate, 1)
: this.dateAdapter.addCalendarYears(this.calendar.activeDate, 1);
}
}
<mat-form-field
attr.data-cy="{{ input.label }} {{ field.name }}"
*ngIf="field.name !== 'Due Date Extension Count'"
appearance="outline"
class="date-input body-regular"
>
<mat-label class="select-label body-regular">
{{
!(determineFilterIndex(input.id, field.dateType) !== -1)
? 'Select...'
: ''
}}
</mat-label>
<!-- Input Fields -->
<mat-date-range-input
class="date-range-input"
[rangePicker]="picker"
[max]="field.max"
[min]="min"
#dateRangePicker
>
<!-- Start Date -->
<input
#start
matStartDate
(blur)="
handleBlur(
dateRangePicker.value?.start,
dateRangePicker.value?.end,
field.dateType,
input.id
)
"
(dateChange)="
handleDateSelection(
dateRangePicker.value?.start,
dateRangePicker.value?.end,
field.dateType,
input.id
)
"
placeholder="Start date"
class="body-small"
[value]="
determineCurrentFilterValue(input.id, field.dateType)?.startDate
"
/>
<!-- End Date -->
<input
#end
(blur)="
handleBlur(
dateRangePicker.value?.start,
dateRangePicker.value?.end,
field.dateType,
input.id
)
"
(dateChange)="
handleDateSelection(
dateRangePicker.value?.start,
dateRangePicker.value?.end,
field.dateType,
input.id
)
"
matEndDate
placeholder="End date"
class="body-small date-end"
[value]="
determineCurrentFilterValue(input.id, field.dateType)?.endDate
"
/>
</mat-date-range-input>
<!-- Calendar Icon Toggle Button -->
<mat-datepicker-toggle
attr.data-cy="{{
input.label.trim().toLowerCase().replace(' ', '-')
}}-{{ field.name.trim().toLowerCase().replace(' ', '-') }}"
class="calendar-suffix"
matSuffix
[for]="picker"
><mat-icon
class="calendar-icon"
matSuffix
matDatepickerToggleIcon
svgIcon="calendar"
></mat-icon
></mat-datepicker-toggle>
<!-- Custom Header -->
<mat-datepicker
#picker
[calendarHeaderComponent]="calendarHeader"
></mat-datepicker>
<!-- Clear and Apply buttons -->
<mat-date-range-picker #picker>
<mat-date-range-picker-actions>
<button
mat-button
matDateRangePickerCancel
class="clear-button"
(click)="clearFilter(input.id, field.dateType)"
>
Clear
</button>
<button
mat-raised-button
color="accent"
class="apply-button"
matDateRangePickerApply
>
Apply
</button>
</mat-date-range-picker-actions>
</mat-date-range-picker>
</mat-form-field>

