A quick introduction to Svelte from an Angular developer perspective

Svelte is a relatively new UI framework developed by Rich Harris , who is also the author of the Rollup builder. Most likely, Svelte will seem completely different from what you have dealt with before, but perhaps this is even good. The two most impressive features of this framework are speed and simplicity. In this article, we will focus on the second.













Since my main development experience is related to Angular, itโ€™s natural that Iโ€™m trying to learn Svelte by copying the approaches that are already familiar to me. And this is exactly what this article will be about: how to do the same things in Svelte as in Angular.







Note: Despite the fact that in some cases I will express my preference, the article is not a comparison of frameworks. This is a quick and easy introduction to Svelte for people who already use Angular as their main framework.







Spoiler alert : Svelte is fun.







Components



In Svelte, each component is associated with the file where it is written. For example, the Button



component will be created by naming the Button.svelte



file. Of course, we usually do the same in Angular, but with us it's just a convention. (In Svelte, the name of the imported component may also not coincide with the file name - note by the translator)







Svelte components are single-file, and consist of 3 sections: script



, style



and a template that does not need to be wrapped in any special tag.







Let's create a very simple component that shows "Hello World".







hello_world







Component Import



In general, this is similar to importing a JS file, but with a couple of caveats:









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





From the above snippets, itโ€™s obvious that the number of lines for creating a component in Svelte is incredibly small. Of course, there are some implicitities and limitations, but at the same time everything is simple enough to quickly get used to it.







Basic syntax



Interpolation



Interpolations in Svelte are more similar to those in React than in Vue or Angular:







 <script> let someFunction = () => {...} </script> <span>{ 3 + 5 }</span> <span>{ someFunction() }</span> <span>{ someFunction() ? 0 : 1 }</span>
      
      





Iโ€™m used to using double curly braces, so Iโ€™m sometimes sealed up, but maybe I only have this problem.







Attributes



Passing attributes to components is also quite simple. Quotation marks are optional and any Javascript expressions can be used:







 //Svelte <script> let isFormValid = true; </script> <button disabled={!isFormValid}></button>
      
      





Developments



The syntax for event handlers is: on:={}



.







 <script> const onChange = (e) => console.log(e); </script> <input on:input={onChange} />
      
      





Unlike Angular, we do not need to use parentheses after the function name to call it. If you need to pass arguments to the handler, just use the anonymous function:







 <input on:input={(e) => onChange(e, 'a')} />
      
      





My view on the readability of such code:









Structural directives



Unlike structured directives in Vue and Angular, Svelte offers a special syntax for loops and branches inside templates:







 {#if todos.length === 0}    {:else} {#each todos as todo} <Todo {todo} /> {/each} {/if}
      
      





I love. No additional HTML elements are needed, and in terms of readability, this looks amazing. Unfortunately, the #



symbol in the British keyboard layout of my Macbook is in an inaccessible place, and this negatively affects my experience with these structures.







Input properties



Defining properties that can be passed to a component (analogous to @Input



in Angular) is as easy as exporting a variable from a JS module using the export



keyword. Perhaps at first it can be confusing - but let's write an example and see how really simple it is:







 <script> export let todo = { name: '', done: false }; </script> <p> { todo.name } { todo.done ? 'โœ“' : 'โœ•' } </p>
      
      







Now create a container for this component, which will transmit data to it:







 <script> import Todo from './Todo.svelte'; const todos = [{ name: " Svelte", done: false }, { name: " Vue", done: false }]; </script> {#each todos as todo} <Todo todo={todo}></Todo> {/each}
      
      





Similar to the fields in a regular JS object, todo={todo}



can be shortened and rewritten as follows:







 <Todo {todo}></Todo>
      
      





At first it seemed strange to me, but now I think it's brilliant.







Output Properties



To implement the behavior of the @Output



directive, for example, when the parent component @Output



any notifications from the child, we will use the createEventDispatcher



function, which is available in Svelte.









 <script> import { createEventDispatcher } from 'svelte'; export let todo; const dispatch = createEventDispatcher(); function markDone() { dispatch('done', todo.name); } </script> <p> { todo.name } { todo.done ? 'โœ“' : 'โœ•' } <button on:click={markDone}></button> </p>
      
      





In the parent component, you need to create a handler for the done



event so that you can mark the necessary objects in the todo



array.









 <script> import Todo from './Todo.svelte'; let todos = [{ name: " Svelte", done: false }, { name: " Vue", done: false }]; function onDone(event) { const name = event.detail; todos = todos.map((todo) => { return todo.name === name ? {...todo, done: true} : todo; }); } </script> {#each todos as todo} <Todo {todo} on:done={onDone}></Todo> {/each}
      
      





Note: to start detecting changes in an object, we do not mutate the object itself. Instead, we assign the todos



variable a new array, where the object of the desired task will already be changed to completed.







Therefore, Svelte is considered truly reactive : with the usual assignment of a value to a variable, the corresponding part of the representation will also change.







ngModel



Svelte has a special bind:<>={}



syntax bind:<>={}



for binding specific variables to the attributes of a component and synchronizing them with each other.







In other words, it allows you to organize two-way data binding:







 <script> let name = ""; let description = ""; function submit(e) { //    } </script> <form on:submit={submit}> <div> <input placeholder="" bind:value={name} /> </div> <div> <input placeholder="" bind:value={description} /> </div> <button> </button> </form>
      
      





Reactive Expressions



As we saw earlier, Svelte responds to assigning values โ€‹โ€‹to variables and redraws the view. You can also use reactive expressions to respond to changes in the value of one variable and update the value of another.







For example, let's create a variable that should show us that in the todos



array all tasks are marked as completed:







 let allDone = todos.every(({ done }) => done);
      
      





However, the view will not be redrawn when the array is updated, because the value of the allDone



variable allDone



assigned only once. We will use a reactive expression, which at the same time will remind us of the existence of "labels" in Javascript:







 $: allDone = todos.every(({ done }) => done);
      
      





It looks very exotic. If it seems to you that there is โ€œtoo much magicโ€, I remind you that tags are valid Javascript .







A small demo explaining the above:

demo







Content injection



To embed content, slots are also used that are placed in the right place inside the component.







To simply display the content that was transferred inside the component element, a special slot



element is used:







 // Button.svelte <script> export let type; </script> <button class.type={type}> <slot></slot> </button> // App.svelte <script> import Button from './Button.svelte'; </script> <Button>  </Button>
      
      





In this case, the ""



will take the place of the <slot></slot>



element.

Named slots will need to be named:







 // Modal.svelte <div class='modal'> <div class="modal-header"> <slot name="header"></slot> </div> <div class="modal-body"> <slot name="body"></slot> </div> </div> // App.svelte <script> import Modal from './Modal.svelte'; </script> <Modal> <div slot="header">  </div> <div slot="body">  </div> </Modal>
      
      





Lifecycle hooks



Svelte offers 4 lifecycle hooks that are imported from the svelte



package.









The onMount



function takes as a parameter a callback function that will be called when the component is placed in the DOM. Simply put, it is similar to the action of the ngOnInit



hook.







If the callback function returns another function, it will be called when the component is removed from the DOM.







 <script> import { onMount, beforeUpdate, afterUpdate, onDestroy } from 'svelte'; onMount(() => console.log('', todo)); afterUpdate(() => console.log('', todo)); beforeUpdate(() => console.log('  ', todo)); onDestroy(() => console.log('', todo)); </script>
      
      





It is important to remember that when calling onMount



all the properties included in it must already be initialized. That is, in the fragment above, todo



should already exist.







State management



Svelte's state management is incredibly simple, and perhaps this part of the framework prefers me more than the rest. You can forget about the verbosity of code when using Redux. For an example, we will create storage in our application for storage and task management.







Recordable Vaults



First you need to import the writable



storage writable



from the svelte/store



package and tell it the initial value initialState









 import { writable } from 'svelte/store'; const initialState = [{ name: " Svelte", done: false }, { name: " Vue", done: false }]; const todos = writable(initialState);
      
      





Usually, I put similar code in a separate file like todos.store.js



and export the storage variable from it so that the component where I import it can work with it.







Obviously, the todos



object has now become a repository and is no longer an array. To get the storage value, weโ€™ll use a little magic in Svelte:









Thus, we simply replace in the code all references to the todos



variable with $todos



:







 {#each $todos as todo} <Todo todo={todo} on:done={onDone}></Todo> {/each}
      
      





State setting



The new value of the recordable storage can be specified by calling the set



method, which imperatively changes the state according to the passed value:







 const todos = writable(initialState); function removeAll() { todos.set([]); }
      
      





Status update



To update the storage (in our case, todos



), based on its current state, you need to call the update



method and pass it a callback function that will return the new state for the storage.







We rewrite the onDone



function that we created earlier:







 function onDone(event) { const name = event.detail; todos.update((state) => { return state.map((todo) => { return todo.name === name ? {...todo, done: true} : todo; }); }); }
      
      





Here I used a reducer right in the component, which is bad practice. Let's move it to a file with our repository and export a function from it that will simply update the state.







 // todos.store.js export function markTodoAsDone(name) { const updateFn = (state) => { return state.map((todo) => { return todo.name === name ? {...todo, done: true} : todo; }); }); todos.update(updateFn); } // App.svelte import { markTodoAsDone } from './todos.store'; function onDone(event) { const name = event.detail; markTodoAsDone(name); }
      
      





Status Change Subscription



In order to find out that the value in the repository has changed, you can use the subscribe



method. Keep in mind that the repository is not an observable



object, but provides a similar interface.







 const subscription = todos.subscribe(console.log); subscription(); //    
      
      





Observables



If this part caused you the greatest excitement, then I hasten to please that not so long ago, RxJS support was added to Svelte and the Observable was introduced for ECMAScript.







As an Angular developer, Iโ€™m already used to working with reactive programming, and the lack of an async pipe counterpart would be extremely inconvenient. But Svelte surprised me here.







Let's look at an example of how these tools work together: display a list of repositories on Github found by the keyword "Svelte"



.







You can copy the code below and run it directly in the REPL :







 <script> import rx from "https://unpkg.com/rxjs/bundles/rxjs.umd.min.js"; const { pluck, startWith } = rx.operators; const ajax = rx.ajax.ajax; const URL = `https://api.github.com/search/repositories?q=Svelte`; const repos$ = ajax(URL).pipe( pluck("response"), pluck("items"), startWith([]) ); </script> {#each $repos$ as repo} <div> <a href="{repo.url}">{repo.name}</a> </div> {/each} <!--   Angular: <div *ngFor="let repo of (repos$ | async)> <a [attr.href]="{{ repo.url }}">{{ repo.name }}</a> </div> -->
      
      





Just add the $



symbol to the name of the observable variable repos$



and Svelte will automatically display its contents.







My wishlist for Svelte



Typescript Support



As a Typescript enthusiast, I cannot but wish for the possibility of using types in Svelte. I'm so used to it that sometimes I get addicted and arrange types in my code, which I then have to remove. I really hope that Svelte will soon add Typescript support. I think this item will be on the wish list of anyone who wants to use Svelte with experience with Angular.







Agreements and guidelines



Rendering in the representation of any variable from the <script>



block is a very powerful feature of the framework, but in my opinion, it can lead to code clutter. I hope that the Svelte community will work through a series of conventions and guidelines to help developers write clean and understandable component code.







Community support



Svelte is a grandiose project, which, with increased community effort in writing third-party packages, manuals, blog articles and more, can take off and become a recognized tool in the amazing world of Frontend development that we have today.







Finally



Despite the fact that I was not a fan of the previous version of the framework, Svelte 3 made a good impression on me. It is simple, small, but can do a lot. It is so different from everything around that it reminded me of the excitement that I experienced when I switched from jQuery to Angular.







Regardless of which framework you are using now, learning Svelte is likely to take only a couple of hours. Once you know the basics and understand the differences with what youโ€™re used to writing, working with Svelte will be very easy.







In the Russian-language Telegram channel @sveltejs you will surely find developers who have experience with various frameworks and are ready to share their stories, thoughts and tips regarding Svelte.








All Articles