Svelte for Angular Developers

Giancarlo Buomprisco - Oct 5 '19 - - Dev Community

A simple introduction to Svelte from an Angular developer’s perspective

This article was originally published on Bits and Pieces by Giancarlo Buomprisco

Svelte is a relatively recent framework for building UIs developed by Rich Harris, also the creator of Rollup.

Svelte will likely appear as a radically different approach from what you’ve seen before, and that’s probably a good thing.

Svelte will impress you for mainly two things: its speed, and its simplicity. In this article, we’ll focus on the latter.

As my main expertise revolves around Angular, it is only normal that I try to learn it by replicating the approaches I’m used to with the former by using Svelte. And this is what this article is going to be about: how to do Angular things — but with Svelte.


Useful tip: Use **Bit** to encapsulate components with all their dependencies and setup. Build truly modular applications with better code reuse, simpler maintenance, and less overhead.


Note: Although I will be expressing my preferences, this is not a comparison: it is a simple and quick introduction to Svelte for people who use Angular as a framework of choice.

Spoiler Alert: Svelte is fun.

Components 📦

In Svelte every component corresponds to its relative file: for instance, the component Button will be created by naming its file Button.svelte. Of course, we normally do the same in Angular, but it is purely a convention.

Svelte components are written using a single-file convention, and it is made of 3 sections: script, style, and the template, which doesn’t have to be wrapped within its specific tag.

Let’s create a dead-simple component that renders “Hello World”.

Importing Components

This is mostly the same as if you were importing a JS file, with an only difference:

  • you need to explicitly reference the components with the .svelte extension

  • it’s important to notice that you will need to import Svelte components from within the script section

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

    <Todo></Todo>
Enter fullscreen mode Exit fullscreen mode

💡It is clear from the snippets above that the number of lines to create a component in Svelte is incredibly low. There’s a lot of implicitness and constraints there, of course, yet it is simple enough to get quickly used to it. Nicely played.

Basic Syntax 📕

Interpolations

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

    <script>
      let someFunction = () => {...}

    </script>

    <span>{ 3 + 5 }</span>
    <span>{ someFunction() }</span>
    <span>{ someFunction() ? 0 : 1 }</span>
Enter fullscreen mode Exit fullscreen mode

I am quite used to typing twice the curly brackets that sometimes I make simple mistakes, but that’s just me.

Attributes

Passing attributes to components is also fairly easy, there’s no need for quotes and can even be Javascript expressions:

    // Svelte
    <script>
      let isFormValid = true;
    </script>

    <button disabled={!isFormValid}>Button</button>
Enter fullscreen mode Exit fullscreen mode

Events

The syntax for listening to events is on:event={handler}.


    <script>
      const onChange = (e) => console.log(e);
    </script>

    <input on:input={onChange} />
Enter fullscreen mode Exit fullscreen mode

As you may notice, opposite to Angular we don’t need to call the functions using parenthesis. To pass arguments to the function with could simply define an anonymous function:


    <input on:input={(e) => onChange(e, ‘a’)} />
Enter fullscreen mode Exit fullscreen mode

In terms of readability, I am in two minds:

  • typing less, as we do not need brackets and quotes, is always a good thing

  • readability-wise, I always liked Angular better than React, and as a result, to me is slightly more readable than Svelte. With that said, once again, I am very used to it and therefore my view here is biased

Structural Directives

Contrary to Vue and Angular, Svelte provides a special syntax for looping and control flow within templates rather than using structural directives:

    {#if todos.length === 0}
      No todos created
    {:else}
      {#each todos as todo}
        <Todo {todo} /> 
      {/each}
    {/if}
Enter fullscreen mode Exit fullscreen mode

I like this a lot. No need to create HTML nodes, and readability-wise it does look awesome. Unfortunately my Macbook’s UK keyboard places # in a difficult to-reach-spot, which is making my experience with it a bit clunky.

Inputs

Setting and getting properties (or @Input) from other components is as easy as exporting a constant from a script. Alright, that can be
confusing — let’s write an example and see how easy it is:

    <script>
      export let todo = { name: '', done: false };
    </script>

    <p>
      { todo.name } { todo.done ? '✅' : '❌' }
    </p>
Enter fullscreen mode Exit fullscreen mode
  • As you may have noticed, we initialized todo with a value: that will be the default value if the consumer will not provide a value for the input

Next, we create the container component that will pass the data down:

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

     const todos = [{
      name: "Learn Svelte",
      done: false
     },
     {
      name: "Learn Vue",
      done: false
     }];
    </script>

    {#each todos as todo}
      <Todo todo={todo}></Todo>
    {/each}
Enter fullscreen mode Exit fullscreen mode

Similarly to plain JS, todo={todo} can be shortened and written as follows:

    <Todo {todo}></Todo>
Enter fullscreen mode Exit fullscreen mode

At first, I thought it was crazy, and now I think it is genial.

Outputs

To create an@Output, i.e. a notification from child components to their parents, we will be using Svelte’s createEventDispatcher.

  • We import the function createEventDispatcher and assign its return value to a variable called dispatch

  • We call dispatch with two arguments: its name, and its payload (or “detail”)

  • Notice: we add a function on click events (on:click) that will call the function markDone

    <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}>Mark done</button>
    </p>
Enter fullscreen mode Exit fullscreen mode

The container component will need to provide a callback for the event dispatched, so that we can mark that todo object as “done”:

  • we create a function called onDone

  • we assign the function to the component’s event that we called done.
    The syntax is on:done={onDone}

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

     let todos = [{
      name: "Learn Svelte",
      done: false
     },
     {
      name: "Learn 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}
Enter fullscreen mode Exit fullscreen mode

Notice: to trigger a change detection, we do not mutate the object. Instead, we reassign the array todos and replace the todo marked as done.

💡That is why Svelte is considered truly *reactive*: by simply reassigning the variable, the view will re-render accordingly.

ngModel

Svelte provides a special syntax bind:={value} for binding certain variables to a component’s attributes and keep them in sync.

In different words, it allows for two-way data binding:

    <script>
      let name = "";
      let description = "";

      function submit(e) {  // submit }
    </script>

    <form on:submit={submit}>
      <div>
        <input placeholder="Name" bind:value={name} />
      </div>

      <div> 
        <input placeholder="Description" bind:value={description} />
      </div>

      <button>Add Todo</button>
    </form>
Enter fullscreen mode Exit fullscreen mode

Reactive Statements 🚀

As we’ve seen before, Svelte reacts to an assignment and re-renders the view. To react to a change from another value, we can use reactive statements so that another value can automatically change in its turn.

For instance, let’s create a variable that will display whether all the todos have been checked:

    let allDone = todos.every(({ done }) => done);
Enter fullscreen mode Exit fullscreen mode

Unfortunately, though, the view will not be rendered because we never reassign allDone. Let’s replace this with a reactive statement, that makes us of Javascript “labels”:

    $: allDone = todos.every(({ done }) => done);
Enter fullscreen mode Exit fullscreen mode

Oh! That’s exotic. And before you shout “that’s too magic!”: did you know that labels are valid Javascript?

Let’s take a look at a demo:

Content Projection

Content Projection also uses slots, which means you can name a slot and project it wherever you want within your component.

To simply interpolate all the content passed as content to your component, you can simply use the special element slot:

    // Button.svelte
    <script>
        export let type;
    </script>

    <button class.type={type}>
     <slot></slot>
    </button>

    // App.svelte
    <script>
      import Button from './Button.svelte';
    </script>

    <Button>
      Submit
    </Button>
Enter fullscreen mode Exit fullscreen mode

As you can see, the string “Submit” will take the place of . Named slots require us to assign a name to a slot:

    // 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">
      Header
     </div>

     <div slot="body">
      Body
     </div>
    </Modal>
Enter fullscreen mode Exit fullscreen mode

Lifecycle Hooks

Svelte provides 4 lifecycle hooks, that are imported from the svelte package.

  • onMount, a callback that runs when the component gets mounted

  • beforeUpdate, a callback that runs before the components updates

  • afterUpdate, a callback that runs after the components updates

  • onDestroy, a callback that runs when the component gets destroyed

onMount is a function that accepts a callback that will be called when the component is mounted on the DOM. In short, it can be associated to ngOnInit.

If you return a function from the callback, this will be called when the component is unmounted.

    <script>

    import { 
      onMount, 
      beforeUpdate, 
      afterUpdate, 
      onDestroy 
    } from 'svelte';

    onMount(() => console.log('Mounted', todo));
    afterUpdate(() => console.log('Updated', todo));
    beforeUpdate(() => console.log('Going to be updated', todo));
    onDestroy(() => console.log('Destroyed', todo));

    </script>
Enter fullscreen mode Exit fullscreen mode

💡It’s important to notice that when onMount is called, its inputs have already been initialized. That means, todo is already defined when we log it in the snippet above.

State Management

State Management is Svelte is incredibly fun and easy, and probably the aspect I like the most about it. Forget Redux’s boilerplate, and let’s see how to build a store that will allow us to store and manipulate our todos.

Writable Store

The first thing we can do is to import writable from the package svelte/store and pass to the function the initial state

    import { writable } from 'svelte/store';

    const initialState = [{
      name: "Learn Svelte",
      done: false
    },
    {
      name: "Learn Vue",
      done: false
    }];

    const todos = writable(initialState);
Enter fullscreen mode Exit fullscreen mode

Normally, I’d store this in a file called todos.store.js and export the writable store so that the container component can update it.

As you may have noticed, todos is now a writable object and not an array. To retrieve the value of the store, we are going to use some Svelte magic:

  • by prepending the store name with $ we can directly access the value of the store!

💡That means, all the references to todos will now be $todos:

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

Setting State

A writable store’s state can be set by calling the method set which will imperatively set the state to the value passed:

    const todos = writable(initialState);

    function removeAll() {
      todos.set([]);
    }
Enter fullscreen mode Exit fullscreen mode

Updating State

To update the store based on the current state, in our case the todos store, we can call the function update to which we pass a callback. The return value of the callback will be the new state passed to the store:

Let’s rewrite the function onDone that we defined above:

    function onDone(event) {
      const name = event.detail;

      todos.update((state) => {
        return state.map((todo) => {
           return todo.name === name ? {...todo, done: true} : todo;
        });
      });
     }
Enter fullscreen mode Exit fullscreen mode

Alright, I know, I wrote a reducer in the component. Not cool, you’re saying. Let’s move that to the store file, and export a function that simply takes care of updating 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);
    }
Enter fullscreen mode Exit fullscreen mode

Listening to value changes

We can use the method .subscribe to listen to value changes from a store, . Notice, though, that the store is not an observable although the interface looks similar.

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

    subscription(); // unsubscribe subscription by calling it
Enter fullscreen mode Exit fullscreen mode

💡 Svelte’s store package also provides two more utilities called readable and derivable.

Observables 🦊

Oh, the part you were waiting for! You’ll be delighted to know that Svelte recently added support for RxJS and the ECMAScript Observable proposal.

As an Angular developer, I am quite used to working with reactive programming and not having something like the async pipe would be a bummer. But Svelte surprised me once again.

Let’s see how the two can work together: we’ll render a list of repositories from Github searched with the keyword “Svelte”.

You can paste the snippet below in the Svelte REPL and it will just work:

    <script>
     import rx from "[https://unpkg.com/rxjs/bundles/rxjs.umd.min.js](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`](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's implementation
    <div *ngFor="let repo of (repos$ | async)>
      <a [attr.href]="{{ repo.url }}">{{ repo.name }}</a>
    </div>
Enter fullscreen mode Exit fullscreen mode

💡 As you may have noticed, I prefixed the observable repos$ with $ and Svelte will automagically render it!

My Svelte Wishlist 🧧

Typescript support

As a Typescript enthusiast, I can’t help but wish for being able to write typed Svelte. I am so used to it that I keep typing my code, and then have to revert it. I do hope Svelte will soon add support for Typescript, as I suspect it is on everyone’s wishlist should they use Svelte from an Angular background.

Conventions & Coding Guidelines

Being able to render in the view any variable in the script block is both powerful and, in my opinion, potentially messy. I am hoping the Svelte community will be working on a set of conventions and guidelines to help developers keep their files clean and understandable.

Community Support

Svelte is a great project. With more community support such as third-party packages, backers, blog posts, etc. it can take off and become an established, further option to the awesome front-end landscape we’re enjoying nowadays.

Final Words

Although I was not a fan of the previous version, I am fairly impressed with Svelte 3. It’s easy, small (yet exhaustive) and fun. It is so different that it reminds me of the first time when I transitioned from jQuery to Angular, and that’s exciting.

Whatever your framework of choice, learning Svelte will probably take a couple of hours. Once you figured out the basics and the differences with what you’re used to writing, writing Svelte is going to very easy.


If you need any clarifications, or if you think something is unclear or wrong, do please leave a comment!

I hope you enjoyed this article! If you did, follow me on Medium or Twitter for more articles about the FrontEnd, Angular, RxJS, Typescript and more!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .