Typescript: For...of with HTMLCollection

Created on 19 Oct 2015  ·  22Comments  ·  Source: microsoft/TypeScript

Hi,

Getting a compile error when trying to compile:

// ul is an HTMLElement
for (let child of ul.children) {
    //body
}

I get the following error: error TS2495: Type 'HTMLCollection' is not an array type or a string type.

Is there a way to get type support for this? Or is too much work to bother with?

Bug lib.d.ts Fixed

Most helpful comment

Yeah. The other thing you can do (if you promise not to mess with the body of the DOM in the loop) is:

for(let c of <any>bar) {}

All 22 comments

HTMLCollection is not an array.

https://developer.mozilla.org/en-US/docs/Web/API/NodeList#Why_can't_I_use_forEach_or_map_on_a_NodeList.3F

xLama, I am aware. However, the compiled code only needs an interface of .length, which means psuedoarrays should also be able to compile with the for..of syntax, right?

I think If ECMAScript does not support HTMLCollection like an array, TypeScript neither.

However ECMAScript 6 supports it, so I am agree.

That's a good point!

You can do this por the moment:

var foo:HTMLCollection;
var bar = [].slice.call(foo);
for (var g of bar){}

Yeah. The other thing you can do (if you promise not to mess with the body of the DOM in the loop) is:

for(let c of <any>bar) {}

Thanks! :)

ES6 does not support HTMLCollection on for-of loop as it is not iterable. NodeList is iterable and should be supported. Check #2695.

@SaschaNaz What about this? #2696
for-of only works on arrays for ES3/ES5 by design.

@xLama You are right. A limited number of iterable interfaces should be supported but currently not. #4947 is also related.

@SaschaNaz There is a problem. How do you want to iterate them when emit to ES3/5 without complicate it?

i think this is something that's missing from lib.dom.iterable.d.ts

i haven't tested other browsers but chrome and firefox both return a function (named values) for HTMLCollection.prototype[Symbol.iterator]

not that i know which one i should be reading but mdn provides some spec links for HTMLCollection

or is this something wrong with the spec? in "living standard" and "dom4" i can see NodeList has iterable<Node>; but HTMLCollection doesn't seem to have anything like that (is wanting to conveniently loop through an element's children really that uncommon?)

const myElement: Element = document.body;

const children: HTMLCollection = myElement.children;

// is this what i'm supposed to write?

for (const child of <IterableIterator<Element>> Array.prototype[Symbol.iterator].call(children)) {
    console.log(child);
}

// it's somehow even longer than the traditional ugly method

for (let i = 0; i < children.length; i++) { const child = children[i];
    console.log(child);
}

// this method is cheating but it also works:

interface HTMLCollection {
   [Symbol.iterator](): IterableIterator<Element>;
}

for (const child of children) {
    console.log(child);
}

Looking at this again, i believe the TS definitions do match the spec. NodeList is iterable, but HTMLCollection is not. and HTMLElement.children is an HTMLCollection see https://developer.mozilla.org/en-US/docs/Web/API/ParentNode/children.

If the spec is wrong, then we need to change it, but i do not think i have enough DOM expertise to say which is correct.

WebIDL says:

If the interface has any of the following:

  • an iterable declaration
  • an indexed property getter and an integer-typed attribute named “length”
  • a maplike declaration
  • a setlike declaration

then a property must exist whose name is the @@iterator symbol, with attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true } and whose value is a function object.

Chromium on April 2016 implemented and merged the change based on this criteria.

This means we can add [Symbol.iterator] key, and also means that TSJS-lib-generator can automatically add the key but without methods e.g. values().

Microsoft/TSJS-lib-generator#235 to fix this.

I see that PR was merged in May, but I still have this error with latest TS (2.4.2)

@felixfbecker have you tried turning on --downlevelIteration?

No, didn't know that existed, but as I understand it I don't need it with target: ES6. Here's the error:

[ts] Type must have a '[Symbol.iterator]()' method that returns an iterator

when I jump to definition the typings don't have the Symbol.iterator defined. The flag doesn't help :/

The PR https://github.com/Microsoft/TSJS-lib-generator/pull/235 added dom.es6.generated.d.ts but TS hasn't merged the file into the build process yet.

Is there a reason why not the MDN's example of for-of iterator is not working? I have a slight memory that it was working before:

var iterable = {
  [Symbol.iterator]() {
    return {
      i: 0,
      next() {
        if (this.i < 3) {
          return { value: this.i++, done: false };
        }
        return { value: undefined, done: true };
      }
    };
  }
};

for (var value of iterable) {
  console.log(value); // Error: is not an array type or a string type.
}

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of

Here is the code in playground:
http://www.typescriptlang.org/play/index.html#src=var%20iterable%20%3D%20%7B%0A%20%20%5BSymbol.iterator%5D()%20%7B%0A%20%20%20%20return%20%7B%0A%20%20%20%20%20%20i%3A%200%2C%0A%20%20%20%20%20%20next()%20%7B%0A%20%20%20%20%20%20%20%20if%20(this.i%20%3C%203)%20%7B%0A%20%20%20%20%20%20%20%20%20%20return%20%7B%20value%3A%20this.i%2B%2B%2C%20done%3A%20false%20%7D%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20return%20%7B%20value%3A%20undefined%2C%20done%3A%20true%20%7D%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%3B%0A%20%20%7D%0A%7D%3B%0A%0Afor%20(var%20value%20of%20iterable)%20%7B%0A%20%20console.log(value)%3B%0A%7D

You need to compile with --downlevelIteration when targeting ES3/ES5, which is not supported in the playground at the moment.

Worked like a charm, thanks!

Was this page helpful?
0 / 5 - 0 ratings