A versatile Angular autocomplete library that works as both a directive (attached to any input) and a standalone component.
npm install @ngui/auto-complete @angular/cdkThe 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-formatteror template) must be global, not scoped to an ancestor of the input. See Theming to restyle it with CSS variables.
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 {}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 20 →
npm install @ngui/auto-complete@20(standalone, butNguiAutoCompleteModuleis still re-exported for back-compat). - Angular 19 and older →
npm install @ngui/auto-complete@19.
import { NguiAutoCompleteModule } from '@ngui/auto-complete';
@NgModule({
imports: [BrowserModule, FormsModule, NguiAutoCompleteModule],
})
export class AppModule {}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>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>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>
}<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}`);
};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>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").
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 |
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 |
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 |
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 |
| 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 setdir="rtl"on the element or an ancestor (or the document direction).
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
}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>.
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);
}This library follows Angular's versioning: @ngui/[email protected] supports Angular N.
Install the version matching your Angular major (e.g. Angular 22 → npm install @ngui/auto-complete@22).
git clone https://github.com/ng2-ui/auto-complete.git
cd auto-complete
npm install
# Build library in watch mode, then in a second terminal start the demo app
npm run build-lib:watch
npm start| Script | Description |
|---|---|
npm start |
Serve the demo app on port 4200 |
npm test |
Run unit tests (Karma/Jasmine) |
npm run lint |
Lint all TypeScript and HTML |
npm run build-lib:watch |
Build library in watch mode (for demo development) |
npm run build-lib:prod |
Production library build |
npm run build-docs |
Build demo app for GitHub Pages deployment |
npm run cypress:open |
Open Cypress e2e test runner |
npm run cypress:run |
Run Cypress e2e tests headlessly |
# 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).
npm 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 publicThis 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.
See CHANGELOG.md for the full history of changes.