Search code examples
castingscala.jstype-safetyobject-literal

In Scala.js, how to best create an object conforming to a trait from a JS façade?


Suppose I have this definition in Scala.js for the return type of a function of some library I'm using:

@native
trait Link extends Object {
  val href: String = native
  val title: String = native
}

What's the best, typesafe way, in Scala code, to define an object literal conforming to this? I thought the use(...).as[...] trick would work:

val link = js.use(new {
  val href = "value1"
  val title = "value2"
}).as[Link]

But this yields the error:

AnyRef{val href: String; val title: String} does not export a getter href: String

Why?

I also tried this, and as expected it fails:

val link = js.use(js.Dynamic.literal(
  href = cap(2),
  title = cap(3)
)).as[Link]

gives

scala.scalajs.js.Object with scala.scalajs.js.Dynamic does not have a getter href: String

Finally, I also tried this:

val link = new Link {
  override val href = "value1"
  override val title = "value2"
}

and got

A Scala.js-defined JS class cannot directly extend a native JS trait

Right now, I'm doing this:

val link = js.Dynamic.literal(
  href = "value1",
  title = "value2"
).asInstanceOf[Link]

This works, but it's not checked by the compiler if I'm not mistaken.


Solution

  • The library should declare Link as @ScalaJSDefined instead of @js.native. That would allow you to do the third variant (the one with new Link { ... }. I suggest you file a bug report for that library.

    As a workaround, either do what you're doing with the .asInstanceOf[Link], or, if you want the js.use(x).as[T] safety, use this:

    @ScalaJSDefined
    trait LinkImpl extends js.Object {
      val href: String
      val title: String
    }
    
    val link = js.use(new LinkImpl {
      val href = "value1"
      val title = "value2"
    }).as[Link]