TypeError accessing port


#1

Hi,

I’m starting a new elm project (0.19) and am having a problem setting up ports out to JS. I have other projects that use ports fine, so this is driving me a little crazy… although it’s my first time using Browser.application so maybe I’m missing something.

I’ve stripped out Main.elm to be just a port module using the simple counter example - which compiles and functions as it should.

However, when I try and reference a port method on the JS side, I get:

TypeError: undefined is not an object (app.ports.cache)

If I change init from

init : Value -> Url -> Nav.Key -> ( Model, Cmd Msg )
init flags url navKey =
    ( 0, Cmd.none )

to call the port function

init : Value -> Url -> Nav.Key -> ( Model, Cmd Msg )
init flags url navKey =
    ( 0, cache "" )

then all is good, the cache port gets called on the JS side, and logging the port object to the console reports what would be expected: an object (cache) with subscribe and unsubscribe functions.

Why do I appear to need to call the port function (cache) in init in order to have access to the port on the JS side?

What am I missing (doing wrong)?

Thanks

Code below:

ELM:

port module Counter exposing (main)

import Browser
import Browser.Navigation as Nav
import Html exposing (Html, button, div, text)
import Html.Events exposing (onClick)
import Json.Decode exposing (Value)
import Url exposing (Url)


port cache : String -> Cmd msg



-- MODEL


type alias Model =
    Int


init : Value -> Url -> Nav.Key -> ( Model, Cmd Msg )
init flags url navKey =
    ( 0, Cmd.none )



-- UPDATE


type Msg
    = Increment
    | Decrement
    | Reset
    | ChangedUrl Url
    | ClickedLink Browser.UrlRequest


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        Increment ->
            ( model + 1, Cmd.none )

        Decrement ->
            ( model - 1, Cmd.none )

        Reset ->
            ( 0, Cmd.none )

        ChangedUrl _ ->
            ( model, Cmd.none )

        ClickedLink _ ->
            ( model, Cmd.none )


subscriptions : Model -> Sub Msg
subscriptions model =
    Sub.none



-- VIEW


view : Model -> Browser.Document Msg
view model =
    { title = "Counter"
    , body =
        [ div []
            [ button [ onClick Decrement ] [ text "-" ]
            , div [] [ text (String.fromInt model) ]
            , button [ onClick Increment ] [ text "+" ]
            , button [ onClick Reset ] [ text "reset" ]
            ]
        ]
    }



-- MAIN


main : Program Value Model Msg
main =
    Browser.application
        { init = init
        , onUrlChange = ChangedUrl
        , onUrlRequest = ClickedLink
        , subscriptions = subscriptions
        , update = update
        , view = view
        }

JS:

var elmAppContainerId = 'elm-app-container'
var elmAppContainer = document.getElementById(elmAppContainerId)

if (elmAppContainer)
  {
    var app = Elm.Counter.init({node: elmAppContainer, flags: {}})

    console.log(app.ports) // undefined
    
    app.ports.cache.subscribe( function() { console.log("Cached" );} ); // Results in a TypeError
  }
else
  {
    console.log("Can't find element with ID: " + elmAppContainerId)
  }

#2

Unused ports is considered dead code and is eliminated in Elm 0.19

Could that be what you are seeing?


#3

Yes you are right, thank you.

I guess I was jumping ahead with the JS side thinking just defining the port functions in Elm was enough. I’ve moved the call to cache into update and all is good again.

Thanks for the reminder - I won’t forget that again.


#4

On the js side, I tend to check that the ports are available like this:

app.ports && app.ports.textToSVG &&
  app.ports.textToSVG.subscribe(request => {
    ...
  }

Could probably be made a bit nicer if instead of silently failing when the port is not available, at least logging a warning to the console.