Skip to content

ng2-ui/auto-complete

Repository files navigation

@ngui/auto-complete

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.

Option Type Default Applies to Description
[(ngModel)] / [formControl] / formControlName T Directive The directive is a ControlValueAccessor, so the value flows through Angular forms. (ngModelChange) / the control's valueChanges fire on every accepted value
[(value)] T Component Two-way bindable selected value on the standalone component
select-value-of string Directive Commit this property's value on selection instead of the whole object

Data & filtering

Where suggestions come from and how the keyword is matched.

Option Type Default Applies to Description
source T[] | string | ((keyword) => Observable<T[]>) Both Required (input.required — omitting [source] is a compile-time error). Local array, URL string, or a function returning an Observable
path-to-data string Both Dot-path to the array in an HTTP response, e.g. data.results
min-chars number 0 Both Minimum characters before fetching/filtering
max-num-list number unlimited Both Maximum number of suggestions to show
match-formatted boolean false Both Match the keyword against formatted values instead of the raw data
ignore-accents boolean true Both Treat accented characters as their base characters when matching

Display & formatting

How rows and the selected value are rendered.

Option Type Default Applies to Description
display-with string | ((item) => string) item's value Directive Text shown in the input after selecting an object — a property name (display-with="name") or a function ([display-with]="fn")
list-formatter string | ((item) => string) Both Format each dropdown row. String pattern (key) name or a function
itemTemplate TemplateRef Both ng-template for each dropdown row (context: $implicit = item, index = row index). Takes precedence over list-formatter
headerTemplate TemplateRef Both ng-template for a non-selectable header row
loadingTemplate TemplateRef Both ng-template shown while remote data loads (falls back to loading-text)
loading-text string 'Loading' Both Text shown while fetching remote data
blank-option-text string Both Adds an empty first option with this label
no-match-found-text string Both Text shown when nothing matches. Set to "" to suppress the row entirely
placeholder string Component Placeholder for the component's internal input
auto-complete-placeholder string Directive Placeholder for the dropdown's (normally hidden) internal input

Behavior

Interaction and selection behavior.

Option Type Default Applies to Description
accept-user-input boolean true Both Allow values that are not in the list
auto-select-first-item boolean false Both Pre-highlight the first suggestion
select-on-blur boolean false Both Select the highlighted item on blur
tab-to-select boolean true Both Select the highlighted item on the Tab key
re-focus-after-select boolean true Both Return focus to the input after a selection
autocomplete boolean false Both When false, sets the native autocomplete="off" on the input
open-on-focus boolean true Directive Open the dropdown when the input gains focus
close-on-focusout boolean true Directive Close the dropdown on focusout
show-input-tag boolean true Component Render an <input> inside the component
show-dropdown-on-init boolean false Component Open the dropdown as soon as the component appears

Layout & positioning

Option Type Default Applies to Description
open-direction 'auto' | 'up' | 'down' 'auto' Both Preferred 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-index number 1 Directive z-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.

Output Payload Applies to Description
(valueSelected) NguiAutoCompleteSelection Both Fires when a value is committed. Use fromSource to tell a list pick from a typed value
(noMatchFound) void Both Fires 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.

Variable Default Controls
--ngui-ac-background #fff Dropdown background
--ngui-ac-color inherit Text color
--ngui-ac-border 1px solid rgba(0,0,0,.12) Dropdown border
--ngui-ac-border-radius 4px Corner radius
--ngui-ac-shadow 0 4px 12px rgba(0,0,0,.15) Elevation shadow
--ngui-ac-max-height 256px Height cap (none to remove)
--ngui-ac-z-index 10 Stacking order of the floating list (standalone <ngui-auto-complete>; the directive's overlay uses the z-index input instead)
--ngui-ac-item-padding 6px 12px Row padding (density)
--ngui-ac-item-border 1px solid rgba(0,0,0,.06) Row divider
--ngui-ac-hover-background rgba(0,0,0,.06) Row hover background
--ngui-ac-selected-background rgba(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

Script Description
pnpm start Serve the demo app on port 4200
pnpm test Run unit tests (Vitest + jsdom)
pnpm run lint Lint all TypeScript and HTML
pnpm run build-lib:watch Build library in watch mode (for demo development)
pnpm run build-lib:prod Production library build
pnpm run build-docs Build demo app for GitHub Pages deployment
pnpm run cypress:open Open Cypress e2e test runner
pnpm run cypress:run Run 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

Packages

 
 
 

Contributors