67 votes

Angular2 Champ de saisie dynamique perd le focus lorsque la saisie change

Je crée un formulaire dynamique. A Field a une liste de valeurs. Chaque valeur est représentée par une chaîne de caractères.

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 l'entrée d'une valeur, l'entrée perd le focus. Si j'ajoute plusieurs valeurs à un champ et que j'écris un caractère dans l'une des entrées de valeurs, l'entrée perd le focus et le caractère est écrit dans chaque entrée.

0 votes

165voto

AJT_82 Points 30605

Cela se produit lorsque le tableau est un type primitif, dans votre cas un String réseau. Ce problème peut être résolu en utilisant TrackBy . Changez donc 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 ajouter la fonction trackByFn qui renvoie le (unique) index de la valeur :

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

Il s'agit d'un lien à propos du même problème, sauf que le problème est pour AngularJS, mais le problème correspond au vôtre. L'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 de caractères, qui sont des primitives en JS et donc comparées "par valeur"). Puisque de nouveaux éléments sont détectés, les anciens éléments sont supprimés du DOM et de nouveaux sont créés (qui n'obtiennent évidemment pas le focus).

Avec TrackBy Angular peut suivre les éléments qui ont été ajoutés (ou supprimés) en fonction de l'identifiant unique et créer ou détruire uniquement les éléments qui ont changé, 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 qu'il contienne des objets uniques et utiliser la fonction [(ngModel)]="value.id" par exemple, mais ce n'est peut-être pas ce dont vous avez besoin.

4 votes

Dans mon cas, je n'itérais pas sur un tableau de primitives, mais sur un tableau d'objets (un FormArray, en fait) et j'ai toujours le même problème. Malgré cela, j'ai suivi la suggestion de @AJT_82 et le problème a été résolu.

1 votes

@AlexisJoséBravoLlovera Heureux d'entendre que cela a fonctionné et vous a été utile ! Il semble qu'il y ait une sorte de chose qui se passe avec formarray, je ne connais pas la raison moi-même pour cela, puisque nous avons effectivement affaire à des objets, problème similaire : stackoverflow.com/questions/44445676/

0 votes

Vous êtes un héros. Merci.

0voto

t.888 Points 2619

Cela m'arrivait lorsque j'itérais sur les clés et les valeurs d'un objet en utilisant une fonction d'aide :

<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 exactement comme indiqué. Cependant, dans mon cas, le problème spécifique était que la fonction d'aide, getThings() retournait une nouvelle liste d'objets à chaque fois. Même si leur contenu était le même, les objets eux-mêmes étaient régénérés à chaque appel à la fonction (ce qui se produisait pendant la détection des changements) et donc, pour le détecteur de changements, ils avaient des identités différentes, et le formulaire était régénéré à chaque changement de modèle.

La solution simple dans mon cas a été de mettre en cache le résultat de l'action 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 doit varier, il devra être mis à jour manuellement pour que le détecteur de changement déclenche un nouveau rendu.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X