Angular2 Динамическое поле ввода потеряет фокус при изменении входа

Я делаю динамическую форму. A Field имеет список значений. Каждое значение представлено строкой.

export class Field{
    name: string;
    values: string[] = [];
    fieldType: string;
    constructor(fieldType: string) {this.fieldType = fieldType;}
}

У меня есть функция в моем компоненте, которая добавляет новое значение в поле.

addValue(field){
    field.values.push("");
}

Значения и кнопка отображаются в моем HTML.

<div id="dropdown-values" *ngFor="let value of field.values; let j=index">
    <input type="text" class="form-control" [(ngModel)]="field.values[j]" [name]="'value' + j + '.' + i"/><br/>
</div>
<div class="text-center">
    <a href="javascript:void(0);" (click)="addValue(field)"><i class="fa fa-plus-circle" aria-hidden="true"></i></a>
</div>

Как только я напишу текст во входном значении значения, вход теряет фокус. Если я добавлю много значений в поле, и я напишу символ в один ввод значений, вход потеряет фокус и символ будет записан на каждом входе.

Ответ 1

Это происходит, когда массив является примитивным типом, в вашем случае массивом String. Это можно решить, используя TrackBy. Поэтому измените шаблон, чтобы он соответствовал следующему:

<div *ngFor="let value of field.values; let i=index; trackBy:trackByFn">
    <input type="text" [(ngModel)]="field.values[i]"  /><br/>
</div>
<div>
    <button (click)="addValue(field)">Click</button>
</div>

и в файле ts добавьте функцию trackByFn, которая возвращает (уникальный) index значение:

trackByFn(index: any, item: any) {
   return index;
}

Это ссылка по той же проблеме, за исключением проблемы для AngularJS, но проблема соответствует вашей, Самый важный отрывок из этой страницы:

Вы повторяете массив, и вы меняете элементы массива (обратите внимание, что ваши элементы являются строками, которые являются примитивами в JS и, таким образом, сравниваются "по значению" ). Поскольку обнаружены новые элементы, старые элементы удаляются из DOM, и новые (которые, очевидно, не получают фокуса) не создаются.

С TrackBy Angular можно отслеживать, какие элементы были добавлены (или удалены) в соответствии с уникальным идентификатором, и создать или уничтожить только те вещи, которые изменились, что означает, что вы не теряете фокус на своем поле ввода:)

Как видно из ссылки, вы также можете изменить свой массив, чтобы содержать объекты, которые являются уникальными, и использовать [(ngModel)]="value.id", например, но это может быть не то, что вам нужно.

Ответ 2

Это происходило со мной, когда я выполнял итерирование ключей объектов и значений с помощью вспомогательной функции:

<div *ngFor="let thing of getThings()" [attr.thingname]="thing.key">
  ... {{ applyThing(thing.value) }}
</div>

В моем компоненте я возвращал массив объектов, содержащих пары ключ/значение:

export ThingComponent {
  ...

  //this.things = { a: { ... }, b: { ... }, c: { ... } }

  public getThings() {
    return Object.keys(this.things).map((key) => {
      return {key: key, value: this.things[key] }
    })
  }
}

Ответ, заданный @AJT_82, определенно работает точно так же, как рекламируется. Однако в моем случае особая проблема заключалась в том, что вспомогательная функция getThings() возвращала новый список объектов каждый раз. Несмотря на то, что их контент был одинаков, сами объекты были восстановлены при каждом вызове функции (которая происходила при обнаружении изменений), и, следовательно, к детектору изменения, у них были разные идентичности, и форма была регенерирована при каждом изменении модели.

Простым решением в моем случае было кэшировать результат getThings() и использовать его как итератор:

<div *ngFor="let thing of cachedThings" [attr.thingname]="thing.key">
  ... {{ applyThing(thing.value) }}
</div>

...

export ThingComponent {
  public cachedThings = getThings()
  ...

  //this.things = { a: { ... }, b: { ... }, c: { ... } }

  private getThings() {
    return Object.keys(this.things).map((key) => {
      return {key: key, value: this.things[key] }
    })
  }
}

В случаях, когда cachedThings может потребоваться изменить, его необходимо будет обновить вручную, чтобы детектор изменений инициировал повторный рендеринг.