144

Here's the code:

const test = Array.from(document.getElementsByClassName('mat-form-field-infix'));
test.forEach((element) => {
    element.outerHTML = '<div class="good-day-today" style="width: 0px;"></div>'; // Please note that this line works fine!
    element.style.padding = '10px';
    element.style.borderTop = '0';
});

Error I get when compiled:

ERROR in src/app//.component.ts(101,21): error TS2339: Property 'style' does not exist on type 'Element'. src/app//.component.ts(102,21): error TS2339: Property 'style' does not exist on type 'Element'.

How can I fix it?

I tried to remove the Array.from... part, tried to use for of and for in, tried as any, but above is the way I have to do it.

4
  • 19
    You just need to cast it as HTMLElement (element: HTMLElement). Not sure why you need array.from though.
    – Chris W.
    Commented Nov 8, 2019 at 20:56
  • as any or any other as or any would not be allowed :( Commented Nov 8, 2019 at 20:57
  • @ChrisW., error TS2315: Type 'HTMLCollection' is not generic. Commented Nov 8, 2019 at 21:13
  • Ah right, I didn't take the time to check anything, it's just a matter of casting though like jonas has.
    – Chris W.
    Commented Nov 8, 2019 at 21:20

8 Answers 8

168

You need a typecast:

Array.from(document.getElementsByClassName('mat-form-field-infix') as HTMLCollectionOf<HTMLElement>)

That's because getElementsByClassName only returns HTMLCollection<Element>, and Element does not have a styleproperty. The HTMLElement however does implement it via it's ElementCSSInlineStyle extended interface.

Note that this typecast is typesafe in the way that every Elementis either a HTMLElement or an SVGElement, and I hope that your SVG Elements don't have a class.

4
  • 2
    I had to use HTMLCollectionOf<HTMLElement> to make this work. Commented Nov 8, 2019 at 21:04
  • 2
    @JakeHolzinger totally right, was busy searching the reason for having Element and HTMLElement ... Commented Nov 8, 2019 at 21:06
  • 1
    Only solution worked for me :) Commented Feb 15 at 15:52
  • Assignment to "style" impossible due to being readonly... Commented Apr 8 at 17:04
158

Another option is to use querySelectorAll and a type parameter. getElementsByClassName is not generic, but querySelectorAll is - you can just pass the type of the elements that will be selected, like this:

const test = document.querySelectorAll<HTMLElement>('.mat-form-field-infix');

This doesn't require any type casting, and it will allow you to use forEach immediately, rather than converting it to an array first. (getElementsByClassName returns an HTMLCollection, which does not have a forEach method; querySelectorAll returns a NodeList, which does have a forEach method, at least on somewhat up-to-date browsers. To support ancient browsers too, you'll need a polyfill, or convert it to an array first.)

If you happen to just need a single element, you can use querySelector, which is generic as well:

const elm = document.querySelector<HTMLElement>('.foo')!;
elm.style.padding = '10px';

Another benefit of querySelectorAll (and querySelector) over the many other options is that they accept CSS selector strings, which can be far more flexible and precise. For example, the selector string

.container > input:checked

will select children of <div class="container"> which are <input>s and are checked.

1
  • ` as NodeListOf<HTMLDivElement>` will also work with querySelectorAll
    – Tyler V.
    Commented Aug 23, 2023 at 22:11
5

A workaround could be doing something like this:

element["style"].padding = '10px';
element["style"].borderTop = '0';

Maybe it's not the best solution, but it should work, I used it multiple times :)

1
  • 4
    In this case it says: Element implicitly has an 'any' type because expression of type '"style"' can't be used to index type 'Node'. Property 'style' does not exist on type 'Node'.ts(7053) - using eslint-plugin v 5.2.0
    – Panossa
    Commented Mar 17, 2022 at 17:47
4

Maybe this could help:

let element = <HTMLElement> document.getElementsByClassName(className)[0];
1
  • 1
    That worked for me in the following example: const thename = <HTMLElement> document.querySelector(".thename");. The following alternative also worked: const thename: HTMLElement = document.querySelector(".thename");
    – Will
    Commented May 30, 2023 at 17:13
3

While I suspect that type casting will not cause any problems in this case, I would still avoid type casting whenever possible. In this case, you can avoid type casting with instanceof narrowing:

const test = Array.from(
  document.getElementsByClassName('mat-form-field-infix')
)
test.forEach((element) => {
  if (!(element instanceof HTMLElement)) {
    throw new TypeError(`Expected a object of Type HTMLElement`)
  }

  element.outerHTML =
    '<div class="good-day-today" style="width: 0px;"></div>' // Please note that this line works fine!
  element.style.padding = '10px'
  element.style.borderTop = '0'
})

2
  • While it's good to guard against getting potentially bad code, this check really just exists to satisfy a typescript complaint and the negative case is basically impossible. Which means you're running extra unneeded code. And if the negative case did ever occur, you silently ignore the problem. Imho it's better to cast, get an exception and get good error logging in place... but realistically this is just unused code.
    – Evert
    Commented Nov 2, 2023 at 2:13
  • 2
    I agree with you that it is not good to simply return so I updated the answer to trow an Error instead. But I disagree with you that this is better to cast. Yes, you have some unneeded code which makes the code slightly slower but this is so small that it is negligible. But if you sometimes cast and sometimes guard, it makes it harder for inexperienced developers to acquire best practices - which is usually "guard" instead of "cast". So as simple rule I would say: "always use type guards instead of casting except it is not easily possible".
    – Jonathan
    Commented Nov 2, 2023 at 15:49
2

When you set the outerHTML, you're destroying the original element that was there. So, your styling doesn't work.

You'll notice that if you change it to set innerHTML, your styling does work.

This does not do the same exact thing, but I hope it points you in the right direction.

const test = Array.from(document.getElementsByClassName('mat-form-field-infix'));
test.forEach((element) => {
    element.innerHTML = '<div class="good-day-today" style="width: 0px;"></div>'; // Please note that this line works fine!
    element.style.padding = '10px';
    element.style.borderTop = '0';
});
1
  • Those are different elements Commented Nov 8, 2019 at 21:48
2

I was also facing a similar type of issue while doing

document.querySelectorAll(".<className>"); 

so instead of adding the style property, I simply got around it by just adding another class.

example:

//css file
    .<classname> {
        display: none;
      }
    .<classname>.show {
        display: flex;
      }

//ts file
elements.forEach((ele, index) => {
const errors = something.length;
if (index < errors) {
  ele.classList.add("show");
} else {
  ele.classList.remove("show");
}

}); };

2

I think I found a way easier method:

Just create an index.d.ts file and add:

interface Element {
    style: CSSStyleDeclaration
}
1
  • 1
    But this will incorrectly tell typescript that all Elements have a style property. HTMLElement is only one type of Element. Commented Mar 10 at 3:57

Not the answer you're looking for? Browse other questions tagged or ask your own question.