Atomic Angular — Doc interne

Aggregation date

Render a date facet as preset ranges plus an optional custom range, and turn a pick into an applied filter — live or deferred, controlled or not.

<aggregation-date> is what <Aggregation> delegates to for a date column — see Aggregation. Unlike the list and tree variants, its options are a radio list (never a multi-select), and it opens a dialog for a custom range instead of paging through buckets.

Concept — date option vs. custom range

The server turns a date column's buckets into named date options ("Today", "This week", "This year") via translateAggregationToDateOptions. Picking "custom-range" is the seventh, always-present option: it opens the custom-range dialog instead of applying immediately.

Minimal example

A date facet, applied on selection
sample.component.ts
import { Component } from "@angular/core";
import { AggregationDateComponent } from "@sinequa/atomic-angular";

@Component({
  selector: "sample-component",
  imports: [AggregationDateComponent],
  template: ` <aggregation-date name="Modified" column="modified" [showFiltersCount]="true" /> `,
})
export class SampleComponent {}

Picking a preset radio, or confirming a date in the custom-range dialog, reveals the Apply button — exactly like the list and tree facets — rather than applying instantly. Clicking it writes the filter to QueryParamsStore and re-runs the query.

How it works

The custom range dialog

Selecting "custom-range" opens a dialog, mounted imperatively with AggregationDateRange.call({ min, max, lang }) (the createCallable pattern): one calendar showing two months side by side, in which the range is picked and stays visible — both ends, and every day in between, with a hover preview while picking the second end. A first click sets the start, a second one closes the range (clicking an earlier day swaps the two), and a localized summary under the calendar reads the result back. Validating after a single click means that day alone (from and to are the same day); the dialog cannot pick an open-ended range.

one two radio: custom-range AggregationDateRange.call() two-month calendar, pre-filled with the applied range clicks? that day alone (from = to) range between the two days confirm() -> call resolves with the range form seeded, Apply revealed

Every call mounts a fresh dialog instance and tears it down on close, so no selection survives from the last time it was used. The call resolves with the picked range, or undefined when the dialog is dismissed (cancel, Escape, backdrop) — the facet then leaves its model untouched.

Inside a popover

A facet is often rendered in a native popover="auto" — that is what <filter-button> does. The custom-range dialog is modal, and showModal() hides every auto popover that is not an ancestor of the dialog. Mounted in document.body like any callable dialog, it would close the popover that opened it and destroy the facet with it, dialog included.

So <aggregation-date> mounts the dialog inside itself, through the call's container option, instead of in document.body: the dialog becomes a DOM descendant of the facet, hence of the popover, which stays open behind the modal. The dialog is still rendered in the top layer, so it is not clipped by the facet's box. Mounting it in the facet also means the calendar inherits CSS custom properties set on the facet or above it, which is how a single facet's colors are restyled.

click or Enter mount the dialog inside the facet, then showModal() range picked, dialog closed, popover still open the dialog is a descendant, so the popover stays open From / To input aggregation-date popover="auto"

Nothing to configure: the behaviour is automatic. The same container option is available to your own dialogs: see "Open a dialog from inside a native popover" in galactik's Dialog page.

Controlled vs. uncontrolled, and emitOn

Like <Aggregation>, setting any of query, aggregation or selection switches to controlled mode. Being a radio list, controlled mode has one behaviour the list/tree variants do not: emitOn="selection" (the default) emits live, at the moment a preset is picked or a custom range confirmed — there is no Apply click to wait for. Set emitOn="apply" to defer every emission to the Apply button instead, matching the list/tree select-then-Apply flow.

Masked dates

A date column aggregated with a mask (YYYY, YYYY-MM) returns items such as { value: "2016", count: 12 }, with no display. The mask is deduced from the values, and the label is the value itself, formatted in the active language for YYYY-MM (March 2024 / mars 2024). Picking one commits the full dates of the period, gte 2016-01-01 and lt 2017-01-01 (December rolls over to the next year), and puts the mask in the query's aggregation_overrides, keyed by aggregation name ({ Modified: { mask: "YYYY" } }), which tells the server how the values are built. Overrides of other aggregations are kept.

  • Default mode: the component writes the override to QueryParamsStore itself — not to the URL, since a committed range is self-sufficient; only the masked aggregation values are lost after a page reload until the next apply — and removes it when the filter is cleared.
  • Controlled mode: it is emitted through (aggregationOverridesChange), right before (filtersChange), on the same occasions. Assign it to the query you fetch with.

Recipes

A controlled date facet, with a live selection

A date facet over an unmanaged query
agent-date-facet.component.ts
import { Component, signal } from "@angular/core";
import { AggregationDateComponent } from "@sinequa/atomic-angular";
import type { AggregationSelection } from "@sinequa/atomic-angular";
import type { Aggregation, Filter, Query } from "@sinequa/atomic";

@Component({
  selector: "agent-date-facet",
  imports: [AggregationDateComponent],
  template: `
    <aggregation-date
      name="Modified"
      column="modified"
      [aggregation]="modifiedAggregation()"
      [selection]="selection()"
      [query]="agentQuery"
      (selectionChange)="selection.set($event)"
      (filtersChange)="applyToQuery($event)"
    />
  `,
})
export class AgentDateFacetComponent {
  private readonly agentQuery: Query = { name: "agent-query", text: "" };

  protected readonly selection = signal<AggregationSelection | undefined>(undefined);
  protected readonly modifiedAggregation = signal<Aggregation>({
    name: "Modified",
    column: "modified",
    isDistribution: true,
    items: [],
  } as unknown as Aggregation);

  protected async applyToQuery(filters: Filter | undefined) {
    console.log("Filters to send:", filters);
  }
}

The emitted AggregationSelection is { kind: "option", option } for a preset — the item's real filter value, never its display, so a round-trip survives across days for presets computed relative to today — or { kind: "range", from, to } for a custom range.

Disable the custom range entirely

Some deployments only want the fixed presets — no "custom range" option at all.

app.config.ts
import { FILTER_DATE_ALLOW_CUSTOM_RANGE } from "@sinequa/atomic-angular";

providers: [{ provide: FILTER_DATE_ALLOW_CUSTOM_RANGE, useValue: false }];

Options

Prop

Type

Controlled mode

Prop

Type

Outputs: selectionChange and filtersChange as described above, plus aggregationOverridesChange (Record<string, { mask?: string }> | undefined) for masked dates.

Set up the calendar

The custom-range dialog renders its two-month calendar with vanilla-calendar-pro. The calendar is internal to <aggregation-date> — there is no component to declare or provider to register — but the application owns two pieces of setup.

1. Install the peer dependency.

terminal
npm install vanilla-calendar-pro

2. Import the stylesheet once, from the application's global stylesheet (not from a component: at 26 kB it would break the default anyComponentStyle budget).

styles.css
@import "@sinequa/atomic-angular/styles/calendar.css";

It bundles vanilla-calendar-pro's layout and light theme with every color mapped onto galactik's semantic tokens, so the calendar follows the light/dark theme with no extra rule. It is a single flat file with no nested import: without a bundler that resolves package exports, link it instead.

index.html
<link rel="stylesheet" href="node_modules/@sinequa/atomic-angular/styles/calendar.css" />

Nothing else to configure: month and weekday names, and the first day of the week, come from the browser's Intl API for the active Transloco language — no locale file to register.

Customize the colors

Every color of the calendar is a --vc-* custom property set on the date-range-calendar element. Override the ones to change after the @import, with the same selector, or scoped to one facet with a class on <aggregation-date> since the calendar is mounted inside it (.my-facet date-range-calendar { … }). Pointing them at galactik tokens keeps the light/dark switch for free; a raw color applies to both themes. The variables are those of vanilla-calendar-pro, which remains the reference for what each one paints.

styles.css
@import "@sinequa/atomic-angular/styles/calendar.css";

date-range-calendar {
  --vc-date-selected-bg: var(--bg-accent-base);
  --vc-date-range-middle-bg: var(--bg-neutral-hovered);
  --vc-date-range-middle-color: var(--font-neutral-base);
}
PropertyPaintsDefault
--vc-bg / --vc-colorcalendar surface and text--bg-neutral-white / --font-neutral-base
--vc-date-selected-bg / --vc-date-selected-colorboth ends of the range--bg-primary-base / --font-neutral-white
--vc-date-range-middle-bg / --vc-date-range-middle-colordays between the ends--bg-primary-lighter / --font-primary-base
--vc-date-hover-bg / --vc-date-hover-edge-bgpreview while picking the second end--bg-primary-lighter / --bg-primary-light
--vc-date-today-bg / --vc-date-today-colortoday--bg-primary-lighter / --font-primary-base
--vc-date-disabled-colordisabled days--font-neutral-disabled
--vc-focus-outline-colorkeyboard focus ring--stroke-focus

The full list (month/year grids, days outside the month…) is in the generated stylesheet, under the date-range-calendar rule at its end. Layout — sizes, spacing, radii — comes from vanilla-calendar-pro and is best left alone; the dialog sizes itself to the two months.

Pitfalls

What's next

On this page