@ngui/auto-complete

June 24, 2026 · View on GitHub

CI npm version npm downloads Angular types license

A versatile Angular autocomplete library that works as both a directive (attached to any input) and a standalone component.

Live Demo


Installation

npm install @ngui/auto-complete @angular/cdk

The directive positions its dropdown with the CDK Overlay, so @angular/cdk is a peer dependency and your app must include the CDK overlay styles once:

/* styles.scss — skip this if you already import an Angular Material theme (it bundles them) */
@import '@angular/cdk/overlay-prebuilt.css';

Styling the dropdown: because the directive renders its dropdown in an overlay at the document root, any custom dropdown styles (e.g. classes used by a list-formatter or template) must be global, not scoped to an ancestor of the input. See Theming to restyle it with CSS variables.

Setup

Standalone (Angular 21+)

The component and directive are standalone. Import them directly into your standalone component (or NgModule) imports:

import { NguiAutoCompleteComponent, NguiAutoCompleteDirective } from '@ngui/auto-complete';

@Component({
  // ...
  imports: [FormsModule, NguiAutoCompleteComponent, NguiAutoCompleteDirective],
})
export class MyComponent {}

NgModule (Angular 20 and below)

NguiAutoCompleteModule was removed in v21 — import the standalone component/directive shown above instead. If your app still relies on the NgModule, install the matching major and import the module as before:

  • Angular 20npm install @ngui/auto-complete@20 (standalone, but NguiAutoCompleteModule is still re-exported for back-compat).
  • Angular 19 and oldernpm install @ngui/auto-complete@19.
import { NguiAutoCompleteModule } from '@ngui/auto-complete';

@NgModule({
  imports: [BrowserModule, FormsModule, NguiAutoCompleteModule],
})
export class AppModule {}

Usage

As a Directive

Attach to any element containing an input. The directive is a ControlValueAccessor, so it binds with [(ngModel)] like any form control:

<!-- Directly on an input -->
<input ngui-auto-complete [(ngModel)]="myValue" [source]="myArray" />

<!-- On a wrapping div: put the value binding on the host element -->
<div ngui-auto-complete [(ngModel)]="myValue" [source]="myArray">
  <input />
</div>

Reactive forms

Because the directive is a ControlValueAccessor, [formControl] and formControlName work directly:

<input ngui-auto-complete [formControl]="cityControl" [source]="cities" />

<form [formGroup]="form">
  <input ngui-auto-complete formControlName="city" [source]="cities" />
</form>

As a Component

Use <ngui-auto-complete> directly, control its visibility with @if:

<input [(ngModel)]="myValue" (focus)="show = true" (blur)="show = false" />
@if (show) {
  <ngui-auto-complete
    [source]="myArray"
    [show-input-tag]="false"
    [show-dropdown-on-init]="true"
    [(value)]="myValue">
  </ngui-auto-complete>
}

Remote / Observable Source

<input ngui-auto-complete
  [(ngModel)]="address"
  [source]="searchFn"
  path-to-data="results"
  list-formatter="formatted_address"
  min-chars="2" />
searchFn = (keyword: string): Observable<any> => {
  return this.http.get(`https://api.example.com/search?q=${keyword}`);
};

Custom dropdown templates

Pass Angular ng-templates for custom rendering: itemTemplate (each row — receives the item as $implicit and the row index as index), headerTemplate (a header row), and loadingTemplate (shown while remote data loads). They work on both the component and the directive:

<input ngui-auto-complete [(ngModel)]="myValue" [source]="myArray"
  [itemTemplate]="row" [headerTemplate]="head" />

<ng-template #head>Suggestions</ng-template>
<ng-template #row let-item let-i="index">
  <strong>{{ i + 1 }}.</strong> {{ item.name }} — <em>{{ item.country }}</em>
</ng-template>

API Reference

One reference for both surfaces. The Applies to column says whether each option works on the directive ([ngui-auto-complete]), the component (<ngui-auto-complete>), or both. Every option keeps its kebab-case attribute name; numeric/boolean inputs accept both the attribute form (min-chars="2") and the bound form ([min-chars]="2").

Value & forms

How the selected value is read and written.

OptionTypeDefaultApplies toDescription
[(ngModel)] / [formControl] / formControlNameTDirectiveThe directive is a ControlValueAccessor, so the value flows through Angular forms. (ngModelChange) / the control's valueChanges fire on every accepted value
[(value)]TComponentTwo-way bindable selected value on the standalone component
select-value-ofstringDirectiveCommit this property's value on selection instead of the whole object

Data & filtering

Where suggestions come from and how the keyword is matched.

OptionTypeDefaultApplies toDescription
sourceT[] | string | ((keyword) => Observable<T[]>)BothRequired (input.required — omitting [source] is a compile-time error). Local array, URL string, or a function returning an Observable
path-to-datastringBothDot-path to the array in an HTTP response, e.g. data.results
min-charsnumber0BothMinimum characters before fetching/filtering
max-num-listnumberunlimitedBothMaximum number of suggestions to show
match-formattedbooleanfalseBothMatch the keyword against formatted values instead of the raw data
ignore-accentsbooleantrueBothTreat accented characters as their base characters when matching

Display & formatting

How rows and the selected value are rendered.

OptionTypeDefaultApplies toDescription
display-withstring | ((item) => string)item's valueDirectiveText shown in the input after selecting an object — a property name (display-with="name") or a function ([display-with]="fn")
list-formatterstring | ((item) => string)BothFormat each dropdown row. String pattern (key) name or a function
itemTemplateTemplateRefBothng-template for each dropdown row (context: $implicit = item, index = row index). Takes precedence over list-formatter
headerTemplateTemplateRefBothng-template for a non-selectable header row
loadingTemplateTemplateRefBothng-template shown while remote data loads (falls back to loading-text)
loading-textstring'Loading'BothText shown while fetching remote data
blank-option-textstringBothAdds an empty first option with this label
no-match-found-textstringBothText shown when nothing matches. Set to "" to suppress the row entirely
placeholderstringComponentPlaceholder for the component's internal input
auto-complete-placeholderstringDirectivePlaceholder for the dropdown's (normally hidden) internal input

Behavior

Interaction and selection behavior.

OptionTypeDefaultApplies toDescription
accept-user-inputbooleantrueBothAllow values that are not in the list
auto-select-first-itembooleanfalseBothPre-highlight the first suggestion
select-on-blurbooleanfalseBothSelect the highlighted item on blur
tab-to-selectbooleantrueBothSelect the highlighted item on the Tab key
re-focus-after-selectbooleantrueBothReturn focus to the input after a selection
autocompletebooleanfalseBothWhen false, sets the native autocomplete="off" on the input
open-on-focusbooleantrueDirectiveOpen the dropdown when the input gains focus
close-on-focusoutbooleantrueDirectiveClose the dropdown on focusout
show-input-tagbooleantrueComponentRender an <input> inside the component
show-dropdown-on-initbooleanfalseComponentOpen the dropdown as soon as the component appears

Layout & positioning

OptionTypeDefaultApplies toDescription
open-direction'auto' | 'up' | 'down''auto'BothPreferred side. For the directive (CDK overlay) up/down set the preference and the overlay still flips when there isn't room; for the component, up renders above via CSS and auto/down keep it below
z-indexnumber1Directivez-index of the dropdown overlay. Rarely needed — the CDK overlay already renders above page content; only useful to order overlapping overlays

RTL: there is no RTL input — the directive's overlay follows the input's computed direction, and the component's drop-up anchors via logical CSS (inset-inline-start). Just set dir="rtl" on the element or an ancestor (or the document direction).

Events

Both surfaces emit the same two outputs.

OutputPayloadApplies toDescription
(valueSelected)NguiAutoCompleteSelectionBothFires when a value is committed. Use fromSource to tell a list pick from a typed value
(noMatchFound)voidBothFires when the filtered list is empty and the min-chars threshold is met — use it to show an "Add new…" affordance

The (valueSelected) payload:

interface NguiAutoCompleteSelection<T = any> {
  value: T;          // the committed value (same as [(ngModel)] / [(value)])
  item: T;           // the full picked object (or the typed text)
  index: number;     // row in the shown list; -1 when typed (fromSource = false)
  fromSource: boolean; // true = picked from [source]; false = typed by the user
}

Type inference

NguiAutoCompleteComponent<T = any> is generic. Bind a typed [source] (a typed array or a function returning Observable<T[]>) and Angular infers the item type — [(value)], (valueSelected) (NguiAutoCompleteSelection<T>) and the itemTemplate context are then all typed with no extra annotation:

<ngui-auto-complete [source]="cities" [(value)]="city" (valueSelected)="onPick($event)"></ngui-auto-complete>
cities: City[] = [/* … */];
city?: City;
onPick(e: NguiAutoCompleteSelection<City>) { /* e.item is City */ }

It defaults to any, so existing templates are unaffected. The directive ([ngui-auto-complete]) stays loosely typed — Angular can't infer a generic for an attribute directive in templates, so its (valueSelected) payload is NguiAutoCompleteSelection<any>.


Theming

Restyle the dropdown by overriding these CSS variables. Each has a sensible default, so set only what you need. Set them on :root — the directive's dropdown renders in an overlay at the document root, so variables on an ancestor of the input won't reach it.

VariableDefaultControls
--ngui-ac-background#fffDropdown background
--ngui-ac-colorinheritText color
--ngui-ac-border1px solid rgba(0,0,0,.12)Dropdown border
--ngui-ac-border-radius4pxCorner radius
--ngui-ac-shadow0 4px 12px rgba(0,0,0,.15)Elevation shadow
--ngui-ac-max-height256pxHeight cap (none to remove)
--ngui-ac-z-index10Stacking order of the floating list (standalone <ngui-auto-complete>; the directive's overlay uses the z-index input instead)
--ngui-ac-item-padding6px 12pxRow padding (density)
--ngui-ac-item-border1px solid rgba(0,0,0,.06)Row divider
--ngui-ac-hover-backgroundrgba(0,0,0,.06)Row hover background
--ngui-ac-selected-backgroundrgba(0,0,0,.1)Highlighted row background
/* e.g. a dark dropdown */
:root {
  --ngui-ac-background: #2b2b2b;
  --ngui-ac-color: #eee;
  --ngui-ac-item-border: 1px solid rgba(255, 255, 255, 0.08);
  --ngui-ac-hover-background: rgba(255, 255, 255, 0.08);
  --ngui-ac-selected-background: rgba(255, 255, 255, 0.16);
}

Angular Version Compatibility

This library follows Angular's versioning: @ngui/auto-complete@N.x supports Angular N. Install the version matching your Angular major (e.g. Angular 22 → npm install @ngui/auto-complete@22).


Development

Quick start

git clone https://github.com/ng2-ui/auto-complete.git
cd auto-complete
pnpm install

# Build library in watch mode, then in a second terminal start the demo app
pnpm run build-lib:watch
pnpm start

This repo uses pnpm. Enable it with corepack enable (ships with Node).

Available scripts

ScriptDescription
pnpm startServe the demo app on port 4200
pnpm testRun unit tests (Vitest + jsdom)
pnpm run lintLint all TypeScript and HTML
pnpm run build-lib:watchBuild library in watch mode (for demo development)
pnpm run build-lib:prodProduction library build
pnpm run build-docsBuild demo app for GitHub Pages deployment
pnpm run cypress:openOpen Cypress e2e test runner
pnpm run cypress:runRun Cypress e2e tests headlessly

Publish a new version

# 1. Update version in projects/auto-complete/package.json
# 2. Build the library. This also copies README/CHANGELOG/MIGRATION/LICENSE
#    into dist/ for you (via the copy-lib script).
pnpm run build-lib:prod

# 3. Move into the built output — do NOT run npm publish from the project root
#    (the root package.json is private and will fail)
cd dist
npm publish --access public

Contributing — help wanted!

This library is maintained by a small team with limited time — every contribution genuinely helps keep it alive and improving. If you use it and find it useful, here are easy ways to give back:

  • Found a bug? Open an issue with a clear description and a minimal reproduction
  • Have an idea? Check the open issues first, then open a new one if needed
  • Want to fix something? Pull requests are always welcome — small focused PRs are easiest to review
  • Using it at work? A GitHub star goes a long way for visibility

Issues and pull requests: github.com/ng2-ui/auto-complete.

Changelog

See CHANGELOG.md for the full history of changes.

License

MIT