3
votes

I have a function that takes an element on the page and adds CSS to its style attribute. I want the argument that is passed to it to be an object with keys such as height, minWidth, flexDirection, etc.

function addStyle (el: HTMLElement, style: Style): void {
  for (const property in style) {
    el.style[property] = style[property]
  }
}

My problem is in typing this object. It's obviously not feasible to define every single CSS property myself. I'm pretty sure that TypeScript should be able to do this itself, I'm just not sure how. This is my best guess:

type Style = Partial<CSSStyleDeclaration>

...but that results in the error "Type 'string | undefined' is not assignable to type 'string'": TypeScript playground

That specific error can be easily overridden, but I'm wary that it indicates I'm doing something wrong or unintuitive.

Is there a standard way to type an object that contains CSS properties?

2
If you remove the partial, this works just fine.dwjohnston
@dwjohnston But that would assert that my construced object contains not only all the CSS styles but also the methods that are attached to a HTMLElement's style: developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclarationsnazzybouche

2 Answers

1
votes

I don't think there's a good way to do this sort of thing, When typing a property that may or may not exist on an object, TypeScript does not differentiate between the property existing and containing the value undefined, and the property not existing at all.

For example, disregarding styles and everything else, consider typing an object which may have the foo property which is a string. You can't do { foo: string } because the property may not exist. You can do { foo: string | undefined }. You can also do { foo?: string }, but this type is identical to the previous one. I don't think there are any other options: you can't say "This property may or may not exist, but if it does exist, it does not contain undefined."

A CSSStyleDeclaration is the same sort of situation, except with lots more properties. Partial<CSSStyleDeclaration> is the right way to go, but unfortunately, since TypeScript won't distinguish between partial types having properties containing undefined and not having those properties at all, you have to assert that the value does exist, if dealing with an individual value

But, there's another way to bypass having to reference and assert individual values, which is to use Object.assign:

type Style = Partial<CSSStyleDeclaration>;
function addStyle(el: HTMLElement, style: Style) {
    Object.assign(el.style, style);
}

(also remember that there's no need to specify the return type of a function if TS can determine it automatically - less code to read and write is usually a good thing)

0
votes

Use the non-null assertion operator that is here exactly for your use case. adding exclamation mark

type Style = Partial<CSSStyleDeclaration>

 function addStyle (el: HTMLElement, style: Style): void {
   for (const property in style) {
     el.style[property] = style[property]!;
   }
 }

Or add || ''

type Style = Partial<CSSStyleDeclaration>

function addStyle (el: HTMLElement, style: Style): void {
  for (const property in style) {
    el.style[property] = style[property] || '';
  }
}