Sitelet https://coreui.io/angular/docs/forms/autocomplete/

Angular Autocomplete Component

Autocomplete

CoreUI PRO
This component is part of CoreUI PRO – a powerful UI library with over 250 components and 25+ templates, designed to help you build modern, responsive apps faster. Fully compatible with Angular, Bootstrap, React.js, and Vue.js.

Release candidate (RC)

This component is in the Release Candidate phase and its API is considered stable. Minor adjustments may still occur before the final release.

Develop robust Angular Autocomplete components that enable dynamic search, dropdown suggestions, and seamless integration with external data sources. The pinnacle Angular Autocomplete solution for contemporary web applications.

Available in Other JavaScript Frameworks

CoreUI Angular Autocomplete Component is also available for Bootstrap, React, and Vue. Explore framework-specific implementations below:

Added in v5.5.20

Overview

The CoreUI Angular Autocomplete Component is a powerful, feature-rich autocomplete solution that enhances form usability by providing intelligent suggestions based on user types. Whether you use static data, APIs, or complex search logic, this component delivers a smooth, accessible user experience with extensive customization options.

Key features of this Angular Autocomplete include:

  • Dynamic dropdown suggestions with real time filtering
  • External data integration with API support
  • Advanced search capabilities
  • Accessibility-first design
  • Custom styles
  • Customizable templates

Basic Example

This straightforward demonstration provides a clear guide on how to implement a basic autocomplete input field, emphasizing the essential attributes and configurations required for its functionality.

import { Component } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { AutocompleteDirective, AutocompleteOption, FormLabelDirective } from '@coreui/angular';

@Component({
  selector: 'docs-autocomplete',
  imports: [AutocompleteDirective, ReactiveFormsModule, FormLabelDirective],
  templateUrl: './autocomplete.component.html'
})
export class AutocompleteComponent {
  readonly options: AutocompleteOption[] = ['Angular', 'Bootstrap', 'Next.js', 'React.js', 'Vue.js'];
}
<label cLabel for="ac-01">Framework</label>
<input
  [options]="options"
  [searchNoResultsLabel]="'No results found'"
  cAutocomplete
  cleaner
  highlightOptionsOnSearch
  indicator
  placeholder="Search technologies..."
  showHints
  type="text"
  value="Bootstrap"
  id="ac-01"
/>
<div class="form-text">Start typing to search option or provide value</div>

You can also use objects with option property for more structured data:

import { Component } from '@angular/core';
import { AutocompleteDirective, AutocompleteOption, FormLabelDirective } from '@coreui/angular';

@Component({
  selector: 'docs-autocomplete-2',
  imports: [AutocompleteDirective, FormLabelDirective],
  templateUrl: './autocomplete-2.component.html'
})
export class Autocomplete2Component {
  readonly options: AutocompleteOption[] = [
    {
      label: 'Angular',
      value: 1
    },
    {
      label: 'Bootstrap',
      value: 2
    },
    {
      label: 'Next.js',
      value: 3
    },
    {
      label: 'React.js',
      value: 4
    },
    {
      label: 'Vue.js',
      value: 5
    }
  ];
}
<label cLabel for="ac-02">Framework</label>
<input
  [options]="options"
  [searchNoResultsLabel]="'No results found'"
  cAutocomplete
  cleaner
  highlightOptionsOnSearch
  indicator
  placeholder="Search technologies..."
  showHints
  type="text"
  [value]="1"
  id="ac-02"
/>
<div class="form-text">Start typing to search option or provide value</div>

Each option is identified by its value, or by its label when it has no value, so these must be unique across all options, groups included. Two options with the same identifier cannot be told apart, and in development mode the listbox logs a duplicate option value warning.

For a minimal implementation without additional features:

import { Component } from '@angular/core';
import { AutocompleteDirective } from '@coreui/angular';

@Component({
  selector: 'docs-autocomplete-3',
  imports: [AutocompleteDirective],
  templateUrl: './autocomplete-3.component.html'
})
export class Autocomplete3Component {}
<input
  [options]="['Angular', 'Bootstrap', 'Next.js', 'React.js', 'Vue.js']"
  cAutocomplete
/>

Search functionality

Configure the search behavior to match your application’s needs. The search prop determines how the component handles user input and filtering.

By default, search operates only when the input field is focused and filters options internally:

import { Component } from '@angular/core';
import { AutocompleteDirective } from '@coreui/angular';

@Component({
  selector: 'docs-autocomplete-default-search',
  imports: [AutocompleteDirective],
  templateUrl: './autocomplete-default-search.component.html'
})
export class AutocompleteDefaultSearchComponent {}
<input
  [options]="['Angular', 'Bootstrap', 'Next.js', 'React.js', 'Vue.js']"
  cAutocomplete
/>

Typing anywhere within the component, for example on the cleaner or indicator button, always continues in the search input, so no configuration is needed. search="global" and { global: true } are deprecated and have no effect.

When external search is enabled search="external", the component delegates search operations to your custom logic or external API. This is perfect for server-side filtering, complex search algorithms, or third-party search services:

html
<input cAutocomplete
       [options]="filteredOptions"
       (inputChange)="handleSearch($event)"
       search="external"
>

See the External Data section for a complete working example.

Restricted selection

Limit users to only select from the provided options by enabling allowOnlyDefinedOptions. This prevents custom value entry:

import { Component } from '@angular/core';
import { AutocompleteDirective } from '@coreui/angular';

@Component({
  selector: 'docs-autocomplete-restricted-selection',
  imports: [AutocompleteDirective],
  templateUrl: './autocomplete-restricted-selection.component.html'
})
export class AutocompleteRestrictedSelectionComponent {}
<input
  [options]="['Angular', 'Bootstrap', 'Next.js', 'React.js', 'Vue.js']"
  allowOnlyDefinedOptions
  cAutocomplete
/>

UX enhancements

Enable intelligent hints and auto-completion features to improve user experience.

Show hints

Display intelligent completion hints that preview the first matching option as user types:

import { Component } from '@angular/core';
import { AutocompleteDirective } from '@coreui/angular';

@Component({
  selector: 'docs-autocomplete-show-hints',
  imports: [AutocompleteDirective],
  templateUrl: './autocomplete-show-hints.component.html'
})
export class AutocompleteShowHintsComponent {}
<input
  [options]="['Angular', 'Bootstrap', 'Next.js', 'React.js', 'Vue.js']"
  cAutocomplete
  showHints
/>

Highlight matching text

Highlight the parts of option labels that match the search with highlightOptionsOnSearch:

import { Component } from '@angular/core';
import { AutocompleteDirective } from '@coreui/angular';

@Component({
  selector: 'docs-autocomplete-highlight-matching-text',
  imports: [AutocompleteDirective],
  templateUrl: './autocomplete-highlight-matching-text.component.html'
})
export class AutocompleteHighlightMatchingTextComponent {}
<input
  [options]="['Angular', 'Bootstrap', 'Next.js', 'React.js', 'Vue.js']"
  cAutocomplete
  highlightOptionsOnSearch
/>

Validation states

Apply validation styling to indicate input validity.

import { Component } from '@angular/core';
import { AutocompleteDirective } from '@coreui/angular';

@Component({
  selector: 'docs-autocomplete-validation-states',
  imports: [AutocompleteDirective],
  templateUrl: './autocomplete-validation-states.component.html',
  styleUrl: './autocomplete-validation-states.component.scss'
})
export class AutocompleteValidationStatesComponent {}
<input
  [options]="['Angular', 'Bootstrap', 'Next.js', 'React.js', 'Vue.js']"
  [valid]="true"
  cAutocomplete
  placeholder="Valid autocomplete"
/>
<input
  [options]="['Angular', 'Bootstrap', 'Next.js', 'React.js', 'Vue.js']"
  [valid]="false"
  cAutocomplete
  placeholder="Invalid autocomplete"
/>
::ng-deep .autocomplete + .autocomplete {
  padding-top: .5rem;
}

Disabled state

Disable the component to prevent user interaction:

import { Component } from '@angular/core';
import { AutocompleteDirective } from '@coreui/angular';

@Component({
  selector: 'docs-autocomplete-disabled-state',
  imports: [AutocompleteDirective],
  templateUrl: './autocomplete-disabled-state.component.html'
})
export class AutocompleteDisabledStateComponent {}
<input
  [options]="['Angular', 'Bootstrap', 'Next.js', 'React.js', 'Vue.js']"
  cAutocomplete
  disabled
  indicator
  placeholder="Disabled autocomplete..."
/>

Read-only state

Make the field read-only with readOnly. Unlike disabled, a read-only field stays focusable, keeps its place in the tab order and submits its form value, but the dropdown does not open and the user cannot change the value by typing, selecting or clearing:

import { Component } from '@angular/core';
import { AutocompleteDirective } from '@coreui/angular';

@Component({
  selector: 'docs-autocomplete-read-only-state',
  imports: [AutocompleteDirective],
  templateUrl: './autocomplete-read-only-state.component.html'
})
export class AutocompleteReadOnlyStateComponent {}
<input
  [options]="['Angular', 'Bootstrap', 'Next.js', 'React.js', 'Vue.js']"
  cAutocomplete
  cleaner
  indicator
  readOnly
  value="Angular"
/>

Sizing

Choose from different sizes to match your design system and form layout:

import { Component } from '@angular/core';
import { AutocompleteDirective } from '@coreui/angular';

@Component({
  selector: 'docs-autocomplete-sizing',
  imports: [AutocompleteDirective],
  templateUrl: './autocomplete-sizing.component.html',
  styleUrl: './autocomplete-sizing.component.scss'
})
export class AutocompleteSizingComponent {}
<input
  [options]="['Angular', 'Bootstrap', 'Next.js', 'React.js', 'Vue.js']"
  cAutocomplete
  placeholder="Large autocomplete..."
  sizing="lg"
/>
<input
  [options]="['Angular', 'Bootstrap', 'Next.js', 'React.js', 'Vue.js']"
  cAutocomplete
  placeholder="Default autocomplete..."
/>
<input
  [options]="['Angular', 'Bootstrap', 'Next.js', 'React.js', 'Vue.js']"
  cAutocomplete
  placeholder="Small autocomplete..."
  sizing="sm"
/>
::ng-deep .autocomplete + .autocomplete {
  padding-top: .5rem;
}

Cleaner functionality

Enable a cleaner button to quickly clear input element:

import { Component } from '@angular/core';
import { AutocompleteDirective } from '@coreui/angular';

@Component({
  selector: 'docs-autocomplete-cleaner-functionality',
  imports: [AutocompleteDirective],
  templateUrl: './autocomplete-cleaner-functionality.component.html'
})
export class AutocompleteCleanerFunctionalityComponent {}
<input
  [options]="['Angular', 'Bootstrap', 'Next.js', 'React.js', 'Vue.js']"
  cAutocomplete
  cleaner
  placeholder="With cleaner button..."
/>

Bind [(visible)] to open and close the dropdown from your own code. visible is the requested state: the list is shown while the field is enabled, editable and has something to show, so a request made before the options arrive opens the list once they do. Leaving the field, disabling it, or a typed search that matches nothing (without searchNoResultsLabel) cancels the request.

visibleChange reports changes made by the user or by the component itself, such as typing, arrow keys, a click on the field or the indicator, a selection, Escape, a click outside or focus leaving the field, and for visible.set() on the exported directive. It is not emitted for changes made through the visible input, nor on the first render.

import { Component, signal } from '@angular/core';
import { AutocompleteDirective, ButtonDirective } from '@coreui/angular';

@Component({
  selector: 'docs-autocomplete-dropdown-visibility',
  imports: [AutocompleteDirective, ButtonDirective],
  templateUrl: './autocomplete-dropdown-visibility.component.html'
})
export class AutocompleteDropdownVisibilityComponent {
  readonly visible = signal(false);
}
<input
  [(visible)]="visible"
  [options]="['Angular', 'Bootstrap', 'Next.js', 'React.js', 'Vue.js']"
  cAutocomplete
  indicator
  placeholder="Search technologies..."
/>
<div class="mt-3">
  <button (click)="visible.set(!visible())" cButton color="primary">
    {{ visible() ? 'Close' : 'Open' }}
  </button>
  <span class="ms-3">visible: {{ visible() }}</span>
</div>

Custom templates

The CoreUI Angular Autocomplete Component provides the flexibility to personalize options and group labels by utilizing custom templates. You can easily customize the options using the optionTemplate, and for groups, you can use optionGroupTemplate, as demonstrated in the examples below:

import { Component, signal } from '@angular/core';
import { AutocompleteDirective, ColComponent, FormLabelDirective, RowComponent } from '@coreui/angular';
import { IconDirective } from '@coreui/icons-angular';
import { cifDe, cifEs, cifGb, cifPl, cifUs } from '@coreui/icons';

@Component({
  selector: 'docs-autocomplete-custom-templates',
  imports: [AutocompleteDirective, ColComponent, FormLabelDirective, RowComponent, IconDirective],
  templateUrl: './autocomplete-custom-templates.component.html'
})
export class AutocompleteCustomTemplatesComponent {
  readonly flags: Record<string, string[]> = {
    de: cifDe,
    es: cifEs,
    gb: cifGb,
    pl: cifPl,
    us: cifUs
  };

  readonly cities = [
    {
      label: 'Germany',
      value: 'de',
      options: [
        {
          label: 'Saarbrücken'
        },
        {
          label: 'Berlin'
        },
        {
          label: 'München'
        }
      ]
    },
    {
      label: 'Spain',
      value: 'es',
      options: [
        {
          label: 'Madrid'
        },
        {
          label: 'Alicante'
        },
        {
          label: 'Huesca'
        }
      ]
    },
    {
      label: 'United Kingdom',
      value: 'gb',
      options: [
        {
          label: 'Liverpool'
        },
        {
          label: 'London'
        },
        {
          label: 'Manchester'
        }
      ]
    },
    {
      label: 'United States',
      value: 'us',
      options: [
        {
          label: 'Austin'
        },
        {
          label: 'Chicago'
        },
        {
          label: 'Los Angeles'
        }
      ]
    }
  ];

  readonly countries = this.cities.map(({ label, value }) => ({ label, value }));

  readonly filteredCities = signal(this.cities);

  handleOptionChange(country: any) {
    if (country === null) {
      this.filteredCities.set(this.cities);
      return;
    }
    const match = this.cities.find((c) => c.value === country?.value);
    this.filteredCities.set(match ? [match] : this.cities);
  }
}
<c-row>
  <c-col>
    <label cLabel for="ac-15-1">Country</label>
    <input (optionChange)="handleOptionChange($event)" [optionTemplate]="optionTpl" [options]="countries" cAutocomplete id="ac-15-1" placeholder="Select country" showHints />
  </c-col>
  <c-col>
    <label cLabel for="ac-15-2">City</label>
    <input [optionGroupTemplate]="groupTpl" [options]="filteredCities()" cAutocomplete id="ac-15-2" placeholder="Select city" showHints resetSelectionOnOptionsChange />
  </c-col>
</c-row>

<ng-template #optionTpl let-idx="idx" let-option>
  <div class="d-flex">
    <svg [cIcon]="flags[option.value]" class="me-3" size="xl"></svg>
    {{ option.label }}
  </div>
</ng-template>

<ng-template #groupTpl let-option>
  <div class="d-flex align-items-center">
    <svg [cIcon]="flags[option.value]" class="me-2" size="lg"></svg>
    {{ option.label }}
  </div>
</ng-template>

External Data

One of the most powerful features of the Angular Autocomplete component is its ability to work with external data sources, such as REST APIs, GraphQL endpoints, or server-side search services. This is essential when dealing with large datasets that shouldn’t be loaded entirely into the client.

Implementation example

Here’s how to implement external data loading with proper debouncing to optimize API calls:

import { JsonPipe } from '@angular/common';
import { HttpErrorResponse } from '@angular/common/http';
import { Component, computed, inject, signal } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import {
  AutocompleteDirective,
  AutocompleteOption,
  BadgeComponent,
  ButtonDirective,
  ColComponent,
  FormLabelDirective,
  RowComponent
} from '@coreui/angular';
import { UsersService } from './users.service';
import { distinctUntilChanged, map } from 'rxjs/operators';

@Component({
  selector: 'docs-autocomplete-implementation',
  imports: [
    AutocompleteDirective,
    BadgeComponent,
    FormLabelDirective,
    JsonPipe,
    ReactiveFormsModule,
    ButtonDirective,
    RowComponent,
    ColComponent
  ],
  templateUrl: './autocomplete-implementation.component.html'
})
export class AutocompleteImplementationComponent {
  readonly #usersService = inject(UsersService);

  protected formGroup = new FormGroup({
    userName: new FormControl<string>('Barbara')
  });

  readonly searchName = signal<string | undefined>(this.formGroup.value.userName ?? undefined);

  #typedName = '';

  readonly usersResource = this.#usersService.getUsers(this.searchName);

  #users: AutocompleteOption[] = [];

  readonly users = computed(() => {
    const usersResource = this.usersResource;
    if (!usersResource.isLoading()) {
      const rawUsers = usersResource?.value()?.records?.map((user) => user.first_name as AutocompleteOption) ?? [];
      this.#users = [...new Set(rawUsers)];
    }
    return this.#users;
  });
  readonly error = computed(() => this.usersResource?.error() as HttpErrorResponse);
  readonly loading = computed(() => this.usersResource?.isLoading());

  // readonly #usersEffect = effect(() => {
  //   console.log('Users:', this.users());
  //   console.log('Loading:', this.loading());
  //   console.log('Error:', this.error());
  // });

  protected handleOptionChange($event: AutocompleteOption | null) {
    console.log('* handleOptionChange', $event);
    if ($event) {
      this.#typedName = '';
      this.searchName.set(typeof $event === 'string' ? $event : $event.label);
    } else {
      this.searchName.set(this.#typedName);
    }
  }

  protected handleValueChange($event: string | number | null | undefined) {
    console.log('* handleValueChange', $event);
  }

  protected handleInputChange($event: string) {
    console.log('* handleInputChange', $event);
    this.#typedName = $event;
    this.searchName.set($event);
  }

  protected changeValue() {
    const findName = 'Markus';
    this.searchName.set(findName);
    this.formGroup.get('userName')?.setValue(findName);
  }

  protected resetForm() {
    this.#typedName = '';
    this.searchName.set(undefined);
    this.formGroup.reset();
  }

  constructor() {
    this.formGroup.valueChanges
      .pipe(
        map((value) => value.userName),
        distinctUntilChanged()
      )
      .subscribe((value) => {
        console.log('@ valueChange', value);
      });
  }
}
<form [formGroup]="formGroup">
  <c-row>
    <c-col>
      <label cLabel for="ac-16">Users
        <c-badge color="success" size="sm">{{ loading() ? 'loading' : '' }}</c-badge>
      </label>
      <input
        (inputChange)="handleInputChange($event)"
        (optionChange)="handleOptionChange($event)"
        (valueChange)="handleValueChange($event)"
        [clearSearchOnSelect]="false"
        [delay]="500"
        [loading]="loading()"
        [options]="users()"
        search="external"
        cAutocomplete
        cleaner
        formControlName="userName"
        highlightOptionsOnSearch
        id="ac-16"
        indicator
        placeholder="Search users..."
        showHints
        virtualScroller
      />
      <div class="form-text">Please select the user.</div>
      <hr>
      <button cButton (click)="changeValue()" class="me-1">Change</button>
      <button cButton (click)="resetForm()">Reset</button>
    </c-col>
    <c-col>
      <span> Form value: {{ formGroup.value | json }}</span>
    </c-col>
  </c-row>

  @if (error()) {
    @let message = error().error?.message || 'Unknown error';
    @let status = error().status;
    <div class="text-danger">An error {{ status }} occurred: {{ message }}</div>
  }
</form>
import { Injectable, Signal } from '@angular/core';
import { HttpParams, httpResource } from '@angular/common/http';

@Injectable({
  providedIn: 'root'
})
export class UsersService {
  private usersUrl = 'https://apitest.coreui.io/demos/users';

  getUsers(userName: Signal<string | undefined>) {
    const apiParams: IApiParams = {
      limit: 1000,
      offset: 0,
      sort: 'first_name'
    };

    return httpResource<IUsersResponse>(() => {
      const firstName = userName();
      return {
        url: this.usersUrl,
        params: new HttpParams({ fromObject: { ...apiParams, ...(firstName ? { first_name: firstName } : {}) } })
      };
    });
  }
}

export interface IUsersResponse {
  number_of_records: number;
  number_of_matching_records: number;
  records: IUser[];
}

export interface IUser {
  id: number;
  first_name: string;
  last_name: string;
  email: string;
  country: string;
  ip_address: string;
  registered: string;
}
export interface IApiParams {
  offset?: number;
  limit?: number;
  columnFilter?: string;
  columnSorter?: string;
  sort?: string;
}

Resetting the selection

When options come from a server, a selected option may be missing from the next response. Enable resetSelectionOnOptionsChange to clear such a selection: when a new, non-empty options list no longer contains the selected option, and the value matches no other option, the field and the search are emptied, the value becomes '' and optionChange emits null.

html
<input
  (inputChange)="search.set($event)"
  [options]="users()"
  cAutocomplete
  formControlName="user"
  resetSelectionOnOptionsChange
  search="external"
/>

The selection is kept when the new list still contains it, when the list is empty (for example while a request is in flight), when the value is a custom one typed by the user, and when your code writes a new value together with the new options. With external search and the default clearSearchOnSelect, the search is emptied after a selection, so the next list is the one returned for an empty search: a selected option missing from it is cleared.

Performance optimization

Added in v5.7.32

For large datasets, enable virtualScroller to render only the options that fit in the dropdown. Only the visible rows and a buffer around them are in the DOM, and keyboard navigation (Arrow Up, Arrow Down, Home, End, Page Up, Page Down) still reaches every option. visibleItems sets the height of the list in rows; itemSize is the initial row height in pixels, replaced by the measured height of a rendered option.

The virtual scroller is loaded on demand, so an application that does not use it does not ship @angular/cdk/scrolling in its initial bundle.

import { Component } from '@angular/core';
import { AutocompleteDirective } from '@coreui/angular';

@Component({
  selector: 'docs-autocomplete-virtual-scroller',
  imports: [AutocompleteDirective],
  templateUrl: './autocomplete-virtual-scroller.component.html'
})
export class AutocompleteVirtualScrollerComponent {
  readonly options = Array.from({ length: 10000 }, (_, index) => ({ label: `Option ${index + 1}`, value: index + 1 }));
}
<input
  [options]="options"
  [visibleItems]="8"
  cAutocomplete
  cleaner
  indicator
  placeholder="Search 10 000 options..."
  virtualScroller
/>

Forms

Angular handles user input through reactive, template-driven and signal forms. CoreUI Autocomplete supports all three approaches.

Reactive

The Angular Autocomplete component can be used with reactive forms. You can bind the value to a form control using the formControlName directive.

import { JsonPipe } from '@angular/common';
import { Component } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { distinctUntilChanged, map } from 'rxjs/operators';
import {
  AutocompleteDirective,
  ButtonDirective,
  ColComponent,
  FormFeedbackComponent,
  IAutocompleteOption,
  RowComponent
} from '@coreui/angular';

@Component({
  selector: 'docs-autocomplete-reactive',
  imports: [
    AutocompleteDirective,
    ButtonDirective,
    ColComponent,
    FormFeedbackComponent,
    JsonPipe,
    ReactiveFormsModule,
    RowComponent
  ],
  templateUrl: './autocomplete-reactive.component.html'
})
export class AutocompleteReactiveComponent {
  readonly formGroup = new FormGroup({
    framework: new FormControl<string | null>('React.js', { nonNullable: true, validators: Validators.required })
  });

  constructor() {
    this.formGroup.valueChanges
      .pipe(
        map((value) => value.framework),
        distinctUntilChanged()
      )
      .subscribe((value) => {
        console.log('* Value changed: ', value);
        console.log('* Control dirty: ', this.formGroup.get('framework')?.dirty);
        console.log('* Control pristine: ', this.formGroup.get('framework')?.pristine);
        console.log('* Control touched: ', this.formGroup.get('framework')?.touched);
      });
  }

  resetForm() {
    this.formGroup.reset({ framework: '' });
  }

  protected changeValue() {
    this.formGroup.get('framework')?.setValue('Angular');
  }

  protected setServerError() {
    const control = this.formGroup.get('framework');
    control?.setErrors({ ...control.errors, server: true });
    control?.markAsTouched();
  }

  protected clearServerError() {
    this.formGroup.get('framework')?.updateValueAndValidity();
  }

  handleOptionChange($event: IAutocompleteOption | null) {
    console.log('* handleOptionChange: ', $event);
  }

  handleValueChange($event: string | number | null | undefined) {
    console.log('* handleValueChange: ', $event);
  }

  handleInputChange($event: any) {
    console.log('* handleInputChange: ', `*${$event}*`, typeof $event);
  }
}
<form [formGroup]="formGroup">
  <c-row>
    <c-col>
      @let control = formGroup.get('framework');
      <input
        (inputChange)="handleInputChange($event)"
        (optionChange)="handleOptionChange($event)"
        (valueChange)="handleValueChange($event)"
        [options]="['Angular', 'Bootstrap', 'Next.js', 'React.js', 'Vue.js']"
        [valid]="control?.touched ? !control?.invalid : undefined"
        cAutocomplete
        cleaner
        formControlName="framework"
        showHints
      />
      @let errors = control?.errors;
      @if (control?.touched && errors) {
        <c-form-feedback [valid]="false">
          {{ errors['server'] ? 'framework was rejected by the server' : 'framework is required' }}
        </c-form-feedback>
      }
    </c-col>
    <c-col>
      <span> Form value: {{ formGroup.value | json }}</span>
      <ul>
        <li> dirty: {{ formGroup.get('framework')?.dirty }}</li>
        <li> pristine: {{ formGroup.get('framework')?.pristine }}</li>
        <li> touched: {{ formGroup.get('framework')?.touched }}</li>
        <li> errors: {{ formGroup.get('framework')?.errors | json }}</li>
      </ul>
    </c-col>
  </c-row>
</form>
<br>
<button (click)="changeValue()" cButton class="me-1">Change</button>
<button (click)="resetForm()" cButton class="me-1">Reset</button>
<button (click)="setServerError()" cButton class="me-1">Server error</button>
<button (click)="clearServerError()" cButton>Clear server error</button>

Template-driven

The Angular Autocomplete component can be used in template-driven forms. Bind the value with [(ngModel)] and read the control state through a template reference to ngModel.

import { JsonPipe } from '@angular/common';
import { Component, signal, viewChild } from '@angular/core';
import { FormsModule, NgForm } from '@angular/forms';
import {
  AutocompleteDirective,
  ButtonDirective,
  ColComponent,
  FormFeedbackComponent,
  IAutocompleteOption,
  RowComponent
} from '@coreui/angular';

@Component({
  selector: 'docs-autocomplete-template-driven',
  imports: [
    AutocompleteDirective,
    ButtonDirective,
    ColComponent,
    FormFeedbackComponent,
    FormsModule,
    JsonPipe,
    RowComponent
  ],
  templateUrl: './autocomplete-template-driven.component.html'
})
export class AutocompleteTemplateDrivenComponent {
  readonly form = viewChild.required<NgForm>('form');

  readonly framework = signal<string | null>('Angular');

  readonly frameworks = ['Angular', 'Bootstrap', 'Next.js', 'React.js', 'Vue.js'];

  protected changeValue() {
    this.framework.set('Next.js');
  }

  protected resetForm() {
    this.framework.set('');
    this.form().resetForm({ framework: '' });
  }

  protected handleValueChange($event: number | string | null | undefined) {
    console.log('handleValueChange', $event);
  }

  protected handleOptionChange($event: IAutocompleteOption | null) {
    console.log('handleOptionChange', $event);
  }

  protected handleInputChange($event: string) {
    console.log('handleInputChange', $event);
  }
}
<form #form="ngForm">
  <c-row>
    <c-col>
      <label for="framework22">Framework:</label>
      <input
        #ctrl="ngModel"
        (inputChange)="handleInputChange($event)"
        (optionChange)="handleOptionChange($event)"
        (valueChange)="handleValueChange($event)"
        [(ngModel)]="framework"
        [valid]="ctrl.touched ? !ctrl.invalid : undefined"
        [delay]="300"
        [options]="frameworks"
        allowOnlyDefinedOptions
        cAutocomplete
        cleaner
        id="framework22"
        name="framework"
        required
        showHints
      />
      @if (ctrl.touched && ctrl.errors?.['required']) {
        <c-form-feedback [valid]="false">framework is required</c-form-feedback>
      }
    </c-col>
    <c-col>
      <strong>Control state: </strong>
      <ul>
        <li>Form value: {{ form.value | json }}</li>
        <li>Model: {{ framework() | json }}</li>
        <li>Dirty: {{ ctrl.dirty }}</li>
        <li>Pristine: {{ ctrl.pristine }}</li>
        <li>Touched: {{ ctrl.touched }}</li>
        <li>Errors: {{ ctrl.errors | json }}</li>
      </ul>
    </c-col>
  </c-row>
  <div class="mt-3">
    <button type="button" cButton class="me-1" (click)="changeValue()">Change</button>
    <button type="button" cButton class="me-1" (click)="resetForm()">Reset</button>
  </div>
</form>

Signal forms

The Angular Autocomplete component works with signal forms. (preview)

import { JsonPipe } from '@angular/common';
import { Component, signal } from '@angular/core';
import { form, FormField, FormRoot, required } from '@angular/forms/signals';
import {
  AutocompleteDirective,
  ButtonDirective,
  ColComponent,
  FormFeedbackComponent,
  IAutocompleteOption,
  RowComponent
} from '@coreui/angular';

@Component({
  selector: 'docs-autocomplete-signal-forms',
  imports: [
    AutocompleteDirective,
    ButtonDirective,
    ColComponent,
    FormFeedbackComponent,
    FormField,
    FormRoot,
    RowComponent,
    JsonPipe
  ],
  templateUrl: './autocomplete-signal-forms.component.html'
})
export class AutocompleteSignalFormsComponent {
  readonly frameworkModel = signal({ framework: '' });

  readonly frameworkForm = form(this.frameworkModel, (schemaPath) => {
    required(schemaPath.framework, { message: 'framework is required' });
  });

  readonly frameworks = ['Angular', 'Bootstrap', 'Next.js', 'React.js', 'Vue.js'];

  protected changeValue() {
    this.frameworkModel.set({ framework: 'Next.js' });
  }

  protected resetForm() {
    this.frameworkForm().reset({ framework: '' });
  }

  protected handleValueChange($event: number | string | null | undefined) {
    console.log('handleValueChange', $event);
  }

  protected handleOptionChange($event: IAutocompleteOption | null) {
    console.log('handleOptionChange', $event);
  }

  protected handleInputChange($event: string) {
    console.log('handleInputChange', $event);
  }
}
<form [formRoot]="frameworkForm">
  <c-row>
    <c-col>
      <label for="framework21">Framework:</label>
      @let ctrlState = frameworkForm.framework();
      <input
        (inputChange)="handleInputChange($event)"
        (optionChange)="handleOptionChange($event)"
        (valueChange)="handleValueChange($event)"
        [delay]="300"
        [formField]="frameworkForm.framework"
        [options]="frameworks"
        [valid]="
          ctrlState.touched() && ctrlState.invalid()
            ? false
            : ctrlState.touched() && !ctrlState.invalid()
              ? true
              : undefined
        "
        allowOnlyDefinedOptions
        cAutocomplete
        cleaner
        id="framework21"
        showHints
      />
      @if (ctrlState.touched() && ctrlState.invalid()) {
        @for (error of ctrlState.errors(); track error.kind) {
          @if (error.kind === 'required') {
            <c-form-feedback [valid]="false">
              {{ error.message }}
            </c-form-feedback>
          }
        }
      }
    </c-col>
    <c-col>
      <strong>Control state: </strong>
      <ul>
        <li>Value: {{ frameworkForm.framework().value() | json }}</li>
        <li>Dirty: {{ frameworkForm.framework().dirty() }}</li>
        <li>Touched: {{ frameworkForm.framework().touched() }}</li>
        <li>Errors: {{ frameworkForm.framework().errors() | json }}</li>
      </ul>
    </c-col>
  </c-row>
  <div class="mt-3">
    <button type="button" cButton class="me-1" (click)="changeValue()">Change</button>
    <button type="reset" cButton class="me-1" (click)="resetForm()">Reset</button>
  </div>
</form>

Accessibility

The Autocomplete component includes several accessibility features:

  • ARIA attributes: The input is a combobox that controls a listbox of option elements, with aria-expanded, aria-controls, aria-haspopup, aria-autocomplete and aria-selected kept in sync. Built on @angular/aria.
  • Screen reader support: Descriptive labels and announcements for state changes
  • Keyboard navigation: Full keyboard support with arrow keys, Home, End, Page Up, Page Down, Enter, Escape, and Tab
  • Focus management: Focus stays in the input while navigating; the highlighted option is announced through aria-activedescendant
  • Option groups: Grouped options are rendered inside a group named by its label, so screen readers announce the group of the highlighted option. With virtualScroller, each option references its group labels through aria-describedby instead.
  • Semantic markup: Uses appropriate HTML elements and structure

Keyboard shortcuts

KeyAction
Arrow DownNavigate to the next option or open dropdown
Arrow UpNavigate to the previous option
Page Down Page UpMove the highlight by visibleItems options
Home EndMove the highlight to the first or last option while the dropdown is open
EnterSelect the highlighted option, or the typed text when no option is highlighted (with allowOnlyDefinedOptions only text matching an option label is accepted)
EscapeClose the dropdown
TabAccept hint completion (when hints are enabled), or select the highlighted option and move focus
Backspace DeleteClear input and trigger search

Customizing

CSS variables

Angular CoreUI Autocomplete use local CSS variables for easy customization. Values for the CSS variables are set via Sass, so Sass customization is still supported, too.

scss
.autocomplete {
  --cui-autocomplete-zindex: #{$autocomplete-zindex};
  --cui-autocomplete-font-family: #{$autocomplete-font-family};
  --cui-autocomplete-font-size: #{$autocomplete-font-size};
  --cui-autocomplete-font-weight: #{$autocomplete-font-weight};
  --cui-autocomplete-line-height: #{$autocomplete-line-height};
  --cui-autocomplete-color: #{$autocomplete-color};
  --cui-autocomplete-bg: #{$autocomplete-bg};
  --cui-autocomplete-box-shadow: #{$autocomplete-box-shadow};
  --cui-autocomplete-border-width: #{$autocomplete-border-width};
  --cui-autocomplete-border-color: #{$autocomplete-border-color};
  --cui-autocomplete-border-radius: #{$autocomplete-border-radius};
  --cui-autocomplete-disabled-color: #{$autocomplete-disabled-color};
  --cui-autocomplete-disabled-bg: #{$autocomplete-disabled-bg};
  --cui-autocomplete-disabled-border-color: #{$autocomplete-disabled-border-color};
  --cui-autocomplete-focus-color: #{$autocomplete-focus-color};
  --cui-autocomplete-focus-bg: #{$autocomplete-focus-bg};
  --cui-autocomplete-focus-border-color: #{$autocomplete-focus-border-color};
  --cui-autocomplete-focus-box-shadow: #{$autocomplete-focus-box-shadow};
  --cui-autocomplete-placeholder-color: #{$autocomplete-placeholder-color};
  --cui-autocomplete-padding-y: #{$autocomplete-padding-y};
  --cui-autocomplete-padding-x: #{$autocomplete-padding-x};
  --cui-autocomplete-cleaner-width: #{$autocomplete-cleaner-width};
  --cui-autocomplete-cleaner-height: #{$autocomplete-cleaner-height};
  --cui-autocomplete-cleaner-padding-y: #{$autocomplete-cleaner-padding-y};
  --cui-autocomplete-cleaner-padding-x: #{$autocomplete-cleaner-padding-x};
  --cui-autocomplete-cleaner-icon: #{escape-svg($autocomplete-cleaner-icon)};
  --cui-autocomplete-cleaner-icon-color: #{$autocomplete-cleaner-icon-color};
  --cui-autocomplete-cleaner-icon-hover-color: #{$autocomplete-cleaner-icon-hover-color};
  --cui-autocomplete-cleaner-icon-size: #{$autocomplete-cleaner-icon-size};
  --cui-autocomplete-indicator-width: #{$autocomplete-indicator-width};
  --cui-autocomplete-indicator-height: #{$autocomplete-indicator-height};
  --cui-autocomplete-indicator-padding-y: #{$autocomplete-indicator-padding-y};
  --cui-autocomplete-indicator-padding-x: #{$autocomplete-indicator-padding-x};
  --cui-autocomplete-indicator-icon: #{escape-svg($autocomplete-indicator-icon)};
  --cui-autocomplete-indicator-icon-color: #{$autocomplete-indicator-icon-color};
  --cui-autocomplete-indicator-icon-hover-color: #{$autocomplete-indicator-icon-hover-color};
  --cui-autocomplete-indicator-icon-size: #{$autocomplete-indicator-icon-size};
  --cui-autocomplete-dropdown-min-width: #{$autocomplete-dropdown-min-width};
  --cui-autocomplete-dropdown-bg: #{$autocomplete-dropdown-bg};
  --cui-autocomplete-dropdown-border-width: #{$autocomplete-dropdown-border-width};
  --cui-autocomplete-dropdown-border-color: #{$autocomplete-dropdown-border-color};
  --cui-autocomplete-dropdown-border-radius: #{$autocomplete-dropdown-border-radius};
  --cui-autocomplete-dropdown-box-shadow: #{$autocomplete-dropdown-box-shadow};
  --cui-autocomplete-options-padding-y: #{$autocomplete-options-padding-y};
  --cui-autocomplete-options-padding-x: #{$autocomplete-options-padding-x};
  --cui-autocomplete-options-font-size: #{$autocomplete-options-font-size};
  --cui-autocomplete-options-font-weight: #{$autocomplete-options-font-weight};
  --cui-autocomplete-options-color: #{$autocomplete-options-color};
  --cui-autocomplete-optgroup-label-padding-y: #{$autocomplete-optgroup-label-padding-y};
  --cui-autocomplete-optgroup-label-padding-x: #{$autocomplete-optgroup-label-padding-x};
  --cui-autocomplete-optgroup-label-font-size: #{$autocomplete-optgroup-label-font-size};
  --cui-autocomplete-optgroup-label-font-weight: #{$autocomplete-optgroup-label-font-weight};
  --cui-autocomplete-optgroup-label-color: #{$autocomplete-optgroup-label-color};
  --cui-autocomplete-optgroup-label-text-transform: #{$autocomplete-optgroup-label-text-transform};
  --cui-autocomplete-option-padding-y: #{$autocomplete-option-padding-y};
  --cui-autocomplete-option-padding-x: #{$autocomplete-option-padding-x};
  --cui-autocomplete-option-margin-y: #{$autocomplete-option-margin-y};
  --cui-autocomplete-option-margin-x: #{$autocomplete-option-margin-x};
  --cui-autocomplete-option-border-width: #{$autocomplete-option-border-width};
  --cui-autocomplete-option-border-color: #{$autocomplete-option-border-color};
  --cui-autocomplete-option-border-radius: #{$autocomplete-option-border-radius};
  --cui-autocomplete-option-box-shadow: #{$autocomplete-option-box-shadow};
  --cui-autocomplete-option-hover-color: #{$autocomplete-option-hover-color};
  --cui-autocomplete-option-hover-bg: #{$autocomplete-option-hover-bg};
  --cui-autocomplete-option-focus-box-shadow: #{$autocomplete-option-focus-box-shadow};
  --cui-autocomplete-option-disabled-color: #{$autocomplete-option-disabled-color};
  --cui-autocomplete-option-indicator-width: #{$autocomplete-option-indicator-width};
  --cui-autocomplete-option-indicator-bg: #{$autocomplete-option-indicator-bg};
  --cui-autocomplete-option-indicator-border: #{$autocomplete-option-indicator-border};
  --cui-autocomplete-option-indicator-border-radius: #{$autocomplete-option-indicator-border-radius};
  --cui-autocomplete-option-selected-bg: #{$autocomplete-option-selected-bg};
  --cui-autocomplete-option-selected-indicator-bg: #{$autocomplete-option-selected-indicator-bg};
  --cui-autocomplete-option-selected-indicator-bg-image: #{escape-svg($autocomplete-option-selected-indicator-bg-image)};
  --cui-autocomplete-option-selected-indicator-border-color: #{$autocomplete-option-selected-indicator-border-color};
}

SASS variables

scss
$autocomplete-zindex:                    1000 !default;
$autocomplete-font-family:               $input-font-family !default;
$autocomplete-font-size:                 $input-font-size !default;
$autocomplete-font-weight:               $input-font-weight !default;
$autocomplete-line-height:               $input-line-height !default;
$autocomplete-padding-y:                 $input-padding-y !default;
$autocomplete-padding-x:                 $input-padding-x !default;
$autocomplete-color:                     $input-color !default;
$autocomplete-bg:                        $input-bg !default;
$autocomplete-box-shadow:                $box-shadow-inset !default;

$autocomplete-border-width:              $input-border-width !default;
$autocomplete-border-color:              $input-border-color !default;
$autocomplete-border-radius:             $input-border-radius !default;
$autocomplete-border-radius-sm:          $input-border-radius-sm !default;
$autocomplete-border-radius-lg:          $input-border-radius-lg !default;

$autocomplete-disabled-color:            $input-disabled-color !default;
$autocomplete-disabled-bg:               $input-disabled-bg !default;
$autocomplete-disabled-border-color:     $input-disabled-border-color !default;

$autocomplete-focus-color:               $input-focus-color !default;
$autocomplete-focus-bg:                  $input-focus-bg !default;
$autocomplete-focus-border-color:        $input-focus-border-color !default;
$autocomplete-focus-box-shadow:          $input-btn-focus-box-shadow !default;

$autocomplete-placeholder-color:         var(--cui-secondary-color) !default;

$autocomplete-invalid-border-color:      $form-invalid-border-color !default;
$autocomplete-valid-border-color:        $form-valid-border-color !default;

$autocomplete-cleaner-width:             1.5rem !default;
$autocomplete-cleaner-height:            1.5rem !default;
$autocomplete-cleaner-padding-x:         0 !default;
$autocomplete-cleaner-padding-y:         0 !default;
$autocomplete-cleaner-icon:              url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='#000'><path d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/></svg>") !default;
$autocomplete-cleaner-icon-color:        var(--cui-tertiary-color) !default;
$autocomplete-cleaner-icon-hover-color:  var(--cui-body-color) !default;
$autocomplete-cleaner-icon-size:         .625rem !default;

$autocomplete-indicator-width:             1.5rem !default;
$autocomplete-indicator-height:            1.5rem !default;
$autocomplete-indicator-padding-x:         0 !default;
$autocomplete-indicator-padding-y:         0 !default;
$autocomplete-indicator-icon:              url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512' fill='#000'><path d='M256.045 416.136.717 160.807l29.579-29.579 225.749 225.748 225.749-225.748 29.579 29.579-255.328 255.329z'/></svg>") !default;
$autocomplete-indicator-icon-color:        var(--cui-tertiary-color) !default;
$autocomplete-indicator-icon-hover-color:  var(--cui-body-color) !default;
$autocomplete-indicator-icon-size:         .75rem !default;

$autocomplete-dropdown-min-width:        100% !default;
$autocomplete-dropdown-bg:               var(--cui-body-bg) !default;
$autocomplete-dropdown-border-color:     var(--cui-border-color) !default;
$autocomplete-dropdown-border-width:     var(--cui-border-width) !default;
$autocomplete-dropdown-border-radius:    var(--cui-border-radius) !default;
$autocomplete-dropdown-box-shadow:       var(--cui-box-shadow) !default;

$autocomplete-options-padding-y:         .5rem !default;
$autocomplete-options-padding-x:         .5rem !default;
$autocomplete-options-font-size:         $font-size-base !default;
$autocomplete-options-font-weight:       $font-weight-normal !default;
$autocomplete-options-color:             var(--cui-body-color) !default;

$autocomplete-optgroup-label-padding-y:       .5rem !default;
$autocomplete-optgroup-label-padding-x:       .625rem !default;
$autocomplete-optgroup-label-font-size:       80% !default;
$autocomplete-optgroup-label-font-weight:     $font-weight-bold !default;
$autocomplete-optgroup-label-color:           var(--cui-tertiary-color) !default;
$autocomplete-optgroup-label-text-transform:  uppercase !default;

$autocomplete-option-padding-y:               .5rem !default;
$autocomplete-option-padding-x:               .75rem !default;
$autocomplete-option-margin-y:                1px !default;
$autocomplete-option-margin-x:                0 !default;
$autocomplete-option-border-width:            $input-border-width !default;
$autocomplete-option-border-color:            transparent !default;
$autocomplete-option-border-radius:           var(--cui-border-radius) !default;
$autocomplete-option-box-shadow:              $box-shadow-inset !default;

$autocomplete-option-hover-color:             var(--cui-body-color) !default;
$autocomplete-option-hover-bg:                var(--cui-tertiary-bg) !default;

$autocomplete-option-focus-box-shadow:        $input-btn-focus-box-shadow !default;

$autocomplete-option-indicator-width:          1em !default;
$autocomplete-option-indicator-bg:             $form-check-input-bg !default;
$autocomplete-option-indicator-border:         $form-check-input-border !default;
$autocomplete-option-indicator-border-radius:  .25em !default;

$autocomplete-option-selected-bg:                      var(--cui-secondary-bg) !default;
$autocomplete-option-selected-indicator-bg:            $form-check-input-checked-bg-color !default;
$autocomplete-option-selected-indicator-bg-image:      $form-check-input-checked-bg-image !default;
$autocomplete-option-selected-indicator-border-color:  $autocomplete-option-selected-indicator-bg !default;

$autocomplete-option-disabled-color:        var(--cui-secondary-color) !default;

$autocomplete-font-size-lg:                 $input-font-size-lg !default;
$autocomplete-padding-y-lg:                 $input-padding-y-lg !default;
$autocomplete-padding-x-lg:                 $input-padding-x-lg !default;

$autocomplete-font-size-sm:                 $input-font-size-sm !default;
$autocomplete-padding-y-sm:                 $input-padding-y-sm !default;
$autocomplete-padding-x-sm:                 $input-padding-x-sm !default;

API reference

Autocomplete Module

ts
import { NgModule } from '@angular/core';
import { AutocompleteModule } from '@coreui/angular';

@NgModule({
  imports: [AutocompleteModule]
})
export class CustomAppModule {}

Autocomplete Standalone

ts
import { Component } from '@angular/core';
import { AutocompleteDirective } from '@coreui/angular';

@Component({
  template: ` <input [options]="['Angular', 'Bootstrap', 'Next.js', 'React.js', 'Vue.js']" cAutocomplete /> `,
  imports: [AutocompleteDirective],
  standalone: true
})
export class CustomAppComponent {}

cAutocomplete

directive

jsx
import { AutocompleteDirective } from '@coreui/angular-pro'

Props

PropertyDefaultType
allowOnlyDefinedOptionsfalseboolean

Only allow selection of predefined options. When true, users cannot enter custom values that are not in the options list. When false, users can enter and select custom values.

ariaCleanerLabel5.7.7+'Clear selection'string

Sets the accessible label (aria-label) for the button that clears the current selection. This improves accessibility for screen readers.

ariaIndicatorLabel5.7.7+'Toggle visibility of options menu'string

Sets the accessible label (aria-label) for the dropdown toggle indicator button. This improves accessibility for screen readers.

cleanerfalseboolean

Enables selection cleaner element. When true, displays a clear button that allows users to reset the selection. The cleaner button is only shown when there is a selection and the component is not disabled or read-only.

clearSearchOnSelecttrueboolean

Clears the search after an option is selected: the list is filtered by an empty search again and inputChange emits '', so with external search the options can be reloaded for it. When false, the search that led to the selection is kept. The field shows the selected label either way.

delay150number

Debounce delay in milliseconds for filtering options based on search input. Controls how quickly the options list updates as the user types. Higher values reduce update frequency for better performance with large datasets.

disabledfalseboolean

Toggle the disabled state for the component. When true, the Angular autocomplete is non-interactive and appears visually disabled. Users cannot type, select options, or trigger the dropdown.

Highlight options that match the search criteria. When true, matching portions of option labels are visually highlighted based on the current search input value.

id'autocomplete-<nextId>'string

Unique identifier for the Autocomplete component. If not provided, a default ID will be generated.

indicatorfalseboolean

Show dropdown indicator/arrow button. When true, displays a dropdown arrow button that can be clicked to manually show or hide options dropdown.

itemSize40number

Initial height of an option row in pixels for the virtual scroller, replaced by the measured row height once options render.

loadingfalseboolean

When set, the options list will have a loading style: loading spinner and reduced opacity. Use this to indicate that options are being fetched asynchronously. While loading, the list opens only when the user has typed a search, and the no results label is not shown.

optionGroupTemplate-TemplateRef<any>

Custom template for rendering option groups. Allows customization of how option group headers appear in the dropdown.

options-AutocompleteOption[]

List of option elements. Can contain Option objects, OptionsGroup objects, or plain strings. Plain strings are converted to simple Option objects internally. Each option is identified by its value, or by its label when it has none, so these identifiers must be unique across all options. This is a required prop - the Angular autocomplete needs options to function.

optionsMaxHeight'auto'string, number

Sets maxHeight of options list. Controls the maximum height of the dropdown options container. Can be a number (pixels) or a CSS length string (e.g., '200px', '10rem'). When content exceeds this height, a scrollbar will appear.

optionTemplate-TemplateRef<any>

Custom template for rendering individual options. Allows complete customization of how each option appears in the dropdown.

placeholder-string

Specifies a short hint that is visible in the search input. Displayed when the input is empty to guide user interaction. Standard HTML input placeholder behavior.

popperOptionsdefaultPopperOptionsPartial<Options>

Optional popper Options object

readOnlyfalseboolean

Toggle the readonly state for the component. When true, the field keeps focus, tab order and its form value, but the dropdown does not open and the user cannot change the value by typing, selecting or clearing.

resetSelectionOnOptionsChangefalseboolean

Determines whether the selected options should be cleared when the options list is updated. When true, a selected option that is missing from a new, non-empty options list, and whose value no longer matches any option, is cleared: the field and the search are emptied, the value becomes '' and optionChange emits null. This ensures that outdated selections are not retained when new options are provided. Options still in the list, custom values and a value written from outside together with the new options are kept.

Enables and configures search functionality. - 'external': Search is handled externally, filtering is not applied internally - Object with an external boolean property 'global' and { global: true } are deprecated and have no effect: typing anywhere in the component always continues in the search input.

searchNoResultsLabelfalsestring, boolean, TemplateRef<any>

Sets the label for no results when filtering. - false: Don't show any message when no results found - true: Show default "No results found" message - string: Show custom text message - TemplateRef: Show custom component/element

showHintsfalseboolean

Show hint options based on the current input value. When true, displays a preview/hint of the first matching option as semi-transparent text in the input field, similar to browser autocomplete.

sizing-'', 'sm', 'lg'

Size the component small or large. - 'sm': Small size variant - 'lg': Large size variant - undefined: Default/medium size

validundefinedboolean

Validation state of the field: true valid, false invalid, undefined none. Not derived from a bound form control; bind it, e.g. [valid]="control.touched ? !control.invalid : undefined".

valueundefinedstring, number, null

Sets the selected value for the Angular autocomplete component, two-way bindable with [(value)]. Matched against option values first, then against option labels. Selecting an option writes its value, or its label when it has none; typed text that matches no option is written as is, unless allowOnlyDefinedOptions is set.

virtualScroller5.7.32+falseboolean

Enable virtual scroller for the options list. When true, only visible options are rendered in the DOM for better performance with large option lists. Works in conjunction with visibleItems and itemSize.

visiblefalseboolean

Whether the dropdown should be open, two-way bindable with [(visible)]. The list is shown while the control is enabled, editable and there is something to show. A request through this input or the exported signal waits until then. A request from the user (keys, a click, the indicator) is accepted only when the list can show, or with external search before the options arrive, so visibleChange can trigger loading them. Leaving the field, disabling it, or a typed search that matches nothing (without searchNoResultsLabel) cancels the request.

visibleItems8number

Number of options visible without scrolling. With virtualScroller it sets the viewport height, and only these rows plus a buffer around them are rendered. <kbd>Page Up</kbd> and <kbd>Page Down</kbd> move the highlight by this many options.

Events

Event name
inputChange

Emits the search text after the user edits the field (when the delay elapses, or earlier on a key that acts on the list or a click on an option), and an empty search when a selection or a clear resets it. An edit that ends on a different text than the field showed emits even when the search text itself is unchanged. An edit not yet emitted when focus leaves the field is dropped. Useful for implementing external search functionality or analytics.

  • $event string
optionChange

Emits an event when a user changes the selected option. Called with the selected option object or null when cleared. This is the primary callback for handling selection changes.

  • $event IAutocompleteOption | null
touch

Emits when the user finishes interacting with the control (blur), marking a bound form field as touched.

  • $event void
valueChange

Emits the value written by a selection, a clear, committed text or value.set(); not emitted for values written by the form.

  • $event string | number | null | undefined
visibleChange

Emits when the requested visibility changes other than through the visible input: typing, arrow keys, a click on the field, the indicator, a selection, <kbd>Escape</kbd>, a click outside, focus leaving the field, disabling the field, a typed search that matches nothing, or the exported visible signal. Not emitted on first render.

  • $event boolean