Я хочу создать ввод автозаполнения с угловым (17) и угловым материалом. У меня есть массив объектов (местоположений), которые имеют идентификатор, координаты и необязательный адрес. Я уже могу фильтровать значения и отображать их при вводе формы, но я не могу сохранить только адрес в элементе управления формой. Я могу сохранить только весь объект, но хочу сохранить только идентификатор. Это возможно?
Шаблон:
<form [formGroup] = "form">
<mat-form-field>
<input
matInput
placeholder = "Search"
[matAutocomplete] = "auto"
[formControlName] = "'location'" />
<mat-autocomplete
#auto = "matAutocomplete"
[displayWith] = "displayFn">
<mat-option
*ngFor = "let option of filteredOptions | async"
[value] = "option">
{{ displayFn(option) }}
</mat-option>
</mat-autocomplete>
</mat-form-field>
</form>
Компонент
locations: Location[] = [
{ id: '1', coordinates: { lat: 10, lon: 20 }, address: { street: 'Street 1', housenumber: '1', city: 'City 1' } },
{ id: '2', coordinates: { lat: 30, lon: 40 }, address: { street: 'Another Street 2', housenumber: '2', city: 'City 2' } },
{ id: '3', coordinates: { lat: 50, lon: 60 } }
];
form = this.createForm();
filteredOptions: Observable<Location[]> = of([]);
constructor(private _formBuilder: FormBuilder) {
this.filteredOptions = this.form.controls['location'].valueChanges.pipe(
startWith(''),
map((value) => (typeof value === 'string' ? value : value.id)),
map((id) => (id ? this._filter(id) : this.locations.slice()))
);
}
createForm(): FormGroup {
return this._formBuilder.nonNullable.group({
location: new FormControl<string>('')
})
}
displayFn(location: Location): string {
if (location) {
if (location.address) {
return location.address.street + ' ' + location.address.housenumber + ', ' + location.address.city
} else {
return location.coordinates.lat + ', ' + location.coordinates.lon;
}
}
return '';
}
private _filter(value: string): any[] {
const filterValue = value.toLowerCase();
return this.locations.filter(
(option) => {
return option.id.toString().toLowerCase().includes(filterValue) ||
(option.address &&
(option.address.street.toLowerCase().includes(filterValue) ||
option.address.city.toLowerCase().includes(filterValue))) ||
option.coordinates.lat.toString().toLowerCase().includes(filterValue) ||
option.coordinates.lon.toString().toLowerCase().includes(filterValue)
}
);
}
Это хорошее предложение. Я пробую это.





Вам нужно использовать [value] = "option.id".
Таким образом, когда опция выбрана, в элементе управления формой будет сохранен только идентификатор объекта Location.
Это то, что я проверил в первую очередь. Но тогда окно автозаполнения невозможно закрыть, и я могу выбрать несколько элементов.
Я изменил код после подсказки Юн Шуна, чтобы иметь отдельный элемент управления формой для автозаполнения. Теперь это работает.
Шаблон
<form [formGroup] = "form">
<mat-form-field>
<input
matInput
placeholder = "Search"
[formControl] = "location"
[matAutocomplete] = "auto" />
<mat-autocomplete
#auto = "matAutocomplete"
[displayWith] = "displayFn">
<mat-option
*ngFor = "let option of filteredOptions | async"
[value] = "option">
{{ displayFn(option) }}
</mat-option>
</mat-autocomplete>
</mat-form-field>
</form>
Компонент
locations: Location[] = [
{ id: '1', coordinates: { lat: 10, lon: 20 }, address: { street: 'Street 1', housenumber: '1', city: 'City 1' } },
{ id: '2', coordinates: { lat: 30, lon: 40 }, address: { street: 'Another Street 2', housenumber: '2', city: 'City 2' } },
{ id: '3', coordinates: { lat: 50, lon: 60 } }
];
form = this.createForm();
location = new FormControl<Location | null>(null);
filteredOptions: Observable<Location[]> = of([]);
constructor(private _formBuilder: FormBuilder) {
this.filteredOptions = this.location.valueChanges.pipe(
startWith(''),
map((value) => {
if (value) {
return typeof value === 'string' ? value : value.id
}
return null
}),
map((id) => {
if (id) {
this.form.controls['locationId'].setValue(id);
return this._filter(id)
}
return this.locations.slice()
})
);
}
createForm(): FormGroup {
return this._formBuilder.nonNullable.group({
locationId: new FormControl<string>('', { nonNullable: true})
})
}
displayFn(location: Location): string {
if (location) {
if (location.address) {
return location.address.street + ' ' + location.address.housenumber + ', ' + location.address.city
} else {
return location.coordinates.lat + ', ' + location.coordinates.lon;
}
}
return '';
}
private _filter(value: string): any[] {
const filterValue = value.toLowerCase();
return this.locations.filter(
(option) => {
return option.id.toString().toLowerCase().includes(filterValue) ||
(option.address &&
(option.address.street.toLowerCase().includes(filterValue) ||
option.address.city.toLowerCase().includes(filterValue))) ||
option.coordinates.lat.toString().toLowerCase().includes(filterValue) ||
option.coordinates.lon.toString().toLowerCase().includes(filterValue)
}
);
}
Отлично, я думаю, что эта строка this.form.controls['locationId'].setValue(id); может переместиться на первую map, если проверить, что value не является строковым типом. Исходя из вашей текущей логики, каждый раз, когда вводимый текст будет устанавливать элемент управления locationId. Вы можете распечатать все значение/форму элемента управления locationId в HTML, ввести поле и наблюдать за значением.
По моему мнению, вам нужно разделить его, чтобы иметь два элемента управления (вход). Первый входной элемент — это
<mat-autocomplete>, который используется для фильтрации [без[formControlName] = "'location'"]. Второй (входной) элемент управления — это скрытый элемент управления, который используется для хранения выбранного идентификатора местоположения. Итак, когда выбран мат-автозаполнение (опция), вам нужно подключитьidко второму элементу управления. И если возможно, можете ли вы попробовать воспроизвести в StackBlitz? Спасибо.