272

I'm trying to iterate over a typescript map but I keep getting errors and I could not find any solution yet for such a trivial problem.

My code is:

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

And I get the Error:

Type 'IterableIteratorShim<[string, boolean]>' is not an array type or a string type.

Full Stack Trace:

 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)

I'm using angular-cli beta5 and typescript 1.8.10 and my target is es5. Has anyone had this Problem?

OneCricketeer
  • 179,855
  • 19
  • 132
  • 245
mwe
  • 3,043
  • 2
  • 11
  • 16

15 Answers15

364

You could use Map.prototype.forEach((value, key, map) => void, thisArg?) : void instead

Use it like this:

myMap.forEach((value: boolean, key: string) => {
    console.log(key, value);
});
Maximilian Riegler
  • 22,720
  • 4
  • 62
  • 71
  • 23
    Just ran into this. Doesn't look like TypeScript is respecting the spec for map iteration, at least according to [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) which specifies a for-of loop. `.forEach` is sub-optimal, since you can't break it , AFAIK – Samjones Jan 27 '17 at 20:36
  • 55
    dunno why its value then key. Seems backwards. – Paul Rooney Mar 21 '19 at 04:38
  • 1
    @PaulRooney it's like that probably to harmonize with `Array.prototype.map`. – Ahmed Fasih Jul 15 '19 at 23:32
  • 3
    How to stop this iteration process if we found what we need? – JavaRunner Jun 03 '20 at 19:44
  • 1
    @Samjones Using `return;` in a foreach is the same as `continue` in a normal for loop – monamona Apr 22 '21 at 08:23
  • 1
    @monamona And, pray, what would the equivalent of `break` be, since Samjones explicitly mentioned **breaking**.... The only way I know is to have an outside flag `skipToEnd = false`, instead of `break;` put `skipToEnd = true; return` and at the very beginning of the function you have to add `if (skipToEnd) return;`... not very elegant. – GACy20 Jul 16 '21 at 08:33
  • For typescript, I use `Object.entries(myMap).forEach((i) => { const key = i[0]; const val = i[1]; ... })` – MicahT Mar 29 '23 at 03:48
144

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
  • 28,523
  • 10
  • 105
  • 71
60

Just use Array.from() method to convert it to an Array:

myMap : Map<string, boolean>;
for(let key of Array.from( myMap.keys()) ) {
   console.log(key);
}
phoenix
  • 7,988
  • 6
  • 39
  • 45
6qat
  • 826
  • 6
  • 5
  • 9
    Converting maps is a very performance-hungry operation and not the correct way to solve a problem that's essentially just the compiler hiding core parts of the language. Just ``-cast the map to iterate it with for-of. – Kilves Aug 06 '18 at 06:24
  • 1
    Beware: I thought that @Kilves suggestion above to cast the Map to "any" was an elegant workaround. When I did it, the code compiled and ran without complaint, but the Map was not actually iterated -- the content of the loop never executed. The `Array.from()` strategy proposed here did work for me. – mactyr Nov 15 '18 at 23:04
  • I tried that too, it didn't work for me either, which is even stupider considering it *is* part of ES6 and should "just work" on most browsers. But I guess our angular overlords use some magic mumbo jumbo in zone.js to make it not work, because they hate ES6. Sigh. – Kilves Nov 16 '18 at 08:24
  • 1
    @Kilves `foreach` doesn't support fully async/await – Han Mar 12 '21 at 03:55
  • @Han I know, that's why I said for-of, not foreach. – Kilves Mar 16 '21 at 18:25
  • @Kilves yeah, maybe I've misread somewhere – Han Mar 18 '21 at 02:28
  • 1
    For me, `for (let key of myMap.keys())` worked both with `ES5` and `ES6`. No casting, no `Array.from()` used. – Janos Vinceller Jan 17 '22 at 17:26
48

This worked for me. TypeScript Version: 2.8.3

for (const [key, value] of Object.entries(myMap)) { 
    console.log(key, value);
}
Debashish Sen
  • 696
  • 5
  • 12
  • 10
    I got this to work by changing Object.entries(myMap) to just myMap.entries(). I like this answer because it avoids the error handling pitfalls of .forEach calls. – encrest Sep 21 '18 at 17:00
  • 4
    Worth noting that if your `target` in `tsconfig` is `es5` this throws an error, but with `es6` works correctly. You can also just do `for (const [key, value] of myMap)` when targeting `es6` – Benjamin Vogler May 07 '19 at 01:51
  • This is potentially problematic because keys will be treated as `string`, while it possible to have a Map with e.g. `number` keys. – Qumeric Aug 06 '22 at 03:38
  • This worked for me on `"typescript": "~4.7.4"` – EGC Feb 05 '23 at 07:32
41

Using Array.from, Array.prototype.forEach(), and arrow functions:

Iterate over the keys:

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

Iterate over the values:

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

Iterate over the entries:

Array.from(myMap.entries()).forEach(entry => console.log('Key: ' + entry[0] + ' Value: ' + entry[1]));
Tobias
  • 4,034
  • 1
  • 28
  • 35
  • 4
    Not sure why, I have the map like Map. none of the above methods worked except Array.from(myMap.values()).forEach(value => console.log(value));. – Rajashree Gr Apr 04 '18 at 21:55
26

Per the TypeScript 2.3 release notes on "New --downlevelIteration":

for..of statements, Array Destructuring, and Spread elements in Array, Call, and New expressions support Symbol.iterator in ES5/E3 if available when using --downlevelIteration

This is not enabled by default! Add "downlevelIteration": true to your tsconfig.json, or pass --downlevelIteration flag to tsc, to get full iterator support.

With this in place, you can write for (let keyval of myMap) {...} and keyval's type will be automatically inferred.


Why is this turned off by default? According to TypeScript contributor @aluanhaddad,

It is optional because it has a very significant impact on the size of generated code, and potentially on performance, for all uses of iterables (including arrays).

If you can target ES2015 ("target": "es2015" in tsconfig.json or tsc --target ES2015) or later, enabling downlevelIteration is a no-brainer, but if you're targeting ES5/ES3, you might benchmark to ensure iterator support doesn't impact performance (if it does, you might be better off with Array.from conversion or forEach or some other workaround).

Ahmed Fasih
  • 6,458
  • 7
  • 54
  • 95
16

This worked for me.

Object.keys(myMap).map( key => {
    console.log("key: " + key);
    console.log("value: " + myMap[key]);
});
Jason Slobotski
  • 1,386
  • 14
  • 18
  • 2
    With this the keys will always be strings though – User Jan 16 '19 at 17:51
  • ERROR!: this only works if you've NOT used myMap as a Map (e.g. with `myMap.set(key, value)`), but instead as an object (e.g. `myMap[key]=value`). That's also why you'll only see string keys. Prove it with `Object.keys(new Map([['a', 1], ['b', 2]]))` . – ericP Jan 26 '23 at 04:19
  • Tried this with your suggestion and this still works for me. – Jason Slobotski Jan 26 '23 at 20:57
  • Map's are assigned with `m.set(key, val)`; Objects are assigned with `o[key]=val`. Maps are also Objects, but so are e.g. dates (try `d=new Date(); d['a']=1; Object.keys(d)`). Initialize a Map: `m=new Map([['a', 1]])`, misuse it as an Object: `m['b']=2`, `[...m.keys()]` has key `a` from initialization; `Object.keys(m)` has `b` from its Object assignment. If you doing Object assignment, there's no reason to use a Map instead of just a `{}`. It won't give you the advantages of a Map (performant, non-string keys) and it will take a few more clock cycles to construct. – ericP Feb 02 '23 at 12:52
13

You can also apply the array map method to the Map.entries() iterable:

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

Also, as noted in other answers, you may have to enable down level iteration in your tsconfig.json (under compiler options):

  "downlevelIteration": true,
cham
  • 8,666
  • 9
  • 48
  • 69
  • This feature was introduced in TypeScript 2.3. The problem occured with TypeScript 1.8.10 – mwe Oct 28 '19 at 10:15
  • If you can't use "downlevelIteration", you can use: `const projected = Array.from(myMap).map(...);` – Efrain Feb 06 '20 at 09:32
9

I'm using latest TS and node (v2.6 and v8.9 respectively) and I can do:

let myMap = new Map<string, boolean>();
myMap.set("a", true);
for (let [k, v] of myMap) {
    console.log(k + "=" + v);
}
lazieburd
  • 2,326
  • 2
  • 14
  • 12
4

On Typescript 3.5 and Angular 8 LTS, it was required to cast the type as follows:

for (let [k, v] of Object.entries(someMap)) {
    console.log(k, v)
}
2

Just a simple explanation to use it in an HTML document.

If you have a Map of types (key, array) then you initialise the array this way:

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

And to iterate over it, you create an array from key values.

Just use it as an array as in:

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

Then, in HTML, you can use:

*ngFor="let key of keys"

Inside this loop, you just get the array value with:

this.cityShop.get(key)

Done!

Stephane
  • 11,836
  • 25
  • 112
  • 175
  • Just a small correction: the HTML you mean happen to be an Angular Template, not plain HTML. Please write Angular Template instead of HTML, because plain HTML does not understand *ngFor. – Janos Vinceller Jan 17 '22 at 17:14
1

I tried using Array.from( myMap.keys() ) to get an array of keys in my VSCode extension that's implemented using node.js, but it did not work, even after adding "downlevelIteration": true to my tsconfig.json with the target set to es2017 as suggested by Ahmed Fasih and others.

What ultimately worked was Jason Slobotski's suggestion to use Object.keys:

let keys = Object.keys(myMap);
Art Walker
  • 46
  • 2
1
const test_map=new Map();
test_map.set("hello",1);
test_map.set("world",2);
for (let it=test_map.entries(),val=(it.next().value);val!==undefined;val=(it.next().value)){
    console.log(val);
}
// ["hello", 1]
// ["world", 2]

See Map.prototype.entries()

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Apr 28 '23 at 03:23
-1

If you don't really like nested functions, you can also iterate over the keys:

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

Note, you have to filter out the non-key iterations with the hasOwnProperty, if you don't do this, you get a warning or an error.

peterh
  • 11,875
  • 18
  • 85
  • 108
  • how to iterate over map in html? it doesn't seem to work at all.
    {{key}}.
    – Shinya Koizumi Aug 31 '17 at 23:15
  • @powerfade917 It doesn't work, it works only for arrays because angular is a pile of trash. But ask this as a new question and so you will learn, that angular is not a pile of trash, but you have to convert it to an array. Note, also you are not the top of the top of the programming, because you are seemingly incapable to differentiate between angular and typescript. – peterh Dec 26 '17 at 15:47
-3
let map = new Map();

map.set('a', 2);
map.set('b', 4);
map.set('c', 6);
map.set('d', 7);

for(let key of map) 
{ 
    console.log(key[0], "----" key[1]) 
}

Output -

a ---- 2
b ---- 4
c ---- 6
d ---- 7
Atique Ahmed
  • 308
  • 4
  • 16