192
votes

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?

12
See this answer from github github.com/Microsoft/TypeScript/issues/… - yurzui
I made a silly mistake, in JS, don't do map["key"] = "value", but map.set("key", "value") - Ben Butterworth

12 Answers

276
votes

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);
});
82
votes

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];
}
45
votes

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);
}
35
votes

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]));
35
votes

This worked for me. TypeScript Version: 2.8.3

for (const [key, value] of Object.entries(myMap)) { 
    console.log(key, value);
}
19
votes

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

12
votes

This worked for me.

Object.keys(myMap).map( key => {
    console.log("key: " + key);
    console.log("value: " + myMap[key]);
});
8
votes

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);
}
7
votes

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,
4
votes

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)
}
1
votes

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!

-1
votes

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.