Itération sur la carte Typescript

135

J'essaie d'itérer sur une carte dactylographiée mais je continue à recevoir des erreurs et je n'ai pas encore trouvé de solution pour un problème aussi trivial.

Mon code est:

myMap : Map<string, boolean>;
for(let key of myMap.keys()) {
   console.log(key);
}

Et j'obtiens l'erreur:

Le type 'IterableIteratorShim <[string, boolean]>' n'est pas un type de tableau ou un type de chaîne.

Trace complète de la pile:

 Error: Typescript found the following errors:
  /home/project/tmp/broccoli_type_script_compiler-input_base_path-q4GtzHgb.tmp/0/src/app/project/project-data.service.ts (21, 20): Type 'IterableIteratorShim<[string, boolean]>' is not an array type or a string type.
    at BroccoliTypeScriptCompiler._doIncrementalBuild (/home/project/node_modules/angular-cli/lib/broccoli/broccoli-typescript.js:115:19)
    at BroccoliTypeScriptCompiler.build (/home/project/node_modules/angular-cli/lib/broccoli/broccoli-typescript.js:43:10)
    at /home/project/node_modules/broccoli-caching-writer/index.js:152:21
    at lib$rsvp$$internal$$tryCatch (/home/project/node_modules/rsvp/dist/rsvp.js:1036:16)
    at lib$rsvp$$internal$$invokeCallback (/home/project/node_modules/rsvp/dist/rsvp.js:1048:17)
    at lib$rsvp$$internal$$publish (/home/project/node_modules/rsvp/dist/rsvp.js:1019:11)
    at lib$rsvp$asap$$flush (/home/project/node_modules/rsvp/dist/rsvp.js:1198:9)
    at _combinedTickCallback (internal/process/next_tick.js:67:7)
    at process._tickCallback (internal/process/next_tick.js:98:9)

J'utilise angular-cli beta5 et dactylographié 1.8.10 et ma cible est es5. Est-ce que quelqu'un a eu ce problème?

mwe
la source
4
Voir cette réponse de github github.com/Microsoft/TypeScript/issues/…
yurzui

Réponses:

212

Vous pouvez utiliser à la Map.prototype.forEach((value, key, map) => void, thisArg?) : voidplace

Utilisez-le comme ceci:

myMap.forEach((value: boolean, key: string) => {
    console.log(key, value);
});
rinukkusu
la source
15
Je viens de rencontrer ça. Il ne semble pas que TypeScript respecte la spécification pour l'itération de la carte, du moins selon MDN qui spécifie une boucle for-of. .forEachest sous-optimal, car vous ne pouvez pas le casser, AFAIK
Samjones
14
Je ne sais pas pourquoi sa valeur est alors clé. Semble à l'envers.
Paul Rooney
@PaulRooney, c'est probablement comme ça avec lequel s'harmoniser Array.prototype.map.
Ahmed Fasih
1
Comment arrêter ce processus d'itération si nous trouvons ce dont nous avons besoin?
JavaRunner
36

es6

for (let [key, value] of map) {
    console.log(key, value);
}

es5

for (let entry of Array.from(map.entries())) {
    let key = entry[0];
    let value = entry[1];
}
Oded Breiner
la source
1
La version es6 donnée ci-dessus ne compile pas en mode strict.
Stéphane
Merci Monsieur.
Kitanga Nday le
36

Utilisez simplement la Array.from()méthode pour le convertir en Array:

myMap : Map<string, boolean>;
for(let key of Array.from( myMap.keys()) ) {
   console.log(key);
}
6qat
la source
5
La conversion de cartes est une opération très gourmande en performances et n'est pas la bonne façon de résoudre un problème qui est essentiellement juste le compilateur cachant les parties essentielles du langage. Il suffit de <any>lancer la carte pour l'itérer avec for-of.
Kilves
1
Méfiez-vous: je pensais que la suggestion de @Kilves ci-dessus de convertir la carte en "any" était une solution de contournement élégante. Quand je l'ai fait, le code a été compilé et exécuté sans plainte, mais la carte n'a pas été réellement itérée - le contenu de la boucle n'a jamais été exécuté. La Array.from()stratégie proposée ici a fonctionné pour moi.
mactyr
J'ai essayé cela aussi, cela n'a pas fonctionné pour moi non plus, ce qui est encore plus stupide étant donné qu'il fait partie d'ES6 et devrait "juste fonctionner" sur la plupart des navigateurs. Mais je suppose que nos seigneurs angulaires utilisent un jumbo magique dans zone.js pour que cela ne fonctionne pas, car ils détestent ES6. Soupir.
Kilves
28

Utilisation des fonctions Array.from , Array.prototype.forEach () et flèche :

Parcourez les touches :

Array.from(myMap.keys()).forEach(key => console.log(key));

Répétez les valeurs :

Array.from(myMap.values()).forEach(value => console.log(value));

Parcourez les entrées :

Array.from(myMap.entries()).forEach(entry => console.log('Key: ' + entry[0] + ' Value: ' + entry[1]));
Tobold
la source
2
Je ne sais pas pourquoi, j'ai la carte comme Map <String, CustomeClass>. aucune des méthodes ci-dessus n'a fonctionné, sauf Array.from (myMap.values ​​()). forEach (value => console.log (value)) ;.
Rajashree Gr
27

Cela a fonctionné pour moi. Version TypeScript: 2.8.3

for (const [key, value] of Object.entries(myMap)) { 
    console.log(key, value);
}
Debashish Sen
la source
7
J'ai fait fonctionner cela en changeant Object.entries (myMap) en juste myMap.entries (). J'aime cette réponse car elle évite les pièges de gestion des erreurs des appels .forEach.
encrest le
2
A noter que si votre targeten tsconfigest es5cela jette une erreur, mais es6fonctionne correctement. Vous pouvez aussi simplement le faire for (const [key, value] of myMap)lors du ciblagees6
Benjamin Vogler
16

Selon les notes de publication de TypeScript 2.3 sur "Nouveau --downlevelIteration" :

for..of statements, Array Destructuring et Spread dans les expressions Array, Call et New prennent en charge Symbol.iterator dans ES5 / E3 si disponible lors de l'utilisation --downlevelIteration

Ce n'est pas activé par défaut! Ajoutez "downlevelIteration": trueà votre tsconfig.json, ou passez l' --downlevelIterationindicateur à tsc, pour obtenir une prise en charge complète de l'itérateur.

Avec cela en place, vous pouvez écrire for (let keyval of myMap) {...}et keyvalle type de sera automatiquement déduit.


Pourquoi est-ce désactivé par défaut? Selon le contributeur TypeScript @aluanhaddad ,

Elle est facultative car elle a un impact très significatif sur la taille du code généré, et potentiellement sur les performances, pour toutes les utilisations d'itérables (y compris les tableaux).

Si vous pouvez cibler ES2015 ( "target": "es2015"dans tsconfig.jsonou tsc --target ES2015) ou une version ultérieure, l'activation downlevelIterationest une évidence, mais si vous ciblez ES5 / ES3, vous pouvez effectuer une analyse comparative pour vous assurer que la prise en charge de l'itérateur n'affecte pas les performances (si c'est le cas, vous pourriez être mieux avec la Array.fromconversion ou forEachune autre solution de contournement).

Ahmed Fasih
la source
est-ce correct ou dangereux d'activer cela lors de l'utilisation d'Angular?
Simon_Weaver
9

Cela a fonctionné pour moi.

Object.keys(myMap).map( key => {
    console.log("key: " + key);
    console.log("value: " + myMap[key]);
});
Jason Slobotski
la source
2
Avec cela, les clés seront toujours des chaînes
Ixx
8

J'utilise le dernier TS et le dernier nœud (v2.6 et v8.9 respectivement) et je peux faire:

let myMap = new Map<string, boolean>();
myMap.set("a", true);
for (let [k, v] of myMap) {
    console.log(k + "=" + v);
}
Lazieburd
la source
Pouvez-vous confirmer que vous deviez d'abord définir "downlevelIteration": truevotre tsconfig.json?
Ahmed Fasih
3
Je ne l'ai pas downlevelIteratonfixé, mon objectif l'est es2017cependant.
lazieburd
Ne puis-je pas faire cela pour l'élément Map <string, CustomClass []>? le compilateur dit que ce n'est pas un tableau de type ou de type chaîne.
Rajashree Gr
cela ne fonctionne pas pour moi sur 2.8 - je reçoisType 'Map<K, V>' is not an array type or a string type.
Simon_Weaver
3

Vous pouvez également appliquer la méthode de carte de tableau à l'itérable Map.entries ():

[...myMap.entries()].map(
     ([key, value]: [string, number]) => console.log(key, value)
);

De plus, comme indiqué dans d'autres réponses, vous devrez peut-être activer l'itération de bas niveau dans votre tsconfig.json (sous les options du compilateur):

  "downlevelIteration": true,
cham
la source
Cette fonctionnalité a été introduite dans TypeScript 2.3. Le problème est survenu avec TypeScript 1.8.10
mwe
Si vous ne pouvez pas utiliser "downlevelIteration", vous pouvez utiliser:const projected = Array.from(myMap).map(...);
Efrain
1

Juste une explication simple pour l'utiliser dans un document HTML.

Si vous avez une carte de types (clé, tableau), vous initialisez le tableau de cette façon:

public cityShop: Map<string, Shop[]> = new Map();

Et pour l'itérer, vous créez un tableau à partir de valeurs clés.

Utilisez-le simplement comme un tableau comme dans:

keys = Array.from(this.cityShop.keys());

Ensuite, en HTML, vous pouvez utiliser:

*ngFor="let key of keys"

Dans cette boucle, vous obtenez simplement la valeur du tableau avec:

this.cityShop.get(key)

Terminé!

Sophie C Musical
la source
1

Sur Typescript 3.5 et Angular 8 LTS, il était nécessaire de convertir le type comme suit:

for (let [k, v] of Object.entries(someMap)) {
    console.log(k, v)
}
Constantin Konstantinidis
la source
-1

Si vous n'aimez pas vraiment les fonctions imbriquées, vous pouvez également parcourir les touches:

myMap : Map<string, boolean>;
for(let key of myMap) {
   if (myMap.hasOwnProperty(key)) {
       console.log(JSON.stringify({key: key, value: myMap[key]}));
   }
}

Notez que vous devez filtrer les itérations non clés avec le hasOwnProperty, si vous ne le faites pas, vous obtenez un avertissement ou une erreur.

peterh - Réintégrer Monica
la source
comment parcourir la carte en html? cela ne semble pas du tout fonctionner. <div ng-repeat = "(clé, valeur) in model.myMap"> {{clé}}. </div>
Shinya Koizumi
@ powerfade917 Cela ne fonctionne pas, cela ne fonctionne que pour les tableaux car angular est un tas de déchets. Mais posez ceci comme une nouvelle question et vous apprendrez ainsi que angular n'est pas un tas de déchets, mais vous devez le convertir en tableau. Notez que vous n'êtes pas non plus au sommet de la programmation, car vous êtes apparemment incapable de faire la différence entre angulaire et dactylographié.
peterh - Réintégrer Monica