File

libs/core/dynamic-form/date/src/date.component.ts

Extends

DfBaseComponent

Implements

OnInit AfterViewInit

Metadata

Index

Properties
Methods

Constructor

constructor()

Methods

addPanelClass
addPanelClass(configValue: DfDateConfig, panelClass: string)
Parameters :
Name Type Optional
configValue DfDateConfig No
panelClass string No
Returns : void
configureDateFieldByMode
configureDateFieldByMode(configValue: DfDateConfig)
Parameters :
Name Type Optional
configValue DfDateConfig No
Returns : void
formatDateByMode
formatDateByMode(date: Date)
Parameters :
Name Type Optional
date Date No
Returns : string
isValidDate
isValidDate(dateValue: any)
Parameters :
Name Type Optional
dateValue any No
Returns : Date
monthSelected
monthSelected(value)
Parameters :
Name Optional
value No
Returns : void
onBlur
onBlur(event: FocusEvent)
Parameters :
Name Type Optional
event FocusEvent No
Returns : void
onDatePickerClosed
onDatePickerClosed()
Returns : void
onInputBlur
onInputBlur()
Returns : void
setSelectedDate
setSelectedDate(value)
Parameters :
Name Optional
value No
Returns : void
startAt
startAt(field?: DfDateConfig)
Parameters :
Name Type Optional
field DfDateConfig Yes
Returns : Date | undefined
yearSelected
yearSelected(value)
Parameters :
Name Optional
value No
Returns : void

Properties

config
Type : InputSignalWithTransform<DfDateConfig | DfDateConfig>
Default value : input.required({ transform: (config: DfDateConfig) => { switch (config.value) { case DfDateDefaultValues.Today: // Changing the input config leads to a ExpressionChangedAfterItHasBeenCheckedError. So we use a copy: config = JSON.parse(JSON.stringify(config)); config.value = dayjs().format(ISO_STRING_FORMAT); break; case DfDateDefaultValues.Yesterday: config = JSON.parse(JSON.stringify(config)); config.value = dayjs().subtract(1, 'day').format(ISO_STRING_FORMAT); break; case DfDateDefaultValues.Tomorrow: config = JSON.parse(JSON.stringify(config)); config.value = dayjs().add(1, 'day').format(ISO_STRING_FORMAT); break; default: break; } return config; } })
Inherited from DfBaseComponent
Defined in DfBaseComponent:52
control
Default value : input<UntypedFormControl>(new UntypedFormControl())
Inherited from DfBaseComponent
Defined in DfBaseComponent:51
dateField
Type : NxDatefieldDirective<>
Decorators :
@ViewChild('dateField', {static: false})
datePicker
Type : NxDatepickerComponent<>
Decorators :
@ViewChild('picker')
datePickerToggle
Type : ElementRef<>
Decorators :
@ViewChild('pickerToggle', {read: ElementRef})
inputField
Type : NxInputDirective
Decorators :
@ViewChild(NxInputDirective, {static: false})
Optional maxDate
Type : Date
Optional minDate
Type : Date
parseFormatToken
Default value : inject(Df_DATE_PICKER_PARSE_FORMAT, { optional: true })
startView
Default value : signal<DfDateStartViewTypeFullDate | DfDateStartViewTypeMonthYearOnly>( DfDateStartViewTypeFullDate.Month )
aclResource
Type : string
Inherited from DfBaseComponent
Defined in DfBaseComponent:56
componentOrControlInitFinished
Default value : new ReplaySubject<AbstractControl | undefined>(1)
Inherited from DfBaseComponent

This ReplaySubject is provided to emit the control once it is initialized.

formAclPath
Default value : input<string>()
Inherited from DfBaseComponent
Defined in DfBaseComponent:89
Readonly formEvent
Default value : output<DfEventPayload>()
Inherited from DfBaseComponent
Defined in DfBaseComponent:98

Emits when events associated to the form control happen.

The emitted object contains the data necessary to uniquely identify the event (field id and event type). It also contains the event data.

isRetailChannel
Default value : input<boolean>()
Inherited from DfBaseComponent
Defined in DfBaseComponent:91
validationConfigs
Default value : input<ValidationConfig[] | undefined>()
Inherited from DfBaseComponent
Defined in DfBaseComponent:87
import { ValidationConfig } from '@allianz/taly-core';
import { DfBaseComponent } from '@allianz/taly-core/dynamic-form';
import {
  AfterViewInit,
  Component,
  effect,
  ElementRef,
  InjectionToken,
  input,
  InputSignalWithTransform,
  OnInit,
  signal,
  ViewChild,
  inject
} from '@angular/core';
import { UntypedFormControl } from '@angular/forms';
import { NxDatefieldDirective, NxDatepickerComponent } from '@allianz/ng-aquila/datefield';
import { NxInputDirective } from '@allianz/ng-aquila/input';
import dayjs from 'dayjs';
import {
  DfDateConfig,
  DfDateDefaultValues,
  DfDateStartViewTypeFullDate,
  DfDateStartViewTypeMonthYearOnly
} from './date.model';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';

export const Df_DATE_PICKER_PARSE_FORMAT: InjectionToken<string> = new InjectionToken<string>(
  'Df_DATE_PICKER_PARSE_FORMAT'
);

const ISO_STRING_FORMAT = 'YYYY-MM-DD';

/*
Please see the readme for a background explanation of this implementation!
*/

@Component({
  selector: 'df-date',
  styleUrls: ['./date.component.scss'],
  templateUrl: './date.component.html',
  standalone: false
})
export class DfDateComponent
  extends DfBaseComponent<DfDateConfig>
  implements OnInit, AfterViewInit
{
  parseFormatToken = inject(Df_DATE_PICKER_PARSE_FORMAT, { optional: true });
  private el = inject(ElementRef);

  override control = input<UntypedFormControl>(new UntypedFormControl());
  override config: InputSignalWithTransform<DfDateConfig, DfDateConfig> = input.required({
    transform: (config: DfDateConfig) => {
      switch (config.value) {
        case DfDateDefaultValues.Today:
          // Changing the input config leads to a ExpressionChangedAfterItHasBeenCheckedError. So we use a copy:
          config = JSON.parse(JSON.stringify(config));
          config.value = dayjs().format(ISO_STRING_FORMAT);
          break;
        case DfDateDefaultValues.Yesterday:
          config = JSON.parse(JSON.stringify(config));
          config.value = dayjs().subtract(1, 'day').format(ISO_STRING_FORMAT);
          break;
        case DfDateDefaultValues.Tomorrow:
          config = JSON.parse(JSON.stringify(config));
          config.value = dayjs().add(1, 'day').format(ISO_STRING_FORMAT);
          break;
        default:
          break;
      }
      return config;
    }
  });

  minDate?: Date;
  maxDate?: Date;
  startView = signal<DfDateStartViewTypeFullDate | DfDateStartViewTypeMonthYearOnly>(
    DfDateStartViewTypeFullDate.Month
  );

  @ViewChild('dateField', { static: false }) dateField!: NxDatefieldDirective<unknown>;
  @ViewChild(NxInputDirective, { static: false }) inputField!: NxInputDirective;
  @ViewChild('pickerToggle', { read: ElementRef })
  datePickerToggle!: ElementRef<unknown>;
  @ViewChild('picker') datePicker!: NxDatepickerComponent<unknown>;

  constructor() {
    super();
    effect(() => {
      const validationConfigs = this.validationConfigs();
      if (validationConfigs) {
        this.disableInvalidDates(validationConfigs);
      }
    });
  }

  override ngOnInit() {
    super.ngOnInit();

    this.emitFormControlEventOnValueChanges();
  }

  ngAfterViewInit(): void {
    const configValue = this.config();
    // First we set the inputs that are not always there:
    if (configValue.placeholder) {
      this.inputField.placeholder = configValue.placeholder;
    }
    this.configureDateFieldByMode(configValue);
  }

  configureDateFieldByMode(configValue: DfDateConfig) {
    const monthYearOnlyFormat = 'YYYY-MM';
    const yearOnlyFormat = 'YYYY';
    switch (configValue.mode) {
      case 'monthYearOnly':
        this.dateField.parseFormat = configValue.parseFormat || monthYearOnlyFormat;
        this.dateField.displayFormat = configValue.displayFormat || monthYearOnlyFormat;
        this.addPanelClass(configValue, 'month-year-only');
        this.startView.set(configValue.startView || DfDateStartViewTypeMonthYearOnly.MultiYear);
        break;
      case 'yearOnly':
        this.dateField.parseFormat = yearOnlyFormat;
        this.dateField.displayFormat = yearOnlyFormat;
        this.addPanelClass(configValue, 'year-only');
        this.startView.set(DfDateStartViewTypeFullDate.MultiYear);
        break;
      case 'fullDate':
        if (configValue.parseFormat) {
          this.dateField.parseFormat = configValue.parseFormat;
        } else if (this.parseFormatToken) {
          this.dateField.parseFormat = this.parseFormatToken;
        }

        if (configValue.displayFormat) {
          this.dateField.displayFormat = configValue.displayFormat;
        } else if (this.parseFormatToken) {
          this.dateField.displayFormat = this.parseFormatToken;
        }

        if (configValue.startView) this.startView.set(configValue.startView);
        break;
    }
  }

  addPanelClass(configValue: DfDateConfig, panelClass: string) {
    if (configValue.panelClass) {
      this.datePicker.panelClass = Array.isArray(configValue.panelClass)
        ? [...configValue.panelClass, panelClass]
        : [configValue.panelClass, panelClass];
    } else {
      this.datePicker.panelClass = panelClass;
    }
  }

  onBlur(event: FocusEvent) {
    // setTimeout() needed for focused element to be updated
    setTimeout(() => {
      const dateComponentHasFocus = this.el.nativeElement.contains(event.relatedTarget);
      // Separate check for datePicker because it is opened in an overlay outside of the date component
      if (dateComponentHasFocus || this.datePicker.opened) {
        return;
      }

      this.setSelectedDate(this.control().value);
      this.emitFormEvent('onBlurEvent', this.control().value);
    }, 0);
  }

  onInputBlur() {
    this.control()?.markAsTouched();
  }

  formatDateByMode(date: Date): string {
    const mode = this.config().mode;
    switch (mode) {
      case 'yearOnly':
        return dayjs(date).format('YYYY');
      case 'monthYearOnly':
        return dayjs(date).format('YYYY-MM');
      case 'fullDate':
      default:
        return dayjs(date).format(ISO_STRING_FORMAT);
    }
  }

  isValidDate(dateValue: any): dateValue is Date {
    return dayjs(dateValue).isValid();
  }

  yearSelected(value: unknown): void {
    if (!(this.config().mode === 'yearOnly')) {
      return;
    }
    this.setSelectedDate(value);
    this.datePicker.close();
  }

  monthSelected(value: unknown): void {
    if (!(this.config().mode === 'monthYearOnly')) {
      return;
    }
    this.setSelectedDate(value);
    this.datePicker.close();
  }

  setSelectedDate(value: unknown): void {
    if (!this.isValidDate(value)) {
      return;
    }
    const formattedValue = this.formatDateByMode(value);
    this.control()?.setValue(formattedValue);
  }

  startAt(field?: DfDateConfig): Date | undefined {
    const startAt = this.retrieveConfigValue(field, 'startAt');
    return startAt ? new Date(startAt) : undefined;
  }

  private retrieveConfigValue<K extends keyof DfDateConfig>(
    field: DfDateConfig | undefined,
    value: K
  ): DfDateConfig[K] | undefined {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    return (field || ({} as any))[value];
  }

  private disableInvalidDates(validators: ValidationConfig[]) {
    validators.forEach((validator) => {
      if (validator?.minDate) {
        if (typeof validator.minDate === 'string') {
          this.minDate = new Date(validator.minDate);
        } else {
          validator.minDate.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((dateValue) => {
            if (dateValue) {
              this.minDate = new Date(dateValue);
            } else {
              this.minDate = undefined;
            }
          });
        }
      }
      if (validator?.maxDate) {
        if (typeof validator.maxDate === 'string') {
          this.maxDate = new Date(validator.maxDate);
        } else {
          validator.maxDate.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((dateValue) => {
            if (dateValue) {
              this.maxDate = new Date(dateValue);
            } else {
              this.maxDate = undefined;
            }
          });
        }
      }
    });
  }

  onDatePickerClosed() {
    setTimeout(() => {
      const nativeInput = this.inputField.elementRef.nativeElement;
      nativeInput.focus();
      nativeInput.blur();
    }, 0);
  }
}
<ng-container *aclTag="aclResource">
  <nx-formfield
    [label]="config().label | interpolateFromStore | async"
    [optionalLabel]="(config().optionalLabel | interpolateFromStore | async) || ''"
  >
    @if (config().inputPrefix) {
    <span nxFormfieldPrefix>
      {{ config().inputPrefix | interpolateFromStore | async }}
    </span>
    }

    <input
      #inputField
      #dateField="nxDatefield"
      nxDatefield
      nxInput
      inputmode="decimal"
      [attr.data-testid]="config().testId"
      [formControl]="control()"
      [id]="config().id"
      [min]="minDate"
      [max]="maxDate"
      [datepicker]="picker"
      [strict]="!config().nonStrictParsing"
      (blur)="onInputBlur(); onBlur($event)"
      (keyup.enter)="onInputBlur()"
    />

    @if (!config().hideCalendarPicker) {
    <nx-datepicker-toggle
      #pickerToggle
      nxFormfieldSuffix
      [for]="picker"
      (focusout)="onBlur($event)"
    ></nx-datepicker-toggle>
    }

    <nx-datepicker
      #picker
      [startView]="startView()"
      [startAt]="startAt(config())"
      (yearSelected)="yearSelected($event)"
      (monthSelected)="monthSelected($event)"
      (closed)="onDatePickerClosed()"
    ></nx-datepicker>

    @if (config().inputSuffix) {
    <span nxFormfieldSuffix>
      {{ config().inputSuffix | interpolateFromStore | async }}
    </span>
    } @if (config().hint) {
    <span nxFormfieldHint>
      {{ config().hint | interpolateFromStore | async }}
    </span>
    } @if (config().infoIcon) {
    <df-info-icon nxFormfieldAppendix [config]="config().infoIcon"></df-info-icon>
    }

    <taly-validation-errors
      nxFormfieldError
      [errorMessages]="validationConfigs()"
      [controlErrors]="control().errors"
    >
    </taly-validation-errors>

    @if (config().note) {
    <nx-message context="info" nxFormfieldNote>
      <span>{{ config().note | interpolateFromStore | async }}</span>
    </nx-message>
    }
  </nx-formfield>
</ng-container>

./date.component.scss

:host {
  display: block;
}

::ng-deep.nx-calendar {
  &.month-year-only,
  &.year-only {
    .nx-calendar-change-view-button {
      display: none;
    }
  }
}
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""