66 * found in the LICENSE file at https://angular.io/license
77 */
88
9- import { QueryList } from '@angular/core' ;
9+ import { EffectRef , Injector , QueryList , Signal , effect , isSignal } from '@angular/core' ;
1010import { Subject , Subscription } from 'rxjs' ;
1111import {
1212 UP_ARROW ,
@@ -54,6 +54,7 @@ export class ListKeyManager<T extends ListKeyManagerOption> {
5454 private _allowedModifierKeys : ListKeyManagerModifierKey [ ] = [ ] ;
5555 private _homeAndEnd = false ;
5656 private _pageUpAndDown = { enabled : false , delta : 10 } ;
57+ private _effectRef : EffectRef | undefined ;
5758
5859 /**
5960 * Predicate function that can be used to check whether an item should be skipped
@@ -64,21 +65,25 @@ export class ListKeyManager<T extends ListKeyManagerOption> {
6465 // Buffer for the letters that the user has pressed when the typeahead option is turned on.
6566 private _pressedLetters : string [ ] = [ ] ;
6667
67- constructor ( private _items : QueryList < T > | T [ ] ) {
68+ constructor ( items : QueryList < T > | T [ ] | readonly T [ ] ) ;
69+ constructor ( items : Signal < T [ ] > | Signal < readonly T [ ] > , injector : Injector ) ;
70+ constructor (
71+ private _items : QueryList < T > | T [ ] | readonly T [ ] | Signal < T [ ] > | Signal < readonly T [ ] > ,
72+ injector ?: Injector ,
73+ ) {
6874 // We allow for the items to be an array because, in some cases, the consumer may
6975 // not have access to a QueryList of the items they want to manage (e.g. when the
7076 // items aren't being collected via `ViewChildren` or `ContentChildren`).
7177 if ( _items instanceof QueryList ) {
72- this . _itemChangesSubscription = _items . changes . subscribe ( ( newItems : QueryList < T > ) => {
73- if ( this . _activeItem ) {
74- const itemArray = newItems . toArray ( ) ;
75- const newIndex = itemArray . indexOf ( this . _activeItem ) ;
78+ this . _itemChangesSubscription = _items . changes . subscribe ( ( newItems : QueryList < T > ) =>
79+ this . _itemsChanged ( newItems . toArray ( ) ) ,
80+ ) ;
81+ } else if ( isSignal ( _items ) ) {
82+ if ( ! injector && ( typeof ngDevMode === 'undefined' || ngDevMode ) ) {
83+ throw new Error ( 'ListKeyManager constructed with a signal must receive an injector' ) ;
84+ }
7685
77- if ( newIndex > - 1 && newIndex !== this . _activeItemIndex ) {
78- this . _activeItemIndex = newIndex ;
79- }
80- }
81- } ) ;
86+ this . _effectRef = effect ( ( ) => this . _itemsChanged ( _items ( ) ) , { injector} ) ;
8287 }
8388 }
8489
@@ -144,12 +149,11 @@ export class ListKeyManager<T extends ListKeyManagerOption> {
144149 * @param debounceInterval Time to wait after the last keystroke before setting the active item.
145150 */
146151 withTypeAhead ( debounceInterval : number = 200 ) : this {
147- if (
148- ( typeof ngDevMode === 'undefined' || ngDevMode ) &&
149- this . _items . length &&
150- this . _items . some ( item => typeof item . getLabel !== 'function' )
151- ) {
152- throw Error ( 'ListKeyManager items in typeahead mode must implement the `getLabel` method.' ) ;
152+ if ( typeof ngDevMode === 'undefined' || ngDevMode ) {
153+ const items = this . _getItemsArray ( ) ;
154+ if ( items . length > 0 && items . some ( item => typeof item . getLabel !== 'function' ) ) {
155+ throw Error ( 'ListKeyManager items in typeahead mode must implement the `getLabel` method.' ) ;
156+ }
153157 }
154158
155159 this . _typeaheadSubscription . unsubscribe ( ) ;
@@ -403,6 +407,7 @@ export class ListKeyManager<T extends ListKeyManagerOption> {
403407 destroy ( ) {
404408 this . _typeaheadSubscription . unsubscribe ( ) ;
405409 this . _itemChangesSubscription ?. unsubscribe ( ) ;
410+ this . _effectRef ?. destroy ( ) ;
406411 this . _letterKeyStream . complete ( ) ;
407412 this . tabOut . complete ( ) ;
408413 this . change . complete ( ) ;
@@ -470,7 +475,22 @@ export class ListKeyManager<T extends ListKeyManagerOption> {
470475 }
471476
472477 /** Returns the items as an array. */
473- private _getItemsArray ( ) : T [ ] {
478+ private _getItemsArray ( ) : T [ ] | readonly T [ ] {
479+ if ( isSignal ( this . _items ) ) {
480+ return this . _items ( ) ;
481+ }
482+
474483 return this . _items instanceof QueryList ? this . _items . toArray ( ) : this . _items ;
475484 }
485+
486+ /** Callback for when the items have changed. */
487+ private _itemsChanged ( newItems : T [ ] | readonly T [ ] ) {
488+ if ( this . _activeItem ) {
489+ const newIndex = newItems . indexOf ( this . _activeItem ) ;
490+
491+ if ( newIndex > - 1 && newIndex !== this . _activeItemIndex ) {
492+ this . _activeItemIndex = newIndex ;
493+ }
494+ }
495+ }
476496}
0 commit comments