Все мы используем формы в наших приложениях. Выбор в формах удобен, когда у вас есть несколько вариантов. В Angular в качестве значения OPTION мы можем использовать не только строковые литералы, но и объекты.

Простейший ВЫБОР

<form [formGroup]="countryForm">
 <select formControlName="countryControl">
   <option [value]="country" *ngFor="let country of countries">             {{country}}</option>
 </select>
</form>

И в файле .ts

countryForm: FormGroup;
countries = ['USA', 'Canada', 'Uk']
constructor(private fb: FormBuilder) {}
ngOnInit() {
 this.countryForm = this.fb.group({
   countryControl: ['Canada']
 });
}

После этого у нас будет SELECT с выбранным по умолчанию «Канада».

Все это хорошо, но как насчет того, чтобы в качестве значения OPTION был сложный объект, а не обычная строка. Для этого Angular предоставляет директиву [ngValue].

Теперь давайте изменим наш массив countries так, чтобы вместо строк он содержал объекты с названием страны, кодом валюты страны и уникальным свойством (в данном случае id).

countries = [{
 id: '8f8c6e98',
 name: 'USA',
 code: 'USD'
},
{
 id: '169fee1a',
 name: 'Canada',
 code: 'CAD'
},
{
 id: '3953154c',
 name: 'UK',
 code: 'GBP'
}]

Теперь, поскольку country является объектом, в нашем шаблоне нам нужно отобразить country.name для SELECT OPTIONS, а также использовать директиву [ngValue], поскольку мы передают объект как значение.

<option [ngValue]="country" *ngFor="let country of countries">{{country.name}}</option>

Также, чтобы предоставить значение по умолчанию, нам нужно установить один из объектов из массива countries.

this.countryForm = this.fb.group({
  countryControl: [this.countries[1]]
});

Итак, теперь у нас есть тот же SELECT, но теперь в качестве значения мы получаем не название страны, а объект всей страны.

Теперь предположим, что нам нужно добавить ввод, где пользователи могут добавлять дополнительные ОПЦИИ для этого ВЫБОР. У нас будет два входа, где пользователи могут добавить название страны и код страны. После этого новый OPTION будет добавлен в наш SELECT, и этот OPTION будет выбран автоматически. Таким образом, обычно все ОПЦИИ из SELECT поступают с сервера, поэтому в реальном примере вам также необходимо добавить новые ОПЦИИ на сервер, выполнить запрос POST и получить новые данные. В этом примере я буду моделировать это с помощью setTimeout.

Итак, в файле шаблона

<input type="text" #name placeholder="Add country name">
<input type="text" #code placeholder="Add country code">
<button (click)="addNewOption(name.value, code.value)">Add to SELECT</button>

Добавить addNewOption() method в файл .ts

addNewOption(name: string, code: string) {
  setTimeout(() => {
   this.countries = [
      {
        id: '8f8c6e98',
        name: 'USA',
        code: 'USD'
      },
      {  
        id: '169fee1a',
        name: 'Canada',
        code: 'CAD'
      },
      {
        id: '3953154c',
        name: 'UK',
        code: 'GBP'
      },
      {
        id: '68c61e29',
        name,
        code
      }
  ];
this.countryForm.controls['countryControl'].patchValue(
     {id : '68c61e29', name, code}
  )
 }, 500)
}

Итак, мы получаем значения из двух входов и имитируем вызов сервера. В результате мы получаем новые данные, содержащие страны (включая ту, которую мы добавили) и назначаем их массиву countries (идентификатор был сгенерирован на «сервере»). После получения данных мы также используем метод patchValue, чтобы изменить значение по умолчанию для нашего SELECT.

Вот что будет

Итак, после отправки нового ВАРИАНТА он есть, но не выбран. Причина этого в том, что Angular использует идентификатор объекта для выбора параметров. Поэтому, когда мы получим новые данные, объекты будут иметь разные идентификаторы. Даже если есть объект, который мы предоставили в patchValue

{id : '68c61e29', name, code}

Этот объект для Angular отличается от того, который находится внутри массива countries.

Для решения этой проблемы Angular предоставляет входные данные compareWith из директивы SelectMultipleControlValueAccessor, которая применяется к нашему SELECT .

compareWith принимает функцию с двумя аргументами: option1 и option2. Если задано compareWith, Angular выбирает параметры по возвращаемому значению функции.

Итак, сначала давайте напишем эту функцию. Создайте новую функцию в файле .ts

compareFn(c1: any, c2:any): boolean {     
     return c1 && c2 ? c1.id === c2.id : c1 === c2; 
}

И добавьте эту функцию в SELECT

<select formControlName="countryControl" [compareWith]="compareFn">
...

После этого Angular будет сравнивать ОПЦИИ, используя их идентификаторы, а не идентификаторы объектов. В итоге мы получили то, что ожидали