Sam
Sam

Reputation: 1828

Typescript,'NodeListOf<Element>' is not an array type or a string type

Converting my JS to TS strict mode.

The following syntax looks fine to me but TS is complaining in the for loop on allSubMenus with:

[ts] Type 'NodeListOf<Element>' is not an array type or a string type.

What am I missing?

function subAct(target:Node){

  const allSubMenus : NodeListOf<Element> = document.querySelectorAll('.subMenuItems') 

  for (const sub of allSubMenus){
    sub.classList.remove('active')
  }  
}

Upvotes: 45

Views: 49848

Answers (7)

Maciej Kravchyk
Maciej Kravchyk

Reputation: 16607

This assumes you want to keep ES5 target (if not, upgrading it to ES6 will solve the issue as well).

  1. Use for loop
function subAct(target:Node){
  const allSubMenus = document.querySelectorAll('.subMenuItems');

  for (let i = 0; i < allSubMenus.length; i += 1){
    const sub = allSubMenus[i];
    sub.classList.remove('active')
  }  
}
  1. Use Array.from

In order to use this, you have to add ES2015.core to compilerOptions.lib and add polyfill for Array.prototype.from Note that this will loop on the collection twice - the first method is better.

function subAct(target:Node){
  const allSubMenus = Array.from(document.querySelectorAll('.subMenuItems'));

  for (const sub of allSubMenus){
    sub.classList.remove('active')
  }  
}

Upvotes: 1

John
John

Reputation: 3535

According to your typescript target compiler, parsing error can be occured.

The for-of loop, introduced in the sixth edition of EcmaScript (ES6). Thus, old browsers' JS engine can not understand for-of loop syntax. https://hacks.mozilla.org/2015/04/es6-in-depth-iterators-and-the-for-of-loop/

To solve this issue,

if you support latest modern browsers(>=ES6) only

change your TS target

//tsconfig.json
{
  "compilerOptions": {
    "target": "es6" //above es6 like ES2015,ES2018 ...
  }
}

if you support old browsers(<=ES5)

I presume that you are using next environment.

//.tsconfig.json
{
  "compilerOptions": {
    "target": "es5"
  }
}
  1. To keep for-of loop, use as any syntax

Note: "as any" will cast collection(Objects) to array and this will affect some type features within "for" scope.

//.ts
const allSubMenus : NodeListOf<SpecifiedElement> = document.querySelectorAll('.subMenuItems') 
    
for (const sub of allSubMenus as any){ // then will pass compiler
  sub.classList.remove('active')
}  

The above TS script will be compiled to

//.js output
var allSubMenus = document.querySelectorAll('.subMenuItems');

for (var _a = 0, _b = forms; _a < _b.length; _a++) {
    var sub = _b[_a];
    sub.classList.remove('active');
}

https://stackblitz.com/edit/node-ywn1bq?file=main.js

  1. Use classic for-loop syntax
const allSubMenus : NodeListOf<SpecifiedElement> = document.querySelectorAll('.subMenuItems') 

for (let i = 0; i < allSubMenus.length; i++) { 
  allSubMenus[i].classList.remove('active'); 
}    

<Element>

In addition to the above, to avoid the following warning,

Property '<property name>' does not exist on type 'Element'

you may specify <Element> if you know element type and type define exists.

//for example,
NodeListOf<Element>  => NodeListOf<HTMLFormElement>

ECMAScript(ES) history

https://codeburst.io/javascript-wtf-is-es6-es8-es-2017-ecmascript-dca859e4821c

Upvotes: 41

Anisha Agarwal
Anisha Agarwal

Reputation: 56

Set "downlevelIteration": true in compilerOptions in your tsconfig.json file.

From https://www.typescriptlang.org/tsconfig#downlevelIteration

Downleveling is TypeScript’s term for transpiling to an older version of JavaScript. This flag is to enable support for a more accurate implementation of how modern JavaScript iterates through new concepts in older JavaScript runtimes

Upvotes: 2

amdev
amdev

Reputation: 7442

while compiling your code you may choose target: ES2017 or target: ES2015. also you can set this property in tsconfig.json file unser compilerOptions.

So here is the command line code for that:

npx tsc path/to/file.ts --target ES2015

TIP: if you are using babel along side with typescript, it is totally recommended that you always make typescript to compile to latest version of Javascript, and then let babel handles rest of transpiring process. With this technique you add another lever of assurance of supportive level to older browsers since typescript is not ok to compile the code that would run in for example ie6; so babel comes to rescue here and make you yo make sure that your js code would run in even ie < 9 with it's helpful polyfills and other mechanisms that it takes to work!

So keep in mind that:

Always let typescript compiles your code to latest javascript ( by setting target: ES2017 ) and let babel transpiles your js code to support older browsers ( separate concerns properly and let each one does the related job).

Upvotes: 1

HenriTel
HenriTel

Reputation: 312

You can iterate over a NodeListOf with the forEach method.

const allSubMenus : NodeListOf<Element> = document.querySelectorAll('.subMenuItems') 
allSubMenus.forEach(sub => sub.classList.remove('active'))

Source: https://microsoft.github.io/PowerBI-JavaScript/interfaces/_node_modules_typedoc_node_modules_typescript_lib_lib_dom_d_.nodelistof.html#foreach

Upvotes: 1

BonjourMonde
BonjourMonde

Reputation: 114

You could try

const allSubMenus : NodeListOf<Element> = document.querySelectorAll('.subMenuItems') 
Array.from(allSubMenus, subMenu => {/* */})

Upvotes: 8

Matt McCutchen
Matt McCutchen

Reputation: 30919

You need to set the target compiler option to es6 or higher for NodeListOf<T> to be iterable.

Upvotes: 44

Related Questions