0

I'm using SvelteKit to build an app, and I import an external script (Apple's MapKit JS) in my app.html like this:

<script src="https://cdn.apple-mapkit.com/mk/5.72.88/mapkit.js"></script>

I then have a Map.svelte component that loads a point on a map using my own Airport object. I have omitted extra details that aren't relevant with ...:

// === Map.svelte ===

<script lang="ts">
import { onMount } from 'svelte'
import type { Airport } from '$lib/data/typesAirports'

export let airport:Airport
let mapWrap:HTMLDivElement
let map: mapkit.Map

onMount(() => {
  setupMap(airport)
})

function setupMap(airport){
  //Setup my mapkit object with my token
  mapkit.init(...)

  //Set my map instance
  map = new mapkit.Map(mapWrap, {...})

  //Create pin on map using airport latitude and longitude
  const annotation = new mapkit.MarkerAnnotation(...airport latitude & longitude...)
  map.addAnnotation(annotation)
}
</script>

<!-- Map in HTML -->
<div bind:this={mapWrap}></div>

So far, this works great. But when I change my airport property in the parent component:

//=== Parent.svelte ===

<Map airport={airport} />

...there is no reactivity in the Map component. My suspicion is this is because things that happen in onMount are excluded from that reactivity, but I'm unclear on how that works.

If I try something like this, I get an error that airport is undefined:

$: setupMap(airport)

How can I preserve reactivity in a component like this that relies on an external JS library? Or more specifically, how do I get my map to reload when the airport changes?

1 Answer 1

3

onMount happens once, when the component mounted to the DOM. The approach using a reactive statement is the correct one.

If a property can be uninitialized/undefined you can simply guard against that using an if statement:

$: if (airport) setupMap(airport)

Regarding the loading of external scripts:

I would load them where they are used, ideally using a dynamic import(); this only works for modules though. Imports like this can be awaited in async functions or via the {#await} directive. REPL example

For non-modules a <script> tag can be added dynamically and the load event can be used to signal a component that the script can now be used. REPL example

A <link rel="prefetch" ...> can be used to speed up the loading of these resources. (More on prefetching)

Sign up to request clarification or add additional context in comments.

7 Comments

Yeah, I had tried guarding against airport being undefined, but then mapkit is not defined. I assume this is because mapkit is defined globally and somehow isn't available on the window object yet when setupMap() is called.
Ah, looks like I just need to do a browser check as well and firing setupMap fires. It was trying to load it on the server side.
Generally I would not recommend global imports, but instead load the script only where it is needed. If the script is exposed as a module, it can even be loaded with a dynamic import() statement and be awaited inside an async function. For global <script> tags you can use the load event and e.g. re-dispatch a custom event or set an attribute on the window which can be used in components.
Here is an example. The REPL will automatically preload the script, but in Svelte Kit this will start the request to load the script when the preview is first enabled if there is no prefetching going on. If the imported resource references an NPM module, it will have to be installed of course (the REPL pulls them from unpkg.com upon compilation).
Apple MapKit does not appear to be available as a module, so you have use a <script> tag. Here would be an example of adding one dynamically.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.