libs/ui/date-range/src/lib/date-range/date-range.component.ts
This is the date-range UI Component
<ts-date-range
[dateFormGroup]="myForm.get('dateRange')"
endMinDate="{{ new Date(2017, 4, 1) }}"
endMaxDate="{{ new Date(2017, 4, 30) }}"
[isDisabled]="true"
startingView="year"
startMinDate="{{ new Date(2017, 4, 1) }}"
startMaxDate="{{ new Date(2017, 4, 30) }}"
theme="primary"
(startSelected)="myMethod($event)"
(endSelected)="myMethod($event)"
(dateRangeChange)="myMethod($event)"
></ts-date-range>
<example-url>https://getterminus.github.io/ui-demos-release/components/date-range</example-url>
changeDetection | ChangeDetectionStrategy.OnPush |
encapsulation | ViewEncapsulation.None |
exportAs | tsDateRange |
host | { |
selector | ts-date-range |
styleUrls | ./date-range.component.scss |
templateUrl | ./date-range.component.html |
Properties |
|
Methods |
|
Inputs |
Outputs |
Accessors |
constructor(changeDetectorRef: ChangeDetectorRef)
|
||||||
Parameters :
|
dateFormGroup | |
Define the form group to attach the date range to |
dateLocale | |
Type : string | undefined
|
|
Define the date locale |
endMaxDate | |
Type : Date | undefined
|
|
Define the max date for the end date |
endMinDate | |
Type : Date | undefined
|
|
Define the min date for the end date |
isDisabled | |
Default value : false
|
|
Define if the range should be disabled |
startingView | |
Type : "month" | "year"
|
|
Default value : 'month'
|
|
Define the starting view for both datepickers |
startMaxDate | |
Type : Date | undefined
|
|
Define the max date for the starting date |
startMinDate | |
Type : Date | undefined
|
|
Define the min date for the starting date |
theme | |
Type : TsStyleThemeTypes
|
|
Default value : 'primary'
|
|
Define the component theme |
dateRangeChange | |
Type : EventEmitter<TsDateRange>
|
|
Event emitted anytime the range is changed |
endSelected | |
Type : EventEmitter<Date | undefined>
|
|
Output the end date when selected |
startSelected | |
Type : EventEmitter<Date | undefined>
|
|
Output the start date when selected |
Public endBlur | ||||||||
endBlur(date: Date | undefined)
|
||||||||
Update dates when the end date input receives a blur event
Parameters :
Returns :
void
|
Public endDateSelected | ||||||
endDateSelected(date: Date)
|
||||||
Emit the selected end date and date range
Parameters :
Returns :
void
|
Public startBlur | ||||||||
startBlur(date: Date | undefined)
|
||||||||
Update dates when the start date input receives a blur event
Parameters :
Returns :
void
|
Public startDateSelected | ||||||
startDateSelected(date: Date)
|
||||||
Emit the selected start date and date range
Parameters :
Returns :
void
|
Public endLabel |
Type : string
|
Default value : 'End date'
|
Define the end date label |
Public internalEndControl |
Default value : new FormControl()
|
The internal FormControl to manage the end date |
Public internalStartControl |
Default value : new FormControl()
|
The internal FormControl to manage the start date |
Public separator |
Type : string
|
Default value : '-'
|
Define the separator between the two date inputs |
Public startLabel |
Type : string
|
Default value : 'Start date'
|
Define the start date label |
endDateControl |
getendDateControl()
|
Provide quick access to the endDate form control
Returns :
AbstractControl
|
startDateControl |
getstartDateControl()
|
Provide quick access to the startDate form control
Returns :
AbstractControl
|
dateFormGroup | ||||
getdateFormGroup()
|
||||
setdateFormGroup(value)
|
||||
Define the form group to attach the date range to
Parameters :
Returns :
void
|
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
EventEmitter,
Input,
OnDestroy,
OnInit,
Output,
ViewEncapsulation,
} from '@angular/core';
import {
AbstractControl,
FormControl,
FormGroup,
} from '@angular/forms';
import { BehaviorSubject } from 'rxjs';
import { untilComponentDestroyed } from '@terminus/fe-utilities';
import { TsStyleThemeTypes } from '@terminus/ui-utilities';
/**
* Define the structure of the date range object used by {@link TsDateRangeComponent}
*/
export interface TsDateRange {
/**
* The start date of the range
*/
start: Date | undefined;
/**
* The end date of the range
*/
end: Date | undefined;
}
/**
* This is the date-range UI Component
*
* @example
* <ts-date-range
* [dateFormGroup]="myForm.get('dateRange')"
* endMinDate="{{ new Date(2017, 4, 1) }}"
* endMaxDate="{{ new Date(2017, 4, 30) }}"
* [isDisabled]="true"
* startingView="year"
* startMinDate="{{ new Date(2017, 4, 1) }}"
* startMaxDate="{{ new Date(2017, 4, 30) }}"
* theme="primary"
* (startSelected)="myMethod($event)"
* (endSelected)="myMethod($event)"
* (dateRangeChange)="myMethod($event)"
* ></ts-date-range>
*
* <example-url>https://getterminus.github.io/ui-demos-release/components/date-range</example-url>
*/
@Component({
selector: 'ts-date-range',
templateUrl: './date-range.component.html',
styleUrls: ['./date-range.component.scss'],
host: { class: 'ts-date-range' },
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
exportAs: 'tsDateRange',
})
export class TsDateRangeComponent implements OnInit, OnDestroy {
/**
* Getter to return the date range as an object
*
* @returns The current date range
*/
private get dateRange(): TsDateRange {
return {
start: this.startDateControl.value,
end: this.endDateControl.value,
};
}
/**
* Provide quick access to the endDate form control
*/
public get endDateControl(): AbstractControl {
const ctrl: AbstractControl | null = this.dateFormGroup ? this.dateFormGroup.get('endDate') : null;
return ctrl ? ctrl : this.internalEndControl;
}
/**
* Expose the minimum date for the endDate
*
* NOTE: `any` is used since we cannot seem to use union types in a BehaviorSubject and the value could be a Date or undefined
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public endMinDate$: BehaviorSubject<any> = new BehaviorSubject(undefined);
/**
* Define the end date label
*/
public endLabel = 'End date';
/**
* The internal FormControl to manage the end date
*/
public internalEndControl = new FormControl();
/**
* The internal FormControl to manage the start date
*/
public internalStartControl = new FormControl();
/**
* Define the separator between the two date inputs
*/
public separator = '-';
/**
* Provide quick access to the startDate form control
*/
public get startDateControl(): AbstractControl {
const ctrl: AbstractControl | null = this.dateFormGroup ? this.dateFormGroup.get('startDate') : null;
return ctrl ? ctrl : this.internalStartControl;
}
/**
* Expose the maximum date for the startDate
*
* NOTE: `any` is used since we cannot seem to use union types in a BehaviorSubject and the value could be a Date or undefined
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public startMaxDate$: BehaviorSubject<any> = new BehaviorSubject(undefined);
/**
* Define the start date label
*/
public startLabel = 'Start date';
/**
* Define the form group to attach the date range to
*
* @param value
*/
@Input()
public set dateFormGroup(value: FormGroup | AbstractControl | undefined) {
this._dateFormGroup = value;
}
public get dateFormGroup(): FormGroup | AbstractControl | undefined {
return this._dateFormGroup;
}
private _dateFormGroup: FormGroup | AbstractControl | undefined;
/**
* Define the date locale
*/
@Input()
public dateLocale: string | undefined;
/**
* Define the max date for the end date
*/
@Input()
public endMaxDate: Date | undefined;
/**
* Define the min date for the end date
*/
@Input()
public endMinDate: Date | undefined;
/**
* Define if the range should be disabled
*/
@Input()
public isDisabled = false;
/**
* Define the starting view for both datepickers
*/
@Input()
public startingView: 'month' | 'year' = 'month';
/**
* Define the max date for the starting date
*/
@Input()
public startMaxDate: Date | undefined;
/**
* Define the min date for the starting date
*/
@Input()
public startMinDate: Date | undefined;
/**
* Define the component theme
*/
@Input()
public theme: TsStyleThemeTypes = 'primary';
/**
* Event emitted anytime the range is changed
*/
@Output()
public readonly dateRangeChange: EventEmitter<TsDateRange> = new EventEmitter();
/**
* Output the end date when selected
*/
@Output()
public readonly endSelected: EventEmitter<Date | undefined> = new EventEmitter();
/**
* Output the start date when selected
*/
@Output()
public readonly startSelected: EventEmitter<Date | undefined> = new EventEmitter();
constructor(private changeDetectorRef: ChangeDetectorRef) { }
/**
* Seed initial date range values
*/
public ngOnInit(): void {
// Seed values from a passed in form group
if (this.dateFormGroup) {
this.initializeMinAndMax(this.dateFormGroup);
}
// istanbul ignore else
if (!this.endDateControl.value) {
this.startMaxDate$.next(this.startMaxDate);
}
// istanbul ignore else
if (!this.startDateControl.value) {
this.endMinDate$.next(this.endMinDate);
}
this.setUpFormControlSync();
}
/**
* Needed for untilComponentDestroyed
*/
public ngOnDestroy(): void {}
/**
* Set up subscriptions to sync the internal FormControl to the external FormControl
*/
private setUpFormControlSync(): void {
if (!this.dateFormGroup) {
return;
}
const startCtrl = this.dateFormGroup.get('startDate');
const endCtrl = this.dateFormGroup.get('endDate');
if (!startCtrl || !endCtrl) {
return;
}
this.changeDetectorRef.detectChanges();
// HACK: This is to fix on an initial load, date range value isn't populating correctly.
this.internalStartControl.setValue(startCtrl.value);
this.internalEndControl.setValue(endCtrl.value);
// START DATE
startCtrl.valueChanges.pipe(untilComponentDestroyed(this)).subscribe(value => {
this.internalStartControl.setValue(value);
this.endMinDate$.next(value);
});
startCtrl.statusChanges.pipe(untilComponentDestroyed(this)).subscribe(() => {
this.internalStartControl.setErrors(startCtrl.errors);
});
// END DATE
endCtrl.valueChanges.pipe(untilComponentDestroyed(this)).subscribe(value => {
this.internalEndControl.setValue(value);
this.startMaxDate$.next(value);
});
endCtrl.statusChanges.pipe(untilComponentDestroyed(this)).subscribe(() => {
this.internalEndControl.setErrors(endCtrl.errors);
});
this.changeDetectorRef.detectChanges();
}
/**
* Set up initial min and max dates
*
* @param formGroup - The date form group
*/
private initializeMinAndMax(formGroup: FormGroup | AbstractControl): void {
const startControl: AbstractControl | null = formGroup.get('startDate');
const endControl: AbstractControl | null = formGroup.get('endDate');
const startControlValue: Date | undefined = startControl ? startControl.value /* istanbul ignore next - Unreachable */ : undefined;
const endControlValue: Date | undefined = endControl ? endControl.value /* istanbul ignore next - Unreachable */ : undefined;
const startValueToUse = startControlValue || this.endMinDate;
const endValueToUse = endControlValue || this.endMinDate;
this.endMinDate$.next(startValueToUse);
this.startMaxDate$.next(endValueToUse);
}
/**
* Emit the selected start date and date range
*
* @param date
*/
public startDateSelected(date: Date): void {
if (date) {
this.endMinDate$.next(date);
// Update the form value if a formGroup was passed in
// istanbul ignore else
if (this.dateFormGroup && this.startDateControl) {
this.startDateControl.setValue(date);
}
this.startSelected.emit(date);
this.dateRangeChange.emit(this.dateRange);
} else {
// If no startDate was selected, reset to the original endMinDate
this.endMinDate$.next(this.endMinDate);
}
}
/**
* Emit the selected end date and date range
*
* @param date
*/
public endDateSelected(date: Date): void {
if (date) {
this.startMaxDate$.next(date);
// Update the form value if a formGroup was passed in
// istanbul ignore else
if (this.dateFormGroup && this.endDateControl) {
this.endDateControl.setValue(date);
}
this.endSelected.emit(date);
this.dateRangeChange.emit(this.dateRange);
} else {
// If no endDate was selected, reset to the original startMaxDate
this.startMaxDate$.next(this.startMaxDate);
}
}
/**
* Update dates when the start date input receives a blur event
*
* @param date - The date entered
*/
public startBlur(date: Date | undefined): void {
const ctrl = this.dateFormGroup ? this.dateFormGroup.get('startDate') /* istanbul ignore next - Untestable */ : null;
const value = date ? date : null;
// Update the max date for the end date control
this.endMinDate$.next(value);
// Update the consumer's control
// istanbul ignore else
if (ctrl) {
ctrl.setValue(value);
ctrl.markAsTouched();
ctrl.updateValueAndValidity();
this.dateRangeChange.emit(this.dateRange);
}
}
/**
* Update dates when the end date input receives a blur event
*
* @param date - The date entered
*/
public endBlur(date: Date | undefined): void {
const ctrl = this.dateFormGroup ? this.dateFormGroup.get('endDate') /* istanbul ignore next - Untestable */ : null;
const value = date ? date : null;
// Update the max date for the start date control
this.startMaxDate$.next(value);
// Update the consumer's control
// istanbul ignore else
if (ctrl) {
ctrl.setValue(value);
ctrl.markAsTouched();
ctrl.updateValueAndValidity();
this.dateRangeChange.emit(this.dateRange);
}
}
}