Skip to content

Commit 4629e23

Browse files
authored
feat(autocomplete): add event when option is activated (#18387)
Exposes an event that emits whenever an autocomplete option is activated using the keyboard. Fixes #17587.
1 parent 35421c8 commit 4629e23

File tree

3 files changed

+82
-3
lines changed

3 files changed

+82
-3
lines changed

src/material/autocomplete/autocomplete.spec.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2496,6 +2496,34 @@ describe('MatAutocomplete', () => {
24962496
expect(event.option.value).toBe('Puerto Rico');
24972497
}));
24982498

2499+
it('should emit an event when an option is activated', fakeAsync(() => {
2500+
const fixture = createComponent(AutocompleteWithActivatedEvent);
2501+
2502+
fixture.detectChanges();
2503+
fixture.componentInstance.trigger.openPanel();
2504+
zone.simulateZoneExit();
2505+
fixture.detectChanges();
2506+
2507+
const input = fixture.nativeElement.querySelector('input');
2508+
const spy = fixture.componentInstance.optionActivated;
2509+
const autocomplete = fixture.componentInstance.autocomplete;
2510+
const options = fixture.componentInstance.options.toArray();
2511+
2512+
expect(spy).not.toHaveBeenCalled();
2513+
2514+
dispatchKeyboardEvent(input, 'keydown', DOWN_ARROW);
2515+
fixture.detectChanges();
2516+
expect(spy.calls.mostRecent().args[0]).toEqual({source: autocomplete, option: options[0]});
2517+
2518+
dispatchKeyboardEvent(input, 'keydown', DOWN_ARROW);
2519+
fixture.detectChanges();
2520+
expect(spy.calls.mostRecent().args[0]).toEqual({source: autocomplete, option: options[1]});
2521+
2522+
dispatchKeyboardEvent(input, 'keydown', DOWN_ARROW);
2523+
fixture.detectChanges();
2524+
expect(spy.calls.mostRecent().args[0]).toEqual({source: autocomplete, option: options[2]});
2525+
}));
2526+
24992527
it('should be able to set a custom panel connection element', () => {
25002528
const fixture = createComponent(AutocompleteWithDifferentOrigin);
25012529

@@ -2978,3 +3006,24 @@ class AutocompleteWithNativeAutocompleteAttribute {
29783006
})
29793007
class InputWithoutAutocompleteAndDisabled {
29803008
}
3009+
3010+
3011+
@Component({
3012+
template: `
3013+
<mat-form-field>
3014+
<input matInput [matAutocomplete]="auto">
3015+
</mat-form-field>
3016+
3017+
<mat-autocomplete #auto="matAutocomplete" (optionActivated)="optionActivated($event)">
3018+
<mat-option *ngFor="let state of states" [value]="state">{{ state }}</mat-option>
3019+
</mat-autocomplete>
3020+
`
3021+
})
3022+
class AutocompleteWithActivatedEvent {
3023+
states = ['California', 'West Virginia', 'Florida'];
3024+
optionActivated = jasmine.createSpy('optionActivated callback');
3025+
3026+
@ViewChild(MatAutocompleteTrigger) trigger: MatAutocompleteTrigger;
3027+
@ViewChild(MatAutocomplete) autocomplete: MatAutocomplete;
3028+
@ViewChildren(MatOption) options: QueryList<MatOption>;
3029+
}

src/material/autocomplete/autocomplete.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
TemplateRef,
2525
ViewChild,
2626
ViewEncapsulation,
27+
OnDestroy,
2728
} from '@angular/core';
2829
import {
2930
CanDisableRipple,
@@ -33,6 +34,7 @@ import {
3334
MatOption,
3435
mixinDisableRipple,
3536
} from '@angular/material/core';
37+
import {Subscription} from 'rxjs';
3638

3739

3840
/**
@@ -50,6 +52,14 @@ export class MatAutocompleteSelectedEvent {
5052
public option: MatOption) { }
5153
}
5254

55+
/** Event object that is emitted when an autocomplete option is activated. */
56+
export interface MatAutocompleteActivatedEvent {
57+
/** Reference to the autocomplete panel that emitted the event. */
58+
source: MatAutocomplete;
59+
60+
/** Option that was selected. */
61+
option: MatOption|null;
62+
}
5363

5464
// Boilerplate for applying mixins to MatAutocomplete.
5565
/** @docs-private */
@@ -91,7 +101,8 @@ export function MAT_AUTOCOMPLETE_DEFAULT_OPTIONS_FACTORY(): MatAutocompleteDefau
91101
]
92102
})
93103
export class MatAutocomplete extends _MatAutocompleteMixinBase implements AfterContentInit,
94-
CanDisableRipple {
104+
CanDisableRipple, OnDestroy {
105+
private _activeOptionChanges = Subscription.EMPTY;
95106

96107
/** Manages active item in option list based on key events. */
97108
_keyManager: ActiveDescendantKeyManager<MatOption>;
@@ -149,6 +160,10 @@ export class MatAutocomplete extends _MatAutocompleteMixinBase implements AfterC
149160
/** Event that is emitted when the autocomplete panel is closed. */
150161
@Output() readonly closed: EventEmitter<void> = new EventEmitter<void>();
151162

163+
/** Emits whenever an option is activated using the keyboard. */
164+
@Output() readonly optionActivated: EventEmitter<MatAutocompleteActivatedEvent> =
165+
new EventEmitter<MatAutocompleteActivatedEvent>();
166+
152167
/**
153168
* Takes classes set on the host mat-autocomplete element and applies them to the panel
154169
* inside the overlay container to allow for easy styling.
@@ -183,10 +198,18 @@ export class MatAutocomplete extends _MatAutocompleteMixinBase implements AfterC
183198

184199
ngAfterContentInit() {
185200
this._keyManager = new ActiveDescendantKeyManager<MatOption>(this.options).withWrap();
201+
this._activeOptionChanges = this._keyManager.change.subscribe(index => {
202+
this.optionActivated.emit({source: this, option: this.options.toArray()[index] || null});
203+
});
204+
186205
// Set the initial visibility state.
187206
this._setVisibility();
188207
}
189208

209+
ngOnDestroy() {
210+
this._activeOptionChanges.unsubscribe();
211+
}
212+
190213
/**
191214
* Sets the panel scrollTop. This allows us to manually scroll to display options
192215
* above or below the fold, as they are not actually being focused when active.

tools/public_api_guard/material/autocomplete.d.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export declare const MAT_AUTOCOMPLETE_SCROLL_STRATEGY_FACTORY_PROVIDER: {
2020

2121
export declare const MAT_AUTOCOMPLETE_VALUE_ACCESSOR: any;
2222

23-
export declare class MatAutocomplete extends _MatAutocompleteMixinBase implements AfterContentInit, CanDisableRipple {
23+
export declare class MatAutocomplete extends _MatAutocompleteMixinBase implements AfterContentInit, CanDisableRipple, OnDestroy {
2424
_classList: {
2525
[key: string]: boolean;
2626
};
@@ -34,6 +34,7 @@ export declare class MatAutocomplete extends _MatAutocompleteMixinBase implement
3434
id: string;
3535
get isOpen(): boolean;
3636
readonly opened: EventEmitter<void>;
37+
readonly optionActivated: EventEmitter<MatAutocompleteActivatedEvent>;
3738
optionGroups: QueryList<MatOptgroup>;
3839
readonly optionSelected: EventEmitter<MatAutocompleteSelectedEvent>;
3940
options: QueryList<MatOption>;
@@ -47,12 +48,18 @@ export declare class MatAutocomplete extends _MatAutocompleteMixinBase implement
4748
_setScrollTop(scrollTop: number): void;
4849
_setVisibility(): void;
4950
ngAfterContentInit(): void;
51+
ngOnDestroy(): void;
5052
static ngAcceptInputType_autoActiveFirstOption: BooleanInput;
5153
static ngAcceptInputType_disableRipple: BooleanInput;
52-
static ɵcmp: i0.ɵɵComponentDefWithMeta<MatAutocomplete, "mat-autocomplete", ["matAutocomplete"], { "disableRipple": "disableRipple"; "displayWith": "displayWith"; "autoActiveFirstOption": "autoActiveFirstOption"; "panelWidth": "panelWidth"; "classList": "class"; }, { "optionSelected": "optionSelected"; "opened": "opened"; "closed": "closed"; }, ["options", "optionGroups"]>;
54+
static ɵcmp: i0.ɵɵComponentDefWithMeta<MatAutocomplete, "mat-autocomplete", ["matAutocomplete"], { "disableRipple": "disableRipple"; "displayWith": "displayWith"; "autoActiveFirstOption": "autoActiveFirstOption"; "panelWidth": "panelWidth"; "classList": "class"; }, { "optionSelected": "optionSelected"; "opened": "opened"; "closed": "closed"; "optionActivated": "optionActivated"; }, ["options", "optionGroups"]>;
5355
static ɵfac: i0.ɵɵFactoryDef<MatAutocomplete>;
5456
}
5557

58+
export interface MatAutocompleteActivatedEvent {
59+
option: MatOption | null;
60+
source: MatAutocomplete;
61+
}
62+
5663
export interface MatAutocompleteDefaultOptions {
5764
autoActiveFirstOption?: boolean;
5865
}

0 commit comments

Comments
 (0)