Date Range

Date range picker with trigger button, modal calendar overlay, quick filter buttons, and ControlValueAccessor support.

This component has both a default concrete implementation (<smart-date-range>) and abstract base classes for custom extensions.


Usage

<smart-date-range
  variant="standard"
  [ngModel]="range()"
  (ngModelChange)="onRangeChange($event)"
/>

Default Component Usage

Selector

<smart-date-range>

Properties

PropertyTypeDefaultDescription
ngModelModelSignal<IDateRange | undefined>undefinedDate range value (start, end in YYYY-MM-DD)

Features

  • Trigger button with calendar icon
  • Clear button (X) to reset
  • Modal overlay with calendar grid
  • Quick filters: Today, Yesterday, Last 7/30 days, This/Last Month
  • Range selection with start/end highlighting
  • ControlValueAccessor for forms
  • Dark mode

DateRangePresetComponent (<smart-date-range-preset>)

A fully styled, drop-in variation based on the Preline "single calendar range" datepicker (FRA-219). The Preline JS plugin is not installed, so the range calendar is rendered entirely with Angular signals (no external runtime): click the trigger to open an inline popover, pick a start and end day to highlight the range, switch months via prev/next buttons or the month/year selects, then Apply.

Extends DateRangeBaseComponent (reuses onClick, onModalApply, onModalDismiss, onClear), so it consumes the exact same API and ControlValueAccessor contract as the standard variant — no new interface options or tokens are required.

Selecting the variant

<smart-date-range> dispatches over the variant input (type DateRangeVariantName = 'standard' | 'preset') with an internal @switch. This component does not use the InjectionToken / NgComponentOutlet pattern:

Because the wrapper binds [class]="cssClass()" directly on the selector (not via NgComponentOutlet), the host class is applied natively — no cssClass input override is needed (unlike token-based components such as badge).

Base Classes API (for Extension)

DateRangeBaseComponent

Provides open/close state, calendar data management, clear/apply logic, and ControlValueAccessor.

MemberTypeDescription
ngModelModelSignal<IDateRange | undefined>Date range value
valueIDateRange | undefinedCurrent value (synced via effect)
isOpenWritableSignal<boolean>Controls modal visibility
calendarDataCalendarStatePersisted calendar state between opens
onClick()methodOpens modal, restores previous selection
onModalApply()methodApplies selection and closes modal
onModalDismiss()methodCloses modal without applying
onClear()methodResets value to undefined

DateRangeModalBaseComponent

Provides calendar rendering, quick filter buttons, date selection, and range restriction.

MemberTypeDescription
previousStateInputSignal<CalendarState>State from parent component
applyOutputEmitter<CalendarState>Emits on apply
dismissOutputEmitter<void>Emits on dismiss
calendarmonth[]Generated calendar months
dateFormUntypedFormGroupForm with dateFrom/dateTo fields
selectToday()methodQuick filter: today
selectYesterday()methodQuick filter: yesterday
selectLastSevenDays()methodQuick filter: last 7 days
selectLastThirtyDays()methodQuick filter: last 30 days
selectThisMonth()methodQuick filter: this month
selectLastMonth()methodQuick filter: last month
isInRange(day)methodCheck if day is in selected range
isSelectionStart(day)methodCheck if day is range start
isSelectionEnd(day)methodCheck if day is range end
applyDates()methodEmits apply with current state
dismissPage()methodEmits dismiss and unsubscribes

Extending the base class

import {
  ChangeDetectionStrategy,
  Component,
  forwardRef,
  signal,
  ViewEncapsulation,
} from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';

import { DateRangeBaseComponent } from '@smartsoft001/angular';
import { IDateRange } from '@smartsoft001/domain-core';

/**
 * A custom range picker built on `DateRangeBaseComponent`.
 *
 * The base owns the value (`ngModel` / `value`), the open state (`isOpen`,
 * `onClick`, `onModalDismiss`), the reset (`onClear`) and the
 * `ControlValueAccessor` contract. Here the full calendar modal is replaced
 * with a short list of preset ranges.
 */
@Component({
  selector: 'docs-custom-date-range',
  template: `
    <div class="docs-date-range">
      <button
        type="button"
        class="docs-date-range__trigger"
        (click)="onClick()"
      >
        @if (value) {
          {{ value.start }} - {{ value.end }}
        } @else {
          Pick a range
        }
      </button>

      @if (value) {
        <button
          type="button"
          class="docs-date-range__clear"
          (click)="onClear()"
        >
          Clear
        </button>
      }

      @if (isOpen()) {
        <ul class="docs-date-range__panel">
          <li>
            <button
              type="button"
              (click)="apply({ start: '2026-04-01', end: '2026-04-07' })"
            >
              First week of April
            </button>
          </li>
          <li>
            <button
              type="button"
              (click)="apply({ start: '2026-04-01', end: '2026-04-30' })"
            >
              Whole of April
            </button>
          </li>
          <li>
            <button type="button" (click)="onModalDismiss()">Cancel</button>
          </li>
        </ul>
      }
    </div>
  `,
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => CustomDateRangeComponent),
      multi: true,
    },
  ],
})
export class CustomDateRangeComponent extends DateRangeBaseComponent {
  /**
   * The base also exposes `onModalApply()`, but that one expects a
   * `CalendarState` carrying moment objects produced by the built-in calendar.
   * A picker that does not use that calendar writes the value itself and keeps
   * everything else the base provides.
   */
  apply(range: IDateRange): void {
    this.value = range;
    this.ngModel.set(range);
    this.propagateChange(range);
    this.propagateTouched();
    this.isOpen.set(false);
  }
}

/**
 * Date range has no injection token: `<smart-date-range>` picks between its
 * `standard` and `preset` variations through the `variant` input, so a custom
 * implementation is rendered directly rather than registered.
 */
@Component({
  selector: 'docs-date-range-custom-example',
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [CustomDateRangeComponent],
  template: `
    <docs-custom-date-range
      [ngModel]="range()"
      (ngModelChange)="range.set($event)"
    />
  `,
})
export class DateRangeCustomExampleComponent {
  range = signal<IDateRange | undefined>({
    start: '2026-04-01',
    end: '2026-04-07',
  });
}

Dependencies

  • @smartsoft001/domain-core — IDateRange interface
  • @ngx-translate/core — TranslatePipe for i18n
  • moment — date operations
  • CalendarService — calendar month generation

Tailwind Classes

All classes use smart: prefix. Trigger: smart:inline-flex smart:rounded-md smart:border smart:border-gray-300. Modal backdrop: smart:fixed smart:inset-0 smart:bg-black/50. Calendar selection: smart:bg-indigo-600 smart:text-white.

Source

The component lives in packages/shared/angular/src/lib/components/date-range and is documented for Claude Code by the angular-components-date-range skill.