Revamp, Modern, Angular 21
[21.0.0] — 2026-06-04
⚠️ BREAKING CHANGES
- Requires Angular 21.
peerDependenciesnow require@angular/commonand@angular/core
^21.0.0. Angular 20 and older projects must stay on@ngui/auto-complete@20(or the matching major). - Removed
NguiAutoCompleteModule. The library's last NgModule has been deleted. Import the
standaloneNguiAutoCompleteComponent/NguiAutoCompleteDirectivedirectly instead:
imports: [NguiAutoCompleteComponent, NguiAutoCompleteDirective]. Apps that still need the NgModule
can stay on@ngui/auto-complete@20, which retained it. - The directive is now a
ControlValueAccessor.NguiAutoCompleteDirectiveimplements
ControlValueAccessorand providesNG_VALUE_ACCESSOR, so[(ngModel)],[formControl]and
formControlNameintegrate through Angular forms (with propervalueChanges,touched/dirtystate).
Consequences (seeMIGRATION.mdfor before/after):- Removed the directive's bespoke
ngModelinput and its(ngModelChange)and
(valueChanged)outputs. Use[(ngModel)](Angular still emits(ngModelChange)) or a reactive
form.(valueChanged)has no direct replacement — use(ngModelChange)or the control'svalueChanges. - Removed the directive's custom
[formControl]/formControlNameinputs. The standard Angular
directives now drive it directly — same template syntax, now with full form integration. - Wrapper-
<div>usage: bind the value on the host
(<div ngui-auto-complete [(ngModel)]="x"><input></div>) instead of on a separate inner<input>.
Direct<input ngui-auto-complete [(ngModel)]="x">is unchanged.
- Removed the directive's bespoke
- Unified the selection outputs into one
(valueSelected)event.valueSelected+customSelected
are merged into a single(valueSelected)(on both the component and directive) carrying
NguiAutoCompleteSelection { value, item, index, fromSource }—fromSourceistruefor a list pick,
falsefor a typed value.(customSelected)is removed (checkfromSource: false). The payload changed
from the bare value to this object, so(valueSelected)="x = $event"→(valueSelected)="x = $event.value"
(or use[(ngModel)]/[(value)]for the value). The never-emittedtextEnteredoutput is also
removed.(noMatchFound)is unchanged. - Consolidated value display into one
display-withinput.display-property-nameand the
string-templatevalue-formatterare replaced by a singledisplay-withthat accepts either a
property name or a function:display-with="name"or[display-with]="(item) => …". (list-formatter
still formats the dropdown rows.) Migratedisplay-property-name="name"→display-with="name", and
value-formatter="(key) name"→[display-with]="(item) => '(' + item.key + ') ' + item.name". - Removed the
is-rtlinput. Direction is now auto-detected from the input's computed direction, so
an ancestordir="rtl"(or the document direction) positions the dropdown correctly on its own — no
input and no extra dependency. Replace[is-rtl]="true"withdir="rtl"on the element or an ancestor. - Replaced the
innerHTMLstring templates withTemplateRefs. The stringloading-templateand
header-item-templateinputs are removed. Use theheaderTemplateTemplateRef(already available) and
the newloadingTemplateTemplateRef(loading-textremains for the simple case). This also removes
aninnerHTMLsink. sourceis now a required input.sourceusesinput.requiredon both the component and directive
(it was always required in practice — the control does nothing without it). Omitting[source]is now a
compile-time error under strict templates (and a runtime error otherwise) instead of silently doing
nothing. Just ensure every[ngui-auto-complete]/<ngui-auto-complete>has a[source].- New
@angular/cdkpeer dependency (directive dropdown now uses the CDK Overlay). The
[ngui-auto-complete]directive renders its dropdown through@angular/cdk/overlayinstead of inserting
an absolutely-positioned element next to the input. You must install@angular/cdk@^21and include the
CDK overlay styles once in your app — either@import '@angular/cdk/overlay-prebuilt.css';or any
@angular/materialtheme (which already bundles them). SeeMIGRATION.md.
Added
[(value)]two-way binding onNguiAutoCompleteComponent. The standalone component now exposes a
valuemodel — e.g.<ngui-auto-complete [(value)]="myValue">.(valueSelected)continues to fire.NguiAutoCompleteSelection<T>interface exported for the(valueSelected)payload
({ value, item, index, fromSource }).- Generic
NguiAutoCompleteComponent<T = any>. Binding a typed[source](array or function)
infers the item type, so[(value)],(valueSelected)(NguiAutoCompleteSelection<T>) and the
itemTemplatecontext are all typed without any annotation. Defaults toany, so existing templates
are unaffected. The directive stays loosely typed (Angular can't infer generics for an attribute
directive in templates).
Changed
- Upgraded to Angular 21 (
@angular/*21.2.x,@angular/material+@angular/cdk21.2.x).
Install@ngui/auto-complete@21for Angular 21 projects. - Signal-based inputs/outputs. All
@Input()s are now signalinput()s and all@Output()s are
output()s on both the component and directive (the component's@ViewChilds becameviewChild()
queries). Template bindings are unchanged — every input keeps its existing name/alias (e.g.
min-chars,list-formatter). The only consumer-visible effect is for code that reaches into a
component/directive instance programmatically: those input properties are now read-only signals
(call them, e.g.cmp.minChars()), not plain fields. - Typed attribute coercion. Numeric inputs (
min-chars,max-num-list,z-index) now use
numberAttributeand boolean inputs usebooleanAttribute, so string-attribute forms (min-chars="2")
and bound forms ([min-chars]="2") are both correctly typed. OnPushdropdown component.NguiAutoCompleteComponentnow usesChangeDetectionStrategy.OnPush
and its internal state (dropdownVisible,isLoading,filteredList,minCharsEntered,itemIndex)
is signal-based, so remote/async results and directive-driven updates refresh reliably with less change
detection work. No public API change.- Bumped the library's own
tslibdependency floor to^2.8.1. - The "drop-up" dropdown now anchors with the logical
inset-inline-startinstead ofleft, so it sits
on the correct edge under RTL (matching the directive's positioning). No API change. - CDK Overlay positioning (directive). The dropdown now renders in a CDK overlay at the document root,
so it escapes ancestor clipping / stacking contexts (e.g. inside amat-form-fieldor a card with
overflow: hidden) and flips above/below automatically on overflow. As a result,open-directionis now
a preference (the overlay still flips when there isn't room), andz-indexis rarely needed (the
overlay already layers above page content; it now only orders overlapping overlays). RTL follows the
input's computed direction. Note: because the dropdown is at the document root, custom dropdown
styles (e.g. classes from alist-formatter) must be global, not scoped to an ancestor of the input. - Refreshed, themeable dropdown styling. The dropdown now has a subtle elevation, rounded corners, a
softer border and roomier rows. Appearance is exposed through CSS variables —--ngui-ac-background,
--ngui-ac-color,--ngui-ac-border,--ngui-ac-border-radius,--ngui-ac-shadow,
--ngui-ac-max-height(noneto remove the cap),--ngui-ac-item-padding,--ngui-ac-item-border,
--ngui-ac-hover-backgroundand--ngui-ac-selected-background— each with a sensible default (set them
on:root). See the
README "Theming" section. Applies to the directive and component.
Fixed
- Internal keyword input a11y. The component's internal input (rendered when
show-input-tag) now
carries a uniqueid, clearing the browser's "a form field element should have an id or name" warning.
It is boundstandalone, so it never registers into a consumer's parent<form>(no stray
keywordcontrol) — which also fixes a latent error when the component was used inside a<form>.
Internal (development tooling only — no impact on consumers)
ng updateto Angular 21:@angular/cli+@angular-devkit/build-angular21.2.x,
ng-packagr21.2.5, TypeScript 5.9.3. The demobootstrapApplicationnow provides
provideZoneChangeDetection()(Angular 21 defaults to zoneless; this preserves the existing
zone-based change detection).- ESLint 10. Bumped
eslintand@eslint/jsto 10 andangular-eslintto 21.4.0 (its ESLint peer
now allows^10), unblocking the upgrade deferred in 19.0.0 / 20.0.0. - The directive now forwards inputs to the dynamically created dropdown via
ComponentRef.setInput()
(required now that the component's inputs are read-only signals).
What's Changed
- feat(angular-21)!: upgrade to Angular 21, remove NguiAutoCompleteModule, ESLint 10 by @almothafar in #490
- refactor(lib): signal inputs/outputs + attribute transforms (Phase A, non-breaking) by @almothafar in #491
- chore: add PR template and Copilot/Claude contributor guides by @almothafar in #492
- refactor(lib): OnPush + signal state for the dropdown component (Phase B) by @almothafar in #493
- feat(lib)!: ControlValueAccessor + [(value)] model (Phase C) by @almothafar in #494
- feat(lib)!: Phase D — API ergonomics (unified event, display-with, RTL auto, TemplateRefs, generic, docs) by @almothafar in #495
- refactor(demo): inline styles → SCSS + logical CSS; fix(lib): logical drop-up anchor by @almothafar in #496
- fix(lib): unique id on internal keyword input (a11y, form-safe) by @almothafar in #497
- feat(lib)!: make source a required input (proposal) by @almothafar in #498
- feat(lib)!: render the directive dropdown via CDK Overlay by @almothafar in #499
Full Changelog: 20.0.0...21.0.0