Skip to content
Draft
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
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
<pre>value: {{myControl().value().value | json}}</pre>
<pre>rawValue: {{myControl().value().rawValue | json}}</pre>

<form class="example-form">
<mat-form-field class="example-full-width">
<mat-label>Assignee</mat-label>
<input type="text" matInput [formControl]="myControl" [matAutocomplete]="auto">
<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn">
@for (option of filteredOptions | async; track option) {
<input
type="text"
matInput
(input)="updateRawValue($any($event.target).value)"
[formField]="myControl.rawValue"
[matAutocomplete]="auto"
>
<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn" (optionSelected)="selectOption($event.option.value)">
@for (option of filteredOptions(); track option) {
<mat-option [value]="option">{{option.name}}</mat-option>
}
</mat-autocomplete>
@for (error of myControl.rawValue().errors(); track error) {
<mat-error>{{error.message}}</mat-error>
}
</mat-form-field>
</form>
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import {Component} from '@angular/core';
import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms';
import {Observable} from 'rxjs';
import {map, startWith} from 'rxjs/operators';
import {AsyncPipe} from '@angular/common';
import {Component, computed, signal} from '@angular/core';
import {MatAutocompleteModule} from '@angular/material/autocomplete';
import {MatInputModule} from '@angular/material/input';
import {MatFormFieldModule} from '@angular/material/form-field';
import {JsonPipe} from '@angular/common';
import {form, FormField, validate} from '@angular/forms/signals';

export interface User {
name: string;
Expand All @@ -18,37 +16,54 @@ export interface User {
selector: 'autocomplete-display-example',
templateUrl: 'autocomplete-display-example.html',
styleUrl: 'autocomplete-display-example.css',
imports: [
FormsModule,
MatFormFieldModule,
MatInputModule,
MatAutocompleteModule,
ReactiveFormsModule,
AsyncPipe,
],
imports: [MatFormFieldModule, MatInputModule, MatAutocompleteModule, JsonPipe, FormField],
})
export class AutocompleteDisplayExample {
myControl = new FormControl<string | User>('');
options: User[] = [{name: 'Mary'}, {name: 'Shelley'}, {name: 'Igor'}];
filteredOptions: Observable<User[]>;

constructor() {
this.filteredOptions = this.myControl.valueChanges.pipe(
startWith(''),
map(value => {
const name = typeof value === 'string' ? value : value?.name;
return name ? this._filter(name as string) : this.options.slice();
}),
);
}
// TODO - split form / model into own class fields?
// TODO - make case matter?
protected myControl = form(
signal<{rawValue: string; value: User | null}>({rawValue: '', value: null}),
p => {
validate(p.rawValue, ({valueOf}) => {
const rawValue = valueOf(p.rawValue);
const matchingOption =
this.options().find(option => option.name.toLowerCase() === rawValue.toLowerCase()) ??
null;

if (!matchingOption) {
return {message: 'Value must be one of the options', kind: 'exactMatch'};
}
return null;
});
},
);

protected options = signal<User[]>([{name: 'Mary'}, {name: 'Shelley'}, {name: 'Igor'}]);

protected filteredOptions = computed(() => {
const name = this.myControl().value().rawValue;
const options = this.options();
return name ? this._filter(name) : options;
});

displayFn(user: User): string {
protected displayFn(user: User | null): string {
return user && user.name ? user.name : '';
}

protected updateRawValue(rawValue: string): void {
const matchingOption =
this.options().find(option => option.name.toLowerCase() === rawValue.trim().toLowerCase()) ??
null;
this.myControl().value.update(model => ({...model, value: matchingOption}));
}

protected selectOption(user: User): void {
this.myControl().value.update(model => ({...model, rawValue: user.name, value: user}));
}

private _filter(name: string): User[] {
const filterValue = name.toLowerCase();

return this.options.filter(option => option.name.toLowerCase().includes(filterValue));
return this.options().filter(option => option.name.toLowerCase().includes(filterValue));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
placeholder="Pick one"
aria-label="Number"
matInput
[formControl]="myControl"
[formField]="form"
[matAutocomplete]="auto">
<mat-autocomplete #auto="matAutocomplete">
@for (option of filteredOptions | async; track option) {
@for (option of filteredOptions(); track option) {
<mat-option [value]="option">{{option}}</mat-option>
}
</mat-autocomplete>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import {Component} from '@angular/core';
import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms';
import {Observable} from 'rxjs';
import {map, startWith} from 'rxjs/operators';
import {AsyncPipe} from '@angular/common';
import {Component, computed, signal} from '@angular/core';
import {form, FormField} from '@angular/forms/signals';
import {MatAutocompleteModule} from '@angular/material/autocomplete';
import {MatInputModule} from '@angular/material/input';
import {MatFormFieldModule} from '@angular/material/form-field';
Expand All @@ -14,26 +11,13 @@ import {MatFormFieldModule} from '@angular/material/form-field';
selector: 'autocomplete-filter-example',
templateUrl: 'autocomplete-filter-example.html',
styleUrl: 'autocomplete-filter-example.css',
imports: [
FormsModule,
MatFormFieldModule,
MatInputModule,
MatAutocompleteModule,
ReactiveFormsModule,
AsyncPipe,
],
imports: [MatFormFieldModule, MatInputModule, MatAutocompleteModule, FormField],
})
export class AutocompleteFilterExample {
myControl = new FormControl('');
options: string[] = ['One', 'Two', 'Three'];
filteredOptions: Observable<string[]>;
protected form = form(signal(''));
protected options = ['One', 'Two', 'Three'] as const;

constructor() {
this.filteredOptions = this.myControl.valueChanges.pipe(
startWith(''),
map(value => this._filter(value || '')),
);
}
protected filteredOptions = computed(() => this._filter(this.form().value()));

private _filter(value: string): string[] {
const filterValue = value.toLowerCase();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
<form [formGroup]="stateForm">
<form [formRoot]="stateForm">
<mat-form-field>
<mat-label>States Group</mat-label>
<input type="text"
matInput
formControlName="stateGroup"
[formField]="stateForm.stateGroup"
required
[matAutocomplete]="autoGroup">
<!-- #docregion mat-autocomplete -->
<mat-autocomplete #autoGroup="matAutocomplete">
@for (group of stateGroupOptions | async; track group) {
@for (group of stateGroupOptions(); track group) {
<mat-optgroup [label]="group.letter">
@for (name of group.names; track name) {
<mat-option [value]="name">{{name}}</mat-option>
Expand Down
Loading
Loading