Using a Custom Element is the best way to do this (if your usersâ browsers support them), so youâre already on your way. When it comes time to communicate between your Ember components and your Elm program you can:
- Accept information from Elm using a getter and setter for a property. Youâve already gotten this working.
- Notify Elm of changes by triggering a custom event on your element
I found the x-tag documentation difficult to follow, but I can share how to do it using the Custom Elements v1 API and your experience with x-tag will let you translate. The example is probably going to retread what youâve already done to send stuff into the Ember program, but the interesting thing I think is triggering events from the custom element to notify Elm of changes.
JavaScript:
customElements.define('ember-program', class FancyButton extends HTMLElement {
constructor() {
super()
this._emberApp = null
this._model = null
}
get model() {
return this._model
}
set model(value) {
this._model = value
if (!this._emberApp) return
// however you actually send a value to an ember program
this._emberApp.setModel(value)
}
connectedCallback() {
// however you actually start an ember program
// where `this` is the root element and `this._model` is the initial state
this._emberApp = startEmberApp(this, this._model)
// notify the Elm program when ever something interesting happens in your
// app using whatever notification system Ember provides
this._emberApp.on('changed', () => {
this.dispatchEvent(new CustomEvent('changed'))
})
}
disconnectedCallback() {
this._emberApp.destroy() // or whatever it actually is
}
})
Elm:
module EmberApp exposing (view, Model)
import Json.Decode as Decode exposing (Decoder)
import Json.Encode as Encode exposing (Value)
import Html exposing (Html)
import Html.Attributes as Attributes
import Html.Events as Events
type alias Model =
{ whatever : String }
encoder : Model -> Value
encoder model =
Encode.object [ ("whatever", Encode.string model.whatever ) ]
decoder : Decoder Model
decoder =
Decode.map Model
(Decode.field "whatever" Decode.string)
view : (Model -> msg) -> Model -> Html msg
view onChange model =
Html.node "ember-program"
[ Attributes.property "model" <| encoder model
-- in the decoder, `target` is the element instance and `model` is the
-- getter for the model that we set up in JS
, Events.on "changed" <|
Decode.map onChange (Decode.at [ "target", "model" ] decoder)
]
[]
The most common pushback I see about this technique is that people expect that the encoding of the model on every render should be slow if your model is really big. There is no reason this should be true by default! It needs to be measured. If profiling shows that encoding is your bottleneck, then some optimizations you can explore are:
- Use
Html.Lazy if your Ember appâs model is not constrructed inline, and can live in your Elm programâs model as-is
- Track changes to the model with a counter and use that counter as the key in
Html.Keyed