301

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?

2
  • 4
    See this answer from github github.com/Microsoft/TypeScript/issues/… Commented Jun 8, 2016 at 10:17
  • 2
    I made a silly mistake, in JS, don't do map["key"] = "value", but map.set("key", "value") Commented May 5, 2021 at 10:58

15 Answers 15

404

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);
});
Sign up to request clarification or add additional context in comments.

7 Comments

Just ran into this. Doesn't look like TypeScript is respecting the spec for map iteration, at least according to MDN which specifies a for-of loop. .forEach is sub-optimal, since you can't break it , AFAIK
dunno why its value then key. Seems backwards.
@PaulRooney it's like that probably to harmonize with Array.prototype.map.
How to stop this iteration process if we found what we need?
@Samjones Using return; in a foreach is the same as continue in a normal for loop
|
176

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

4 Comments

The es6 version given above is not compiling in strict mode.
this question is about typescript, not plain js
Why using "let"? Why not "const"? You won't change entry in most cases. "key" and "value" neither I guess.
@Stephane if you are not using that variable, you can replace it with _ to avoid compile-time errors.
63

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

7 Comments

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 <any>-cast the map to iterate it with for-of.
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.
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 foreach doesn't support fully async/await
For me, for (let key of myMap.keys()) worked both with ES5 and ES6. No casting, no Array.from() used.
|
52

This worked for me. TypeScript Version: 2.8.3

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

4 Comments

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.
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
This is potentially problematic because keys will be treated as string, while it possible to have a Map with e.g. number keys.
This worked for me on "typescript": "~4.7.4"
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]));

1 Comment

Not sure why, I have the map like Map<String, CustomeClass>. none of the above methods worked except Array.from(myMap.values()).forEach(value => console.log(value));.
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).

1 Comment

is this ok or dangerous to enable this when using Angular ?
16

This worked for me.

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

4 Comments

With this the keys will always be strings though
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]])) .
Tried this with your suggestion and this still works for me.
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.
14

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,

2 Comments

This feature was introduced in TypeScript 2.3. The problem occured with TypeScript 1.8.10
If you can't use "downlevelIteration", you can use: const projected = Array.from(myMap).map(...);
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);
}

6 Comments

Can you confirm that you first had to set "downlevelIteration": true in your tsconfig.json?
I do not have downlevelIteraton set, my target is es2017 however.
Can't i do this for Map<string, CustomClass[]> element ? compiler says it is not an array of type or string type.
this doesn't work for me on 2.8 - I get Type 'Map<K, V>' is not an array type or a string type.
This doesn't work for me. Any "lib" I should be using?
|
6

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 Comment

This got nothing to do with Angular though ;)
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!

1 Comment

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

Comments

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

1 Comment

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

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.

2 Comments

how to iterate over map in html? it doesn't seem to work at all. <div ng-repeat="(key, value) in model.myMap"> {{key}}. </div>
@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.
-2
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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.