39

Svelte for Angular Developers

 4 years ago
source link: https://www.tuicool.com/articles/UrYZJvR
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

aeyq63e.jpg!web

Photo by Zan Ilic on Unsplash

Svelte is a relatively recent framework for building UIs developed byRich 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 :package:

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”.

ziiyIbA.png!web

Importing Components

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

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

:bulb: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 :closed_book:

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>

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>

Events

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

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

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’)} />

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}

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 ? ':white_check_mark:' : ':x:' }
</p>
  • 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}

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

<Todo {todo}></Todo>

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 ? ':white_check_mark:' : ':x:' }

<button on:click={markDone}>Mark done</button>
</p>

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}

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.

:bulb: 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:<prop>={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>

Reactive Statements :rocket:

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);

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);

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:

FN3e6rY.jpg

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>

As you can see, the string “Submit” will take the place of <slot></slot> . 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>

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>

:bulb: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);

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!

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

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

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([]);
}

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;
    });
  });
 }

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.jsexport function markTodoAsDone(name) {
const updateFn = (state) => {
return state.map((todo) => {
return todo.name === name ? {...todo, done: true} : todo;
});
});
todos.update(updateFn);
}
// App.svelteimport { markTodoAsDone } from './todos.store';function onDone(event) {
const name = event.detail;
markTodoAsDone(name);
}

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

:bulb: 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";
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's implementation
<div *ngFor="let repo of (repos$ | async)>
<a [attr.href]="{{ repo.url }}">{{ repo.name }}</a>
</div>

:bulb: 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!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK