Autocomplete
Autocomplete input and dropdown selection with filtering options.
Default
Standard autocomplete with filtering.
autocomplete-example-default.ts
import { ChangeDetectionStrategy, Component, computed, signal } from '@angular/core';
import { HlmAutocompleteImports } from '@spartan-ng/helm/autocomplete';
interface SpartanComponent {
id: string;
value: string;
}
@Component({
selector: 'app-autocomplete-example-default',
standalone: true,
imports: [HlmAutocompleteImports],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<hlm-autocomplete [(search)]="search" class="w-full max-w-[300px]">
<hlm-autocomplete-input placeholder="Search components..." />
<hlm-autocomplete-content *hlmAutocompletePortal>
<hlm-autocomplete-empty>No components found.</hlm-autocomplete-empty>
<div hlmAutocompleteList>
@for (component of filteredOptions(); track component.id) {
<hlm-autocomplete-item [value]="component.value">
{{ component.value }}
</hlm-autocomplete-item>
}
</div>
</hlm-autocomplete-content>
</hlm-autocomplete>
`,
})
export class AutocompleteExampleDefault {
private readonly _components: SpartanComponent[] = [
{ id: 'accordion', value: 'Accordion' },
{ id: 'alert', value: 'Alert' },
{ id: 'alert-dialog', value: 'Alert Dialog' },
{ id: 'aspect-ratio', value: 'Aspect Ratio' },
{ id: 'autocomplete', value: 'Autocomplete' },
{ id: 'avatar', value: 'Avatar' },
{ id: 'badge', value: 'Badge' },
{ id: 'button', value: 'Button' },
{ id: 'checkbox', value: 'Checkbox' },
{ id: 'collapsible', value: 'Collapsible' },
{ id: 'combobox', value: 'Combobox' },
{ id: 'command', value: 'Command' },
{ id: 'context-menu', value: 'Context Menu' },
{ id: 'data-table', value: 'Data Table' },
];
public readonly search = signal('');
public readonly filteredOptions = computed(() =>
this._components.filter((component) =>
component.value.toLowerCase().includes(this.search().toLowerCase()),
),
);
}With Clear Button
Autocomplete input with an optional clear button.
autocomplete-example-clear.ts
import { ChangeDetectionStrategy, Component, computed, signal } from '@angular/core';
import { HlmAutocompleteImports } from '@spartan-ng/helm/autocomplete';
@Component({
selector: 'app-autocomplete-example-clear',
standalone: true,
imports: [HlmAutocompleteImports],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<hlm-autocomplete [(search)]="search" class="w-full max-w-[300px]">
<hlm-autocomplete-input placeholder="Search frameworks..." showClear />
<hlm-autocomplete-content *hlmAutocompletePortal>
<hlm-autocomplete-empty>No framework found.</hlm-autocomplete-empty>
<div hlmAutocompleteList>
@for (option of filteredOptions(); track option) {
<hlm-autocomplete-item [value]="option">
{{ option }}
</hlm-autocomplete-item>
}
</div>
</hlm-autocomplete-content>
</hlm-autocomplete>
`,
})
export class AutocompleteExampleClear {
private readonly _options: string[] = [
'Angular',
'React',
'Vue',
'Svelte',
'Solid',
'Qwik',
'Ember',
'Preact',
'Lit',
'Alpine',
];
public readonly search = signal('');
public readonly filteredOptions = computed(() =>
this._options.filter((option) => option.toLowerCase().includes(this.search().toLowerCase())),
);
}With Groups
Display options grouped by category.
autocomplete-example-group.ts
import { ChangeDetectionStrategy, Component, computed, signal } from '@angular/core';
import { HlmAutocompleteImports } from '@spartan-ng/helm/autocomplete';
@Component({
selector: 'app-autocomplete-example-group',
standalone: true,
imports: [HlmAutocompleteImports],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<hlm-autocomplete [(search)]="search" class="w-full max-w-[300px]">
<hlm-autocomplete-input placeholder="Select a timezone..." />
<hlm-autocomplete-content *hlmAutocompletePortal>
<hlm-autocomplete-empty>No items found.</hlm-autocomplete-empty>
<div hlmAutocompleteList>
@for (timezoneGroup of filteredTimezones(); track timezoneGroup.value) {
<div hlmAutocompleteGroup>
<div hlmAutocompleteLabel>{{ timezoneGroup.value }}</div>
@for (timezone of timezoneGroup.items; track timezone) {
<hlm-autocomplete-item [value]="timezone">{{ timezone }}</hlm-autocomplete-item>
}
</div>
}
</div>
</hlm-autocomplete-content>
</hlm-autocomplete>
`,
})
export class AutocompleteExampleGroup {
private readonly _timezones = [
{
value: 'Americas',
items: [
'(GMT-5) New York',
'(GMT-8) Los Angeles',
'(GMT-6) Chicago',
'(GMT-5) Toronto',
'(GMT-3) São Paulo',
],
},
{
value: 'Europe',
items: [
'(GMT+0) London',
'(GMT+1) Paris',
'(GMT+1) Berlin',
'(GMT+1) Rome',
'(GMT+1) Madrid',
],
},
{
value: 'Asia/Pacific',
items: [
'(GMT+9) Tokyo',
'(GMT+8) Shanghai',
'(GMT+8) Singapore',
'(GMT+4) Dubai',
'(GMT+11) Sydney',
],
},
];
public readonly search = signal('');
public readonly filteredTimezones = computed(() => {
const search = this.search().toLowerCase();
return this._timezones
.map((timezoneGroup) => ({
...timezoneGroup,
items: timezoneGroup.items.filter((timezone) => timezone.toLowerCase().includes(search)),
}))
.filter((timezoneGroup) => timezoneGroup.items.length > 0);
});
}Object Values (Custom Label)
Handling complex objects by providing a custom itemToString function.
autocomplete-example-countries.ts
import { ChangeDetectionStrategy, Component, computed, signal } from '@angular/core';
import { HlmAutocompleteImports } from '@spartan-ng/helm/autocomplete';
interface Country {
name: string;
code: string;
flag: string;
}
@Component({
selector: 'app-autocomplete-example-countries',
standalone: true,
imports: [HlmAutocompleteImports],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<hlm-autocomplete
[(search)]="search"
[itemToString]="itemToString"
class="w-full max-w-[300px]"
>
<hlm-autocomplete-input placeholder="Search countries..." />
<hlm-autocomplete-content *hlmAutocompletePortal>
<hlm-autocomplete-empty>No countries found.</hlm-autocomplete-empty>
<div hlmAutocompleteList>
@for (option of filteredCountries(); track option.code) {
<hlm-autocomplete-item [value]="option">
{{ option.flag }} {{ option.name }}
</hlm-autocomplete-item>
}
</div>
</hlm-autocomplete-content>
</hlm-autocomplete>
`,
})
export class AutocompleteExampleCountries {
private readonly _countries: Country[] = [
{ name: 'Argentina', code: 'AR', flag: '🇦🇷' },
{ name: 'Australia', code: 'AU', flag: '🇦🇺' },
{ name: 'Belgium', code: 'BE', flag: '🇧🇪' },
{ name: 'Brazil', code: 'BR', flag: '🇧🇷' },
{ name: 'Canada', code: 'CA', flag: '🇨🇦' },
{ name: 'China', code: 'CN', flag: '🇨🇳' },
{ name: 'France', code: 'FR', flag: '🇫🇷' },
{ name: 'Germany', code: 'DE', flag: '🇩🇪' },
{ name: 'India', code: 'IN', flag: '🇮🇳' },
{ name: 'Italy', code: 'IT', flag: '🇮🇹' },
{ name: 'Japan', code: 'JP', flag: '🇯🇵' },
{ name: 'Mexico', code: 'MX', flag: '🇲🇽' },
{ name: 'Spain', code: 'ES', flag: '🇪🇸' },
{ name: 'United Kingdom', code: 'GB', flag: '🇬🇧' },
{ name: 'United States', code: 'US', flag: '🇺🇸' },
];
public readonly search = signal('');
public readonly itemToString = (item: Country): string => {
return `${item.flag} ${item.name}`;
};
public readonly filteredCountries = computed(() =>
this._countries.filter(
(country) =>
country.name.toLowerCase().includes(this.search().toLowerCase()) ||
`${country.flag} ${country.name}`.toLowerCase().includes(this.search().toLowerCase()),
),
);
}Async Search
Loading and filtering options from an external data source.
autocomplete-example-async.ts
import { ChangeDetectionStrategy, Component, resource, signal } from '@angular/core';
import { HlmAutocompleteImports } from '@spartan-ng/helm/autocomplete';
import { HlmSpinnerImports } from '@spartan-ng/helm/spinner';
interface Movie {
id: string;
title: string;
year: number;
}
@Component({
selector: 'app-autocomplete-example-async',
standalone: true,
imports: [HlmAutocompleteImports, HlmSpinnerImports],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<hlm-autocomplete
[(search)]="search"
[itemToString]="itemToString"
class="w-full max-w-[300px]"
>
<hlm-autocomplete-input placeholder="Search movies..." />
<hlm-autocomplete-content *hlmAutocompletePortal>
<hlm-autocomplete-status class="justify-start">
@if (options.error(); as error) {
{{ error }}
} @else if (options.isLoading()) {
<hlm-spinner />
Loading...
} @else if (search().length === 0) {
Type to search movies.
} @else if (options.hasValue() && options.value().length === 0) {
No results found for "{{ search() }}".
} @else if (options.hasValue()) {
{{ options.value().length }} results found
}
</hlm-autocomplete-status>
<div hlmAutocompleteList>
@if (options.hasValue()) {
@for (option of options.value(); track option.id) {
<hlm-autocomplete-item [value]="option">
<div class="flex flex-col gap-1 text-left">
<p class="font-medium">{{ option.title }}</p>
<p class="text-muted-foreground text-xs">{{ option.year }}</p>
</div>
</hlm-autocomplete-item>
}
}
</div>
</hlm-autocomplete-content>
</hlm-autocomplete>
`,
})
export class AutocompleteExampleAsync {
public readonly search = signal('');
public itemToString = (item: Movie) => item.title;
public options = resource({
defaultValue: [],
params: () => ({ search: this.search() }),
loader: async ({ params }) => {
const search = params.search;
if (search.length === 0) return [];
return await this.searchMovies(search.toLowerCase());
},
});
async searchMovies(query: string): Promise<Movie[]> {
await new Promise((resolve) => setTimeout(resolve, 300));
return this._topMovies.filter(
(movie) => movie.title.toLowerCase().includes(query) || movie.year.toString().includes(query),
);
}
private readonly _topMovies: Movie[] = [
{ id: '1', title: 'The Shawshank Redemption', year: 1994 },
{ id: '2', title: 'The Godfather', year: 1972 },
{ id: '3', title: 'The Dark Knight', year: 2008 },
{ id: '4', title: 'The Godfather Part II', year: 1974 },
{ id: '5', title: '12 Angry Men', year: 1957 },
{ id: '6', title: 'The Lord of the Rings: The Return of the King', year: 2003 },
{ id: '7', title: "Schindler's List", year: 1993 },
{ id: '8', title: 'Pulp Fiction', year: 1994 },
{ id: '9', title: 'Inception', year: 2010 },
{ id: '10', title: 'Fight Club', year: 1999 },
];
}Installation & Usage
Installation
ng g @spartan-ng/cli:ui autocomplete
Imports
import { HlmAutocompleteImports } from '@spartan-ng/helm/autocomplete';Basic Skeleton
<hlm-autocomplete [(search)]="search">
<hlm-autocomplete-input placeholder="Search..." />
<hlm-autocomplete-content *hlmAutocompletePortal>
<hlm-autocomplete-empty>No results found.</hlm-autocomplete-empty>
<div hlmAutocompleteList>
@for (option of filteredOptions(); track option) {
<hlm-autocomplete-item [value]="option">{{ option }}</hlm-autocomplete-item>
}
</div>
</hlm-autocomplete-content>
</hlm-autocomplete>API Reference
Brain API
The brain directives provide the functional logic and state management for the autocomplete.
| Directive | Selector | Inputs / Outputs |
|---|---|---|
| BrnAutocomplete | [brnAutocomplete] |
|
| BrnAutocompleteTrigger | [brnAutocompleteTrigger] | Trigger for opening the autocomplete content. |
Helm API
The helm components provide the default spartan/ui styling.
| Component / Directive | Selector | Description |
|---|---|---|
| HlmAutocompleteComponent | hlm-autocomplete | Main container component. |
| HlmAutocompleteInputDirective | hlm-autocomplete-input | Styled input field. Inputs: showClear: boolean. |
| HlmAutocompleteContentDirective | hlm-autocomplete-content | The overlay container. |
| HlmAutocompleteItemDirective | hlm-autocomplete-item | Individual option item. Input: value: T. |