Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions goldens/material/autocomplete/index.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ export const MAT_AUTOCOMPLETE_DEFAULT_OPTIONS: InjectionToken<MatAutocompleteDef
// @public
export const MAT_AUTOCOMPLETE_SCROLL_STRATEGY: InjectionToken<() => ScrollStrategy>;

// @public
export const MAT_AUTOCOMPLETE_SELECTED_TRIGGER: InjectionToken<MatAutocompleteSelectedTrigger>;

// @public
export const MAT_AUTOCOMPLETE_VALUE_ACCESSOR: any;

Expand All @@ -55,6 +58,7 @@ export class MatAutocomplete implements AfterContentInit, OnDestroy {
_classList: string | string[];
readonly closed: EventEmitter<void>;
protected _color: ThemePalette;
customTrigger: MatAutocompleteSelectedTrigger | undefined;
// (undocumented)
protected _defaults: MatAutocompleteDefaultOptions;
disableRipple: boolean;
Expand Down Expand Up @@ -102,7 +106,7 @@ export class MatAutocomplete implements AfterContentInit, OnDestroy {
_syncParentProperties(): void;
template: TemplateRef<any>;
// (undocumented)
static ɵcmp: i0.ɵɵComponentDeclaration<MatAutocomplete, "mat-autocomplete", ["matAutocomplete"], { "ariaLabel": { "alias": "aria-label"; "required": false; }; "ariaLabelledby": { "alias": "aria-labelledby"; "required": false; }; "displayWith": { "alias": "displayWith"; "required": false; }; "autoActiveFirstOption": { "alias": "autoActiveFirstOption"; "required": false; }; "autoSelectActiveOption": { "alias": "autoSelectActiveOption"; "required": false; }; "requireSelection": { "alias": "requireSelection"; "required": false; }; "panelWidth": { "alias": "panelWidth"; "required": false; }; "disableRipple": { "alias": "disableRipple"; "required": false; }; "classList": { "alias": "class"; "required": false; }; "hideSingleSelectionIndicator": { "alias": "hideSingleSelectionIndicator"; "required": false; }; }, { "optionSelected": "optionSelected"; "opened": "opened"; "closed": "closed"; "optionActivated": "optionActivated"; }, ["options", "optionGroups"], ["*"], true, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<MatAutocomplete, "mat-autocomplete", ["matAutocomplete"], { "ariaLabel": { "alias": "aria-label"; "required": false; }; "ariaLabelledby": { "alias": "aria-labelledby"; "required": false; }; "displayWith": { "alias": "displayWith"; "required": false; }; "autoActiveFirstOption": { "alias": "autoActiveFirstOption"; "required": false; }; "autoSelectActiveOption": { "alias": "autoSelectActiveOption"; "required": false; }; "requireSelection": { "alias": "requireSelection"; "required": false; }; "panelWidth": { "alias": "panelWidth"; "required": false; }; "disableRipple": { "alias": "disableRipple"; "required": false; }; "classList": { "alias": "class"; "required": false; }; "hideSingleSelectionIndicator": { "alias": "hideSingleSelectionIndicator"; "required": false; }; }, { "optionSelected": "optionSelected"; "opened": "opened"; "closed": "closed"; "optionActivated": "optionActivated"; }, ["customTrigger", "options", "optionGroups"], ["*"], true, never>;
// (undocumented)
static ɵfac: i0.ɵɵFactoryDeclaration<MatAutocomplete, never>;
}
Expand Down Expand Up @@ -131,7 +135,7 @@ export class MatAutocompleteModule {
// (undocumented)
static ɵinj: i0.ɵɵInjectorDeclaration<MatAutocompleteModule>;
// (undocumented)
static ɵmod: i0.ɵɵNgModuleDeclaration<MatAutocompleteModule, never, [typeof i2.OverlayModule, typeof MatOptionModule, typeof MatAutocomplete, typeof MatAutocompleteTrigger, typeof MatAutocompleteOrigin], [typeof i1.CdkScrollableModule, typeof MatAutocomplete, typeof MatOptionModule, typeof i2$1.BidiModule, typeof MatAutocompleteTrigger, typeof MatAutocompleteOrigin]>;
static ɵmod: i0.ɵɵNgModuleDeclaration<MatAutocompleteModule, never, [typeof i2.OverlayModule, typeof MatOptionModule, typeof MatAutocomplete, typeof MatAutocompleteTrigger, typeof MatAutocompleteOrigin, typeof MatAutocompleteSelectedTrigger], [typeof i1.CdkScrollableModule, typeof MatAutocomplete, typeof MatOptionModule, typeof i2$1.BidiModule, typeof MatAutocompleteTrigger, typeof MatAutocompleteOrigin, typeof MatAutocompleteSelectedTrigger]>;
}

// @public
Expand All @@ -154,6 +158,16 @@ export class MatAutocompleteSelectedEvent {
source: MatAutocomplete;
}

// @public
export class MatAutocompleteSelectedTrigger {
// (undocumented)
templateRef: TemplateRef<any>;
// (undocumented)
static ɵdir: i0.ɵɵDirectiveDeclaration<MatAutocompleteSelectedTrigger, "ng-template[matAutocompleteSelectedTrigger]", never, {}, {}, never, never, true, never>;
// (undocumented)
static ɵfac: i0.ɵɵFactoryDeclaration<MatAutocompleteSelectedTrigger, never>;
}

// @public
export class MatAutocompleteTrigger implements ControlValueAccessor, AfterViewInit, OnChanges, OnDestroy {
constructor(...args: unknown[]);
Expand All @@ -164,6 +178,8 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, AfterViewIn
closePanel(): void;
connectedTo: MatAutocompleteOrigin;
// (undocumented)
_handleBlur(): void;
// (undocumented)
_handleClick(): void;
// (undocumented)
_handleFocus(): void;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
.example-form {
min-width: 150px;
max-width: 500px;
width: 100%;
}

.example-full-width {
width: 100%;
}

.example-option-img {
vertical-align: middle;
margin-right: 8px;
}

.example-trigger-flag {
vertical-align: middle;
margin-right: 4px;
}

.mat-autocomplete-selected-trigger {
top: 15px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<form class="example-form">
<mat-form-field class="example-full-width">
<mat-label>State</mat-label>
<input
matInput
aria-label="State"
[matAutocomplete]="auto"
[formControl]="stateCtrl" />
<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn">
<!--
matAutocompleteSelectedTrigger replaces the plain text with a custom HTML template
after an option is selected. The let-state binding exposes the raw selected value.
Click the field or start typing to clear the display and re-open the panel.
-->
<ng-template matAutocompleteSelectedTrigger let-state>
<img class="example-trigger-flag" [src]="state?.flag" [alt]="state?.name" height="20" />
{{ state?.name }}
</ng-template>
@for (state of filteredStates | async; track state.name) {
<mat-option [value]="state">
<img alt="" class="example-option-img" [src]="state.flag" height="25" />
<span>{{ state.name }}</span> |
<small>Population: {{ state.population }}</small>
</mat-option>
}
</mat-autocomplete>
</mat-form-field>

<br />

<mat-slide-toggle
[checked]="stateCtrl.disabled"
(change)="stateCtrl.disabled ? stateCtrl.enable() : stateCtrl.disable()">
Disable Input?
</mat-slide-toggle>
</form>
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/

import {AsyncPipe} from '@angular/common';
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {MatAutocompleteModule} from '@angular/material/autocomplete';
import {MatFormFieldModule} from '@angular/material/form-field';
import {MatInputModule} from '@angular/material/input';
import {MatSlideToggleModule} from '@angular/material/slide-toggle';
import {Observable} from 'rxjs';
import {map, startWith} from 'rxjs/operators';

export interface State {
flag: string;
name: string;
population: string;
}

/** @title Autocomplete with custom selected-value template */
@Component({
selector: 'autocomplete-custom-trigger-example',
templateUrl: 'autocomplete-custom-trigger-example.html',
styleUrl: 'autocomplete-custom-trigger-example.css',
imports: [
AsyncPipe,
MatAutocompleteModule,
MatFormFieldModule,
MatInputModule,
MatSlideToggleModule,
ReactiveFormsModule,
],
})
export class AutocompleteCustomTriggerExample {
stateCtrl = new FormControl<State | string | null>(null);
filteredStates = this.stateCtrl.valueChanges.pipe(
startWith(null),
map(value => {
const name = typeof value === 'string' ? value : (value?.name ?? '');
return name ? this._filterStates(name) : this.states.slice();
}),
);

states: State[] = [
{
name: 'Arkansas',
population: '2.978M',
// https://commons.wikimedia.org/wiki/File:Flag_of_Arkansas.svg
flag: 'https://upload.wikimedia.org/wikipedia/commons/9/9d/Flag_of_Arkansas.svg',
},
{
name: 'California',
population: '39.14M',
// https://commons.wikimedia.org/wiki/File:Flag_of_California.svg
flag: 'https://upload.wikimedia.org/wikipedia/commons/0/01/Flag_of_California.svg',
},
{
name: 'Florida',
population: '20.27M',
// https://commons.wikimedia.org/wiki/File:Flag_of_Florida.svg
flag: 'https://upload.wikimedia.org/wikipedia/commons/f/f7/Flag_of_Florida.svg',
},
{
name: 'Texas',
population: '27.47M',
// https://commons.wikimedia.org/wiki/File:Flag_of_Texas.svg
flag: 'https://upload.wikimedia.org/wikipedia/commons/f/f7/Flag_of_Texas.svg',
},
];

displayFn(state: State | null): string {
return state?.name ?? '';
}

private _filterStates(value: string): State[] {
const filterValue = value.toLowerCase();
return this.states.filter(state => state.name.toLowerCase().includes(filterValue));
}
}
1 change: 1 addition & 0 deletions src/components-examples/material/autocomplete/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export {AutocompleteAutoActiveFirstOptionExample} from './autocomplete-auto-active-first-option/autocomplete-auto-active-first-option-example';
export {AutocompleteCustomTriggerExample} from './autocomplete-custom-trigger/autocomplete-custom-trigger-example';
export {AutocompleteDisplayExample} from './autocomplete-display/autocomplete-display-example';
export {AutocompleteFilterExample} from './autocomplete-filter/autocomplete-filter-example';
export {AutocompleteOptgroupExample} from './autocomplete-optgroup/autocomplete-optgroup-example';
Expand Down
1 change: 1 addition & 0 deletions src/material/autocomplete/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ ng_project(
"autocomplete.ts",
"autocomplete-module.ts",
"autocomplete-origin.ts",
"autocomplete-selected-trigger.ts",
"autocomplete-trigger.ts",
"index.ts",
"public-api.ts",
Expand Down
3 changes: 3 additions & 0 deletions src/material/autocomplete/autocomplete-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {BidiModule} from '@angular/cdk/bidi';
import {CdkScrollableModule} from '@angular/cdk/scrolling';
import {OverlayModule} from '@angular/cdk/overlay';
import {MatAutocomplete} from './autocomplete';
import {MatAutocompleteSelectedTrigger} from './autocomplete-selected-trigger';
import {MatAutocompleteTrigger} from './autocomplete-trigger';
import {MatAutocompleteOrigin} from './autocomplete-origin';

Expand All @@ -22,6 +23,7 @@ import {MatAutocompleteOrigin} from './autocomplete-origin';
MatAutocomplete,
MatAutocompleteTrigger,
MatAutocompleteOrigin,
MatAutocompleteSelectedTrigger,
],
exports: [
CdkScrollableModule,
Expand All @@ -30,6 +32,7 @@ import {MatAutocompleteOrigin} from './autocomplete-origin';
BidiModule,
MatAutocompleteTrigger,
MatAutocompleteOrigin,
MatAutocompleteSelectedTrigger,
],
})
export class MatAutocompleteModule {}
39 changes: 39 additions & 0 deletions src/material/autocomplete/autocomplete-selected-trigger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/

import {Directive, InjectionToken, TemplateRef, inject} from '@angular/core';

/**
* Injection token that references the `MatAutocompleteSelectedTrigger`.
* @docs-private
*/
export const MAT_AUTOCOMPLETE_SELECTED_TRIGGER = new InjectionToken<MatAutocompleteSelectedTrigger>(
'MatAutocompleteSelectedTrigger',
);

/**
* Used to provide a custom template for the selected option display in `mat-autocomplete`,
* similar to `mat-select-trigger` for `mat-select`. Place inside `<mat-autocomplete>`:
*
* ```html
* <mat-autocomplete>
* <ng-template matAutocompleteSelectedTrigger let-value>{{ value }}</ng-template>
* </mat-autocomplete>
* ```
*
* The `$implicit` template context variable is the raw selected value.
*/
@Directive({
selector: 'ng-template[matAutocompleteSelectedTrigger]',
providers: [
{provide: MAT_AUTOCOMPLETE_SELECTED_TRIGGER, useExisting: MatAutocompleteSelectedTrigger},
],
})
export class MatAutocompleteSelectedTrigger {
readonly templateRef = inject(TemplateRef);
}
Loading
Loading