web-dev-qa-db-fra.com

Angular2 Le champ de saisie dynamique perd le focus lorsque la saisie change

Je fais une forme dynamique. Une Field a une liste de valeurs. Chaque valeur est représentée par une chaîne.

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

J'ai une fonction dans mon composant qui ajoute une nouvelle valeur au champ.

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

Les valeurs et le bouton sont affichés comme ceci dans mon 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>

Dès que j'écris du texte dans une entrée de valeur, l'entrée perd le focus . Si j'ajoute beaucoup de valeurs à un champ et que j'écris un caractère dans l'une des valeurs en entrée, l'entrée perd le focus et le caractère est écrit dans chaque entrée. 

28
Servietsky

Cela se produit lorsque le tableau est un type primitif, dans votre cas un tableau String. Cela peut être résolu en utilisant TrackBy. Alors changez votre modèle pour qu'il corresponde à ce qui suit:

<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>

et dans le fichier ts ajoutez la fonction trackByFn, qui renvoie la (unique) index de la valeur:

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

Ceci est un link sur le même problème, sauf que le problème concerne AngularJS, mais le problème correspond au vôtre. Extrait le plus important de cette page:

Vous répétez sur un tableau et vous changez les éléments du tableau (notez que vos éléments sont des chaînes, qui sont des primitives dans JS et sont donc comparées "par valeur"). Étant donné que de nouveaux éléments sont détectés, les anciens éléments sont supprimés du DOM et de nouveaux sont créés (ils ne sont évidemment pas activés).

Avec TrackBy Angular, vous pouvez savoir quels éléments ont été ajoutés (ou supprimés) en fonction de l'identifiant unique et créer ou détruire uniquement les éléments modifiés, ce qui signifie que vous ne perdez pas le focus sur votre champ de saisie :)

Comme indiqué dans le lien, vous pouvez également modifier votre tableau pour contenir des objets uniques et utiliser [(ngModel)]="value.id" par exemple, mais ce n'est peut-être pas ce dont vous avez besoin.

75
AJT_82

Cela m'arrivait lorsque je parcourais les clés et les valeurs d'un objet à l'aide d'une fonction d'assistance:

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

Dans mon composant, je renvoyais un tableau d'objets contenant des paires clé/valeur:

export ThingComponent {
  ...

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

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

La réponse donnée par @ AJT_82 fonctionne vraiment exactement comme annoncé. Cependant, dans mon cas, le problème spécifique était que la fonction d'assistance, getThings(), renvoyait une nouvelle liste d'objets à chaque fois. Même si leur contenu était identique, les objets eux-mêmes étaient régénérés à chaque appel de la fonction (ce qui se passait lors de la détection des modifications) et, par conséquent, ils avaient des identités différentes pour le détecteur de modification et la forme était régénérée à chaque modification de modèle.

La solution simple dans mon cas consistait à mettre en cache le résultat de getThings() et à l'utiliser comme itérateur:

<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] }
    })
  }
}

Dans les cas où cachedThings devra peut-être varier, il faudra le mettre à jour manuellement pour que le détecteur de changement déclenche un nouveau rendu.

0
t.888