GUILD OF GLEKS UIv21.14.0

gog-autocomplete

Autocomplete

A text field that suggests options as you type. It shares GogDropdownBase with Select — the same option accessors, placement, float label, error state and ControlValueAccessor — but its trigger is a real <input>, which is what makes it a separate control rather than a mode of the select.

Overview

Import the component and drop it into a template.

typescript
import { AutocompleteComponent } from '@guildofgleks/ui';

@Component({
  // ...
  imports: [AutocompleteComponent],
})

Basic usage — type a letter or two:

<gog-autocomplete
  label="City"
  placeholder="Start typing…"
  [options]="cities"
  [(value)]="city"
/>

Server-backed suggestions

Two things shape how the panel behaves before a query even runs. openOnFocus21.3.1 — on by default — opens the panel with the full option list the moment the field is focused, instead of waiting for minLength characters: for a short, known list that is the difference between a combobox and a text field the user has to guess at. Turn it off per instance, or app-wide with GOG_CONFIG.autocomplete.openOnFocus, to keep the older behaviour of showing nothing until something is typed.

gogLoadMore21.3.1 fires when the panel is scrolled to its end. Fetch the next page and append it to options — that is how a large or server-backed source is paged, with no virtual scroller involved.

gogSearch is debounced (searchDebounce, 300 ms by default), so a lookup fires once the typing settles rather than on every keystroke. Set [filterLocal]="false" alongside it: the server has already filtered, and filtering its answer a second time against the same query is the classic double-filtering bug — it silently drops rows the server matched on a field this component cannot see.

This demo fakes a 400 ms round trip. It also matches on the country, which local filtering would throw away — type germany to see it.

<gog-autocomplete
  label="City"
  [options]="results()"
  [loading]="loading()"
  [filterLocal]="false"
  [searchDebounce]="300"
  [minLength]="2"
  (gogSearch)="search($event)"
  [(value)]="city"
/>

Examples

Your own objects

optionLabel, optionValue and optionDisabled take a property path (dot-paths included) or a function. Set [optionValue]="null" and the control hands back the option object — the same reference you passed in — instead of an id.

<!-- optionValue="null" hands back the option object itself, not an id. -->
<gog-autocomplete
  label="City"
  optionLabel="name"
  [optionValue]="null"
  [options]="cities"
  [(value)]="cityObject"
/>

Custom suggestion rows

A gogDropdownOption template replaces one row, with the option itself plus selected, disabled and the resolved label in its context — so a custom row can decorate the label rather than re-derive it.

<gog-autocomplete label="City" [options]="cities" [(value)]="slotCity">
  <ng-template gogDropdownOption let-option let-label="label">
    <strong>{{ label }}</strong>
    <small>{{ option.country }}</small>
  </ng-template>
</gog-autocomplete>

Free text (forceSelection)

On by default, the field always ends up reflecting a real selection: editing is treated as transient, so value survives keystrokes and Escape or blur snaps the text back to it. Turn it off for a create-as-you-type flow — the text is then left alone on blur and value is dropped as soon as it stops matching, so the two never disagree. Read what was typed from gogSearch, not from value.

<!-- forceSelection="false": what was typed is itself meaningful. -->
<gog-autocomplete
  label="Tag"
  [forceSelection]="false"
  [options]="cities"
  (gogSearch)="draft.set($event)"
  [(value)]="freeText"
/>

Accessibility

The <input> keeps DOM focus the whole time and the highlighted suggestion is pointed at with aria-activedescendant — where a listbox would move focus onto the option itself. That is the difference that makes this a combobox rather than a select, and it is why typing keeps working while the panel is open.

Arrow keys move the highlight, Enter picks it, Escape closes the panel. Give the field a label or an ariaLabel; use inputId if you are wiring your own <label for="…">.

Virtualized options — virtualize21.13.0

[virtualize]="true" renders only the rows in view. Unwindowed, 10 000 options build 10 000 DOM rows to show about six — measured in Chrome, 512 ms before the panel appears, against 21 ms windowed on the same data. The scrollbar looks the same either way, because spacers stand in for the rows that are not there.

10 000 cities. Focus the field and scroll the panel, or press End.

<gog-autocomplete label="City" [options]="cities" [virtualize]="true" />

Off by default, and never switched on at some row count. A windowed list behaves differently in ways nothing about the data predicts: Ctrl+F finds only the rendered rows, and CSS targeting :last-child matches the last rendered row. A threshold would make that depend on how much data happened to arrive, so set it per field, or app-wide with GOG_CONFIG.dropdown.virtualize.

  • The announced count stays honest: the aria-setsize and aria-posinset carry the real list, so a screen reader hears "10 000 items" rather than "20". Arrow keys walk the whole list — End reaches the last option.
  • Focus never moves, which is the one way this differs from the select and the multiselect. Their windowed lists hand focus back to the trigger when a focused row scrolls away; a combobox keeps focus in its input the whole time, so there is nothing to hand back.
  • gog-table's virtualize is a different story under the same name: table rows vary in height, so it measures as it goes — see the Table page.

Not the same thing as gogLoadMore, and the two compose. The server-backed suggestions above keep the number of records the server sends small; virtualize keeps the number of rows the browser builds small. A gogLoadMore list that has loaded 10 000 records still stamps 10 000 rows without it — neither implies the other, and a list long enough to want one usually wants both.

API Reference

Inputs — autocomplete's own

NameTypeDefaultDescription
openOnFocus21.3.1boolean | undefinedundefinedWhether focusing the field opens the panel immediately with the full option list, rather than waiting for minLength characters. Unset, falls back to GOG_CONFIG.autocomplete.openOnFocus, then to true.
valueTValuenullThe selected value — whatever optionValue resolves to. Two-way bindable with [(value)].
filterLocalbooleantrueWhether options are narrowed in the browser as you type. Turn it OFF when gogSearch fetches an already-filtered list: filtering that answer a second time is the classic double-filtering bug, and it silently drops rows the server matched on a field this component cannot see.
minLengthnumberGOG_CONFIG.autocomplete.minLength ?? 1How many characters before the panel opens at all.
searchDebouncenumberGOG_CONFIG.autocomplete.searchDebounce ?? 300Milliseconds of quiet before gogSearch fires. 0 emits on every keystroke.
loadingbooleanfalseShows a spinner in the trailing slot, for a server-backed source still fetching.
emptyMessagestring'No matches'Shown in place of the list when nothing matches.
forceSelectionbooleantrueOn, the field always ends up reflecting a real selection — editing is transient and Escape or blur snaps the text back. Off, the typed text is itself meaningful (a create-as-you-type flow): it survives blur and value is dropped as soon as it stops matching, so the two never disagree.
inputIdstring''id for the inner <input>, for an external <label for="…">.
ripple21.6.1boolean | undefinedundefinedPress ripple on each option row in the panel. Unset, falls back to GOG_CONFIG.ripple.enabled, which is off by default; setting it here wins over the app-wide value in both directions.
virtualize21.13.0boolean | undefinedGOG_CONFIG.dropdown.virtualize ?? falseRenders only the suggestion rows in view — about twenty in the DOM whatever the list holds. Off by default and never switched on at a row count: Ctrl+F finds only rendered rows and :last-child matches the last rendered one. aria-setsize/aria-posinset keep the announced count real.

Inputs — shared with Select

These come from GogDropdownBase and behave exactly as they do on Select. The one exception is filter: an autocomplete's trigger already is the search box, so the panel never gets a second one — filterMatch still applies, though, since it plugs into filterLocal's own matching rather than the panel's search box.

NameTypeDefaultDescription
optionsTOption[][]The suggestions. Your own objects.
optionLabelstring | ((o: TOption) => string)'name'Property path (dot-paths included) or function producing an option’s label.
optionValuestring | ((o: TOption) => unknown) | null'id'What the control emits. null emits the option object itself.
optionDisabledstring | ((o: TOption) => boolean)'disabled'Which suggestions cannot be picked.
filterMatch((option: TOption, query: string) => boolean) | nullnullHow filterLocal matches an option against the typed text. Left null, the resolved optionLabel is matched case-insensitively as a substring.
label / placeholder / ariaLabelstring'' / 'Select...' / ''Field label, placeholder and accessible name.
clearable / clearAriaLabelboolean / stringGOG_CONFIG.control.clearable ?? false / 'Clear selection'A clear button that appears only once there is something to clear.
floatLabel / floatLabelShowPlaceholder'none' | 'in' | 'on' | 'over' / booleanGOG_CONFIG.floatLabel.* ?? 'none' / falseFloat-label variant and whether the placeholder reappears once it has floated.
errorMessage / errorDisplaystring / 'manual' | 'auto''' / GOG_CONFIG.control.errorDisplay ?? 'manual'Validation message, shown manually or derived from the bound form control.
size / disabled / fullWidth / minWidthGogSize / boolean / boolean / string | nullGOG_CONFIG.control.size ?? 'md' / false / true / nullDensity, disabled state, and how the field sizes itself.
appendToBody / dropdownDirection / dropdownWidth / dropdownMaxHeight / dropdownZIndexboolean / GogDropdownDirection / string | null / string | null / number | nullGOG_CONFIG.dropdown.* ?? component defaultsPanel placement. appendToBody renders it into <body> so an overflow-clipped ancestor cannot cut it off.

Outputs

NamePayloadDescription
gogSearchstringThe current query, debounced. Wire a server-side lookup to this.
gogLoadMore21.3.1voidThe panel was scrolled to the end. Fetch the next page and append it to options — this is how a large or server-backed option source is paged without a virtual scroller.
valueChangeTValueEmitted when the selection changes. Comes from the value model input.

Content slots

DirectiveContextDescription
gogDropdownOption$implicit, selected, disabled, labelReplaces one suggestion row.

Global Configuration

Set any of these once with provideGogConfig() instead of repeating them on every instance — an instance's own input still wins when it sets one itself. See Global Configuration for the full reference.

  • GOG_CONFIG.control.size
  • GOG_CONFIG.control.errorDisplay
  • GOG_CONFIG.control.clearable
  • GOG_CONFIG.floatLabel.variant
  • GOG_CONFIG.floatLabel.showPlaceholder
  • GOG_CONFIG.dropdown.appendToBody
  • GOG_CONFIG.dropdown.direction
  • GOG_CONFIG.dropdown.virtualize
  • GOG_CONFIG.ripple.enabled
  • GOG_CONFIG.autocomplete.minLength
  • GOG_CONFIG.autocomplete.searchDebounce
  • GOG_CONFIG.autocomplete.openOnFocus
  • GOG_CONFIG.spinner.component — the spinner loading draws, which has no input of its own
  • GOG_CONFIG.spinner.variant — the same spinner, when no component is set
  • GOG_CONFIG.labels.clearSelection

This site is not on the default here. The library ships ripple.enabled as false; these docs set it to true app-wide so the demos above actually show the press feedback. In a fresh app you get no ripple until you ask for one — the droplet button in the header switches this site between the two, and it is on right now.

Styling Tokens

Every CSS custom property the autocomplete paints with. Override any of them — on a single instance, a subtree, or a theme — to restyle it. See the Theming guide for the full token-layering model, or the Theme Generator to tweak these live.

TokenDescription
--gog-autocomplete-label-colorField label color.
--gog-autocomplete-field-bg / -border-color / -text-colorField surface, border and text.
--gog-autocomplete-radius / -min-widthField corner radius and the floor an auto-width field cannot collapse past.
--gog-autocomplete-focus-ring-color / -focus-ring-width / -hover-border-colorFocus and hover states.
--gog-autocomplete-panel-bg / -panel-border-color / -panel-shadow / -panel-max-heightSuggestion panel surface and height cap.
--gog-autocomplete-option-hover-bg / -option-press-bg / -option-selected-bgSuggestion row states.
--gog-autocomplete-option-height / -panel-gapA seed for the first frame rather than the row height — the component measures a real row and corrects itself, which is also what places the panel above or below the field. And the gap between the field and its panel.
--gog-autocomplete-empty-color / -spinner-sizeThe "nothing found" message and the loading spinner.
--gog-autocomplete-clear-color / -clear-hover-color / -actions-insetClear button color and how far the trailing actions sit from the edge.
--gog-autocomplete-float-label-reserve / -in-top / -over-gap / -over-reserveFloat-label geometry, derived from the shared --gog-field-float-label-* scale.