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

Extends

DfBaseComponent<DfDateConfig>

Implements

OnInit AfterViewInit

Metadata

Relationships

Index

Properties
Methods
Inputs
Outputs

Constructor

constructor()

Inputs

config
Required :  true
Inherited from DfBaseComponent
control
Type : UntypedFormControl
Default value : new UntypedFormControl()
Inherited from DfBaseComponent
formAclPath
Type : string
Inherited from DfBaseComponent
isAclHandled
Type : boolean
Default value : false
Inherited from DfBaseComponent
isRetailChannel
Type : boolean
Inherited from DfBaseComponent
validationConfigs
Type : ValidationConfig[] | undefined
Inherited from DfBaseComponent

Outputs

formEvent
Type : DfEventPayload
Inherited from DfBaseComponent

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.

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: unknown)
Parameters :
Name Type Optional
value unknown 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: unknown)
Parameters :
Name Type Optional
value unknown No
Returns : void
startAt
startAt(field?: DfDateConfig)
Parameters :
Name Type Optional
field DfDateConfig Yes
Returns : Date | undefined
yearSelected
yearSelected(value: unknown)
Parameters :
Name Type Optional
value unknown No
Returns : void

Properties

dateField
Type : NxDatefieldDirective<unknown>
Decorators :
@ViewChild('dateField', {static: false})
datePicker
Type : NxDatepickerComponent<unknown>
Decorators :
@ViewChild('picker')
datePickerToggle
Type : ElementRef<unknown>
Decorators :
@ViewChild('pickerToggle', {read: ElementRef})
inputField
Type : NxInputDirective
Decorators :
@ViewChild(NxInputDirective, {static: false})
maxDate
Type : unknown
Default value : signal<Date | undefined>(undefined)
minDate
Type : unknown
Default value : signal<Date | undefined>(undefined)
parseFormatToken
Type : unknown
Default value : inject(Df_DATE_PICKER_PARSE_FORMAT, { optional: true })
pickerPanelClass
Type : unknown
Default value : signal<string | string[] | undefined>(undefined)
startView
Type : unknown
Default value : signal<DfDateStartViewTypeFullDate | DfDateStartViewTypeMonthYearOnly>( DfDateStartViewTypeFullDate.Month )
componentOrControlInitFinished
Type : unknown
Default value : new ReplaySubject<AbstractControl | undefined>(1)
Inherited from DfBaseComponent

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

errorMessages
Type : WritableSignal<Observable[]>
Default value : signal([])
Inherited from DfBaseComponent

The resolved validation error messages for the current control state.

Readonly nxErrorAppearance
Type : ErrorStyleType
Default value : inject(ERROR_DEFAULT_OPTIONS, { optional: true })?.appearance || (inject<CHANNEL>(CHANNEL_TOKEN) === CHANNEL.EXPERT ? 'text' : 'message')
Inherited from DfBaseComponent
import { ValidationConfig } from '@allianz/taly-core';
import { DfBaseComponent } from '@allianz/taly-core/dynamic-form';
import {
  AfterViewInit,
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  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,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class DfDateComponent
  extends DfBaseComponent<DfDateConfig>
  implements OnInit, AfterViewInit
{
  parseFormatToken = inject(Df_DATE_PICKER_PARSE_FORMAT, { optional: true });
  private el = inject(ElementRef);
  private changeDetector = inject(ChangeDetectorRef);

  pickerPanelClass = signal<string | string[] | undefined>(undefined);

  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 = signal<Date | undefined>(undefined);
  maxDate = signal<Date | undefined>(undefined);
  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);
    // This additional change detection cycle is needed to avoid an "ExpressionChangedAfterItHasBeenCheckedError" of the Aquila component.
    this.changeDetector.detectChanges();
  }

  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.pickerPanelClass.set(
        Array.isArray(configValue.panelClass)
          ? [...configValue.panelClass, panelClass]
          : [configValue.panelClass, panelClass]
      );
    } else {
      this.pickerPanelClass.set(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.set(new Date(validator.minDate));
        } else {
          validator.minDate.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((dateValue) => {
            this.minDate.set(dateValue ? new Date(dateValue) : undefined);
          });
        }
      }
      if (validator?.maxDate) {
        if (typeof validator.maxDate === 'string') {
          this.maxDate.set(new Date(validator.maxDate));
        } else {
          validator.maxDate.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((dateValue) => {
            this.maxDate.set(dateValue ? new Date(dateValue) : undefined);
          });
        }
      }
    });
  }

  onDatePickerClosed() {
    setTimeout(() => {
      const nativeInput = this.inputField.elementRef.nativeElement;
      nativeInput.focus();
      nativeInput.blur();
    }, 0);
  }
}
<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()"
    [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
    [panelClass]="pickerPanelClass()"
    [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>
  } @if (config().note) {
  <nx-message context="info" nxFormfieldNote>
    <span>{{ config().note | interpolateFromStore | async }}</span>
  </nx-message>
  } @for (message$ of errorMessages(); track $index) {
  <nx-error nxFormfieldError [appearance]="nxErrorAppearance">{{ message$ | async }}</nx-error>
  }
</nx-formfield>

./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 ""