Svelte - Tutorial Walkthrough, Part 2

Svelte Tutorial, Part 2 - Logic & Events

This is the 2nd post in a series walking through Svelte’s online tutorial. In the first post we covered the Introduction, Reactivity, and Props. This post will continue where we left off, exploring Logic and Events. With these in your toolbelt, you’ll be further down the Svelte path, well on your way to creating something useful!

The imaginary Svelte ninja master sits atop a large boulder, observing your progress. When you completed part 1, his eyebrows raised the most imperceptable of amounts. He is impressed with your progress, and momentarily lost the total control he maintains over every aspect of his being. Let’s see if we can continue to reward his tutelage with acquired knowledge, starting with “Logic”…

Logic

Logic provides the mechanism for “making decisions”, based on values stored in variables. This includes if/else clauses (eg, “Only show this if XYZ is true!”), as well as loops (eg, “Build a list from this collection!”).

If/Then/Else

Ahhh, good old “if, then, else” - where would we be without you? What would the programming world look like if we couldn’t dream….”if”. This is all straightforward within Svelte.

For example, show elements based on a bool variable:

Simple If/Else Logic Example

<script>
	let errorText = 'You done messed up';
</script>
{#if errorText.length > 20}
	<div class="error">
		{errorText}, Big Time!
  	</div>
{:else if errorText}
	<div class="error">
		{errorText}
  	</div>
{:else}
	<div>
		All is Well!
	</div>
{/if}

The previous example illustrates a few additional reserved characters Svelte uses as part of it’s magic:

Block reserved characters

# - "Block Opening Tag"
/ - "Block Closing Tag"
: - "Block Continuation Tag"

Each

“Each” blocks are another straightforward concept - they allow us to build UI elements from arrays. Here’s a stupid example:

Stupid "Each" Logic Example

<script>
	let moods = [
		{ score: 10, name: 'Happy' },
		{ score: 1, name: 'Sad' },
		{ score: 5, name: 'Indifferent' }
	];
</script>
<ul>
	{#each moods as mood, i}
		<li>{i}: {mood.name}, Score: {mood.score}</li>
	{/each}
</ul>

Note the syntax above gives us access to the current iteration index, “i” - this is optional, but can be useful.

Iteration Index

	{#each moods as mood, i}

Additionally, you can use destructuring to assign member variables directly into variables. This example does so with the “score” and “name” properties of the “mood” objects:

Destructuring Example

<script>
	let moods = [
		{ score: 10, name: 'Happy' },
		{ score: 1, name: 'Sad' },
		{ score: 5, name: 'Indifferent' }
	];
</script>
<ul>
	{#each moods as {score, name}, i}
		<li>{i}: {name}, Score: {score}</li>
	{/each}
</ul>

Keyed Each Blocks

Keyed Each blocks warrant their own section. In order to uniquely identify items within a list, and reliably tie them back to their respective DOM elements, Svelte supports a “keyed each”. Long story short, it assigns a unique identifier during the buildout of the “each” block, and uses it for item->DOM association. Sort of hard to explain, kinda hard to understand. But take a look at their example to see it in action.

Await Blocks

Svelte provides a nifty concept called “Await Blocks” to build a UI that handles asyncronous (async) execution automatically. This can be used to provide a good user experience while waiting on API calls to complete.

But first, a brief segue into async programming…

Very Brief Explanation of Async and Promises

Promises are the core building block of asyncronous programming within Javascript. They allow code to perform long running operations (such as retrieving a value from an external API), without blocking other execution.

As a simple example, consider a page loading that requires 5 API requests to retrieve data. In a syncronous programming model, these would happen one after the other, ie:

Call1->Response 
               Call2->Response 
                              Call3->Response 
                                             Call4->Response 
                                                            Call5->Response

This is inefficient, because the CPU largely sits idle while waiting for each call to return. With async programming, we can launch all the calls at once, and wait for them all to return. So it might look something like this:

Call1->   Response 
Call2->Response 
Call3->            Response 
Call4->  Response 
Call5->      Response

The effect is we reduce the overall time the code is waiting for these calls to return. The benefit - faster overall execution, and snappier UIs!

Svelte Await Blocks

Svelte’s Await Blocks provide a powerful template-based mechanism for launching an async operation, awaiting its return, and displaying the result.

Here’s a simple example:

Await Block Example

<script>
	async function makeNetworkCall() {
		// long running network call
	}

	let promise = makeNetworkCall();
</script>

{#await promise}
	<p>Loading</p>
{:then response}
	<ResponseView response={response}></ResponseView>
{:catch error}
	<ErrorView error={error}></ErrorView>
{/await}

This is a very simple example, and some promise-based async logic may be too complex to fit neatly into this language feature. But for simple scenarios, await blocks offer an easy mechanism for creating components that are reactive to long running calls. Very cool.

Events

Event Driven Programming is a concept widely used in UI programming (and beyond). In a nutshell, it means when “something happens” (a user clicks, layout of screen completes, a timer clicks) the runtime will trigger an event. Code in your application can then respond to (or “handle”) these events (for example, executing a web call when the user clicks “Submit”).

The following sections walk through how Svelte models and handles events.

DOM Events

DOM events are built into the web browser’s javsacript engine, and provide feedback when things happen on the screen. In it’s simplest form, Svelte provides direct support for these events.

Here’s an example of reacting to mouse movement:

DOM Event Example

<script>
	let isMouseOver = false; 

	function handleMouseOver(event) {
		isMouseOver = true;
	}
</script>
<div on:mouseover={handleMouseOver}>
	The mouse is over me: {isMouseOver}
</div>

Inline Handlers

If the logic required by your handler isn’t complicated, you might consider an inline handler.

Here’s an example from the Svelte documentation:

Inline Handler Example

<div on:mousemove="{e => m = { x: e.clientX, y: e.clientY }}">
	The mouse position is {m.x} x {m.y}
</div>

Worth repeating a tidbit the Svelte documentation mentions around performance - with some reactive frameworks, inline handlers can introduce performance issues, especially when used within loops. That doesn’t apply to Svelte. Because Svelte is compiled ahead of time, the compiler will “always do the right thing”.

Event Modifiers

Occasionally DOM events can introduce problems…almost like they do their job TOO well. A common example is when something is clicked multiple times - without special logic, the event handling code executes multiple times. This can result in duplicate entries in your backend, conflict exceptions, etc.

Svelte provides event modifiers to alleviate or prevent DOM event-related issues. Here’s the example from their documentation illustrating how to prevent double-clicking issues with their “once” modifier:

Inline Handler Example

<script>
	function handleClick() {
		alert('no more alerts')
	}
</script>

<button on:click|once={handleClick}>
	Click me
</button>

The Svelte documentation contains the complete list of available modifiers. This is an area you can peruse at a high level initially. Then, once comfortable with the framework, return and spend some time diving deeper to solidify your understanding.

Component Events

In addition to built-in DOM events, Svelte allows components to raise events. Events enable components to communicate actions/behavior to the “wider world” (other components, the framework itself, etc).

Here’s an example based on the example Svelte provides, and it illustrates an important nuance that escaped me initially:

  • the first parameter in the dispatch method is the name of the event
  • the parent component must subscribe to “on:<eventName>”.

This is obvious as I type it, but my brain initially treated “message” as a framework keyword. Took a few minutes of hammering to figure out what I was doing wrong!

Child.svelte

<script>
	import { createEventDispatcher } from 'svelte';

	const dispatch = createEventDispatcher();

	function trigger() {
		dispatch('triggered', {
			payload: {}
		});
	}
</script>

Parent.svelte

<script>
	import Child from './Child.svelte';

	function handleChildTrigger(event) {
		// event.detail carries the payload
		console.log(event.detail);
	}
</script>

// "on:triggered" is corresponding to the "triggered" string the child component passed to dispatch function.
<Child on:triggered={handleChildTrigger}/>

Event Forwarding

By default, Svelte will only raise a Component event to the immediate higher level. This is different than DOM events, which will bubble the whole way up the hierarchy (unless you use the stopPropagation modifier!). When you have multiple levels of nested components, you’ll need to raise the events at each level.

This requires cumbersome boilerplate logic, but thankfully Svelte provides the on:message shorthand. With this, intermediate components will simply raise the event to the next level, where it may or may not be handled:

on:message

<IntermediateComponent on:message/>

Easy peasey.

DOM Event Forwarding

Svelte also supports DOM event forwarding. Similar to Event Forwarding, this applies to the built in DOM events. As an example, their documentation illustrates a “custom button”. It it’s own styling and hover behavior, but we still want it to raise the “on:click” DOM event for anyone consuming it. By declaring the event (without a handler) on the button element, Svelte will raise to parent components:

DOM Event Forwarding

<button on:click>
	Submit
</button>

Summary

OK, we’ve covered logic and events, and now it is time to pause. Up next is bindings, and there’s alot of ground to cover there.

For now, continue playing in the Svelte tutorial and see these concepts in action for yourself. You might also consider creating a simple/stupid project to play on your own - Svelte’s quickstart makes that a painless exercise. If you want to jump ahead a bit and add bindings or stores, that’s perfectly fine. Their tutorial makes it easy to learn, and we’ll dig into those in just a bit here.

Hopefully you’re enjoying the idea of what Svelte offers, and looking forward to more. The imaginary, unsanctioned Svelte ninja certainly approves. You wouldn’t know it by looking at his stoic, granite exterior, but he’s been observing your progress. As you learned logic and events, his heart glowed with love. A teacher is only as good as their students, and he was watching another begin to flap their wings. With focus and dedication, you will soon learn to fly!

Thoughts & Notes

Written on December 18, 2020