File

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

Extends

DfBaseComponent

Implements

OnInit AfterViewInit

Metadata

Index

Properties
Methods

Constructor

constructor(parseFormatToken: string, cdr: ChangeDetectorRef, formfieldDefaultOptions: FormfieldDefaultOptions, el: ElementRef)
Parameters :
Name Type Optional
parseFormatToken string No
cdr ChangeDetectorRef No
formfieldDefaultOptions FormfieldDefaultOptions No
el ElementRef No

Methods

dateType
dateType(field?: DfDateConfig)
Parameters :
Name Type Optional
field DfDateConfig Yes
monthSelected
monthSelected(value)
Parameters :
Name Optional
value No
Returns : void
onBlur
onBlur(event: FocusEvent)
Parameters :
Name Type Optional
event FocusEvent No
Returns : void
onFocus
onFocus()
Returns : void
onInputBlur
onInputBlur()
Returns : void
requiredDateFormFieldValidator
requiredDateFormFieldValidator(c: AbstractControl)
Parameters :
Name Type Optional
c AbstractControl No
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:45
control
Default value : input<UntypedFormControl>(new UntypedFormControl())
Inherited from DfBaseComponent
Defined in DfBaseComponent:44
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})
labelAlwaysFloating
Type : boolean
Optional maxDate
Type : Date
Optional minDate
Type : Date
Public parseFormatToken
Type : string
Decorators :
@Optional()
@Inject(Df_DATE_PICKER_PARSE_FORMAT)
Readonly StartViewType
Default value : DfDateStartViewType
aclResource
Type : string
Inherited from DfBaseComponent
Defined in DfBaseComponent:56
componentOrControlInitFinished
Default value : new ReplaySubject<void>(1)
Inherited from DfBaseComponent

Additionally to the controlChanged EventEmitter a ReplaySubject is provided. In difference to the EventEmitter, this one always contains the last emitted value, which allows to check if a component was initialized after the initialization already happened. (An event will be gone at that point in time.)

Readonly controlChanged
Default value : output<AbstractControl | undefined>()
Inherited from DfBaseComponent

Emits the AbstractControl associated with this formfield, once it has been fully configured (i.e. its initial value and validators have been added as per the config).

Applications that need to further process this AbstractControl (e.g. to add more validators) should therefore wait for this event to be emitted.

Public deferSetupControl
Default value : false
Inherited from DfBaseComponent

By default, the AbstractControl is configured in ngOnInit(), however in some cases this causes an ExpressionChangedAfterItHasBeenCheckedError.

If this is set to true, the setup will not be run automatically. It is then the derived component's responsibility to run call setupFormControl() at the appropriate time.

formAclPath
Default value : input<string>()
Inherited from DfBaseComponent
Defined in DfBaseComponent:89
Readonly formEvent
Default value : output<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.

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 { DfBaseComponent } from '@allianz/taly-core/dynamic-form';
import {
  AfterViewInit,
  ChangeDetectorRef,
  Component,
  effect,
  ElementRef,
  Inject,
  InjectionToken,
  input,
  InputSignalWithTransform,
  OnInit,
  Optional,
  ViewChild
} from '@angular/core';
import { AbstractControl, UntypedFormControl, ValidationErrors } from '@angular/forms';
import { NxDatefieldDirective, NxDatepickerComponent } from '@aposin/ng-aquila/datefield';
import { FORMFIELD_DEFAULT_OPTIONS, FormfieldDefaultOptions } from '@aposin/ng-aquila/formfield';
import { NxInputDirective } from '@aposin/ng-aquila/input';
import dayjs from 'dayjs';
import { DfDateConfig, DfDateDefaultValues, DfDateStartViewType } from './date.model';
import { ValidationConfig } from '@allianz/taly-core';

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
{
  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;
    }
  });

  readonly StartViewType = DfDateStartViewType;

  // Let's not disturb the user while the focus is on the input field
  private inputFocused = false;

  private requiredValidatorSet = false;

  minDate?: Date;
  maxDate?: Date;
  labelAlwaysFloating!: boolean;

  @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(
    @Optional() @Inject(Df_DATE_PICKER_PARSE_FORMAT) public parseFormatToken: string,
    private cdr: ChangeDetectorRef,
    @Optional()
    @Inject(FORMFIELD_DEFAULT_OPTIONS)
    private formfieldDefaultOptions: FormfieldDefaultOptions,
    private el: ElementRef
  ) {
    super();
    effect(() => {
      const validationConfigs = this.validationConfigs();
      if (validationConfigs) {
        this.disableInvalidDates(validationConfigs);
      }
    });
  }

  override ngOnInit() {
    super.ngOnInit();

    /* If the label is not floating, then we pass the label to the nx-formfield.
     ** TODO: exchange this logic when NDBX support that with this config is supported multiple lines
     */
    this.labelAlwaysFloating = this.formfieldDefaultOptions?.nxFloatLabel === 'always';

    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;
    }
    if (configValue.parseFormat) {
      this.dateField.parseFormat = configValue.parseFormat
        ? configValue.parseFormat
        : this.parseFormatToken;
    }
    if (configValue.displayFormat) {
      this.dateField.displayFormat = configValue.displayFormat;
    }
    if (configValue.panelClass) {
      this.datePicker.panelClass = configValue.panelClass;
    }
  }

  requiredDateFormFieldValidator(c: AbstractControl): ValidationErrors | null {
    return c.value || !this.requiredValidatorSet ? null : { required: true };
  }

  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.emitFormEvent('onBlurEvent', this.control().value);
    }, 0);
  }

  onInputBlur() {
    this.inputFocused = false;
    this.control()?.markAsTouched();
  }

  onFocus() {
    this.inputFocused = true;
  }

  yearSelected(value: unknown): void {
    if (this.config().onlyYears) {
      this.control()?.setValue(value);
      this.datePicker.close();
    }
  }

  monthSelected(value: unknown): void {
    if (this.config().dayDisabled) {
      this.control()?.setValue(value);
      this.datePicker.close();
    }
  }

  dateType(field?: DfDateConfig): DfDateStartViewType | undefined {
    return this.retrieveConfigValue(field, 'startView');
  }

  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) {
        this.minDate = new Date(validator.minDate);
      }
      if (validator?.maxDate) {
        this.maxDate = new Date(validator.maxDate);
      }
    });
  }
}
<ng-container *aclTag="aclResource">
  <span nxCopytext class="own-label nx-font-weight-semibold" *ngIf="labelAlwaysFloating">
    {{ config().label | interpolateFromStore | async }}
    <span *ngIf="config().optionalLabel" class="nx-font-weight-regular">
      ({{ config().optionalLabel | interpolateFromStore | async }})</span
    >
  </span>
  <nx-formfield
    [label]="!labelAlwaysFloating ? (config().label | interpolateFromStore | async) : ''"
    [optionalLabel]="
      !labelAlwaysFloating ? (config().optionalLabel | interpolateFromStore | async) || '' : ''
    "
  >
    <span *ngIf="config().inputPrefix" 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)"
      (focus)="onFocus()"
      (keyup.enter)="onInputBlur()"
    />

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

    <nx-datepicker
      #picker
      [startView]="dateType(config()) || StartViewType.Month"
      [startAt]="startAt(config())"
      (yearSelected)="yearSelected($event)"
      (monthSelected)="monthSelected($event)"
    ></nx-datepicker>

    <span *ngIf="config().inputSuffix" nxFormfieldSuffix>
      {{ config().inputSuffix | interpolateFromStore | async }}
    </span>

    <span *ngIf="config().hint" nxFormfieldHint>
      {{ config().hint | interpolateFromStore | async }}
    </span>

    <df-info-icon
      *ngIf="config().infoIcon"
      nxFormfieldAppendix
      [config]="config().infoIcon?.config"
      [modalConfig]="config().infoIcon?.modalConfig"
    ></df-info-icon>

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

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

./date.component.scss

:host {
  display: block;
}
/* Own label management.TODO: Remove when NDBX support multiple lines */
::ng-deep.own-label + * {
  .nx-formfield__wrapper {
    padding-top: 4px !important;
  }
}
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""