Angular vs Svelte - Card Component
angular javascript svelte ui
If you check out some of my previous posts, you'll know that I've been doing some experiments with Svelte and Sapper lately.
As I've been working with Angular for years and now I'm learning Svelte by doing, I thought it could be useful to migrate some of my components from Angular to Svelte.
In this article, I'll share the following card component built both with Angular and Svelte so you can see the differences:
I'm using tailwindcss as my utility-first CSS framework. If you're not familiar with tailwind, please checkout the official tailwindcss site. Also, if you want to start using tailwindcss with angular or with Svelte, I wrote the following articles:
Angular
For the Angular version of this card component, we'll need to create a post-card component like this:
// post-card.component.ts
import { Component, Input } from '@angular/core'
@Component({
selector: 'post-card',
templateUrl: './post-card.component.html',
styleUrls: ['./post-card.component.css']
})
export class PostCardComponent {
@Input() title;
@Input() description;
@Input() location;
@Input() picUrl = 'https://i1.wp.com/www.foot.com/wp-content/uploads/2017/03/placeholder.gif?ssl=1'
@Input() createdAt;
@Input() labels = [];
constructor() { }
showMore() {
alert("showing more...")
}
}
- The
@Input()
decorator allow us to share data between components. An@Input()
property is writable while an@Output()
property is observable.
<!-- post-card.component.html -->
<div class="flex flex-wrap shadow-lg rounded-lg overflow-hidden mb-6">
<!-- Image -->
<div class="w-full h-48 md:h-auto md:w-1/4 bg-cover bg-center" [style.backgroundImage]="'url('+ picUrl +')'"></div>
<!-- Details -->
<div class="w-full md:w-3/4 px-6 py-4 bg-white">
<div class="py-2">
<p class="text-2xl">{{title}}</p>
<p class="text-sm text-gray-600 mb-2">{{createdAt | date:'MM/dd/yyyy'}}</p>
<!-- Labels -->
<div class="flex flex-wrap">
<p *ngFor="let label of labels"
class="border border-solid border-orange-500 rounded w-auto inline-block px-2 py-1 mr-3 text-sm mb-2">
{{label}}
</p>
</div>
</div>
<p class="flex items-center text-sm mb-4">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<path class="heroicon-ui"
d="M4.06 13a8 8 0 0 0 5.18 6.51A18.5 18.5 0 0 1 8.02 13H4.06zm0-2h3.96a18.5 18.5 0 0 1 1.22-6.51A8 8 0 0 0 4.06 11zm15.88 0a8 8 0 0 0-5.18-6.51A18.5 18.5 0 0 1 15.98 11h3.96zm0 2h-3.96a18.5 18.5 0 0 1-1.22 6.51A8 8 0 0 0 19.94 13zm-9.92 0c.16 3.95 1.23 7 1.98 7s1.82-3.05 1.98-7h-3.96zm0-2h3.96c-.16-3.95-1.23-7-1.98-7s-1.82 3.05-1.98 7zM12 22a10 10 0 1 1 0-20 10 10 0 0 1 0 20z" />
</svg>
<span class="ml-2">{{location}}</span>
</p>
<p class="mb-4">{{description}}</p>
<div class="flex flex-wrap md:flex-row-reverse">
<button (click)="showMore()"
class="w-full md:ml-2 px-4 py-3 rounded bg-orange-600 hover:bg-orange-500 mb-2 md:w-auto md:mb-0 text-white">
Show more
</button>
</div>
</div>
</div>
- I'm using pipes to change the date format. A pipe takes in data as input and transforms it to a desired output. In this case, I'm transforming a javascript date to 'MM/dd/yyyy'.
- Data binding is achieved by using double curly braces like this:
{{yourProperty}}
- To setup an image as a background-image within the card, I'm using property binding like this:
[style.backgroundImage]="'url('+ picUrl +')'"
- NgFor is a structural directive that renders a template for each item in a collection. In this case, I'm using it to iterate through each label like this:
<p *ngFor="let label of labels" class="border border-solid border-orange-500 rounded w-auto inline-block px-2 py-1 mr-3 text-sm mb-2">
{{label}}
</p>
Usage
In order to display our post-card component, I'm adding a card property in the AppComponent like this:
// app.component.ts
import { Component } from '@angular/core'
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
// This data will be sent to our post-card component
card = {
title: "Lorem ipsum dolor sit amet, consectetur adipiscing elit",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum",
location: "New York",
picUrl: "https://images.pexels.com/photos/1060803/pexels-photo-1060803.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260",
createdAt: new Date(),
labels: ["Travel", "People"]
}
constructor() { }
}
}
<!-- app.component.html -->
<post-card [title]="card.title"
[description]="card.description"
[location]="card.location"
[picUrl]="card.picUrl"
[createdAt]="card.createdAt"
[labels]="card.labels">
</post-card>
- This time, I'm using property binding to send data from AppComponent to PostCardComponent.
Svelte
Now, let's use Svelte to build exactly the same component
<!-- PostCard.svelte -->
<script>
export let title
export let description
export let location
export let picUrl = 'https://i1.wp.com/www.foot.com/wp-content/uploads/2017/03/placeholder.gif?ssl=1'
export let createdAt
export let labels = []
function showMore() {
alert("Showing more...")
}
</script>
<div class="flex flex-wrap shadow-lg rounded-lg overflow-hidden mb-6">
<!-- Image -->
<div class="w-full h-48 md:h-auto md:w-1/4 bg-cover bg-center" style="background-image: url('{picUrl}')"></div>
<!-- Details -->
<div class="w-full md:w-3/4 px-6 py-4 bg-white">
<div class="py-2">
<p class="text-2xl">{title}</p>
<p class="text-sm text-gray-600 mb-2">{createdAt.toLocaleDateString()}</p>
<!-- Labels -->
<div class="flex flex-wrap">
{#each labels as label}
<p
class="border border-solid border-orange-500 rounded w-auto inline-block px-2 py-1 mr-3 text-sm mb-2">
{label}
</p>
{/each}
</div>
</div>
<p class="flex items-center text-sm mb-4">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path class="heroicon-ui" d="M4.06 13a8 8 0 0 0 5.18 6.51A18.5 18.5 0 0 1 8.02 13H4.06zm0-2h3.96a18.5 18.5 0 0 1 1.22-6.51A8 8 0 0 0 4.06 11zm15.88 0a8 8 0 0 0-5.18-6.51A18.5 18.5 0 0 1 15.98 11h3.96zm0 2h-3.96a18.5 18.5 0 0 1-1.22 6.51A8 8 0 0 0 19.94 13zm-9.92 0c.16 3.95 1.23 7 1.98 7s1.82-3.05 1.98-7h-3.96zm0-2h3.96c-.16-3.95-1.23-7-1.98-7s-1.82 3.05-1.98 7zM12 22a10 10 0 1 1 0-20 10 10 0 0 1 0 20z"/></svg>
<span class="ml-2">{location}</span>
</p>
<p class="mb-4">{description}</p>
<div class="flex flex-wrap md:flex-row-reverse">
<button
on:click={showMore}
class="w-full md:ml-2 px-4 py-3 rounded bg-orange-600 hover:bg-orange-500 mb-2 md:w-auto md:mb-0 text-white">
Show more
</button>
</div>
</div>
</div>
- Instead of using pipes, I'm formatting the javascript Date using
{createdAt.toLocaleDateString()}
. - Data binding is achieved by using single curly braces like this:
{yourProperty}
- To setup an image as a background-image within the card, I'm using data binding like this:
style="background-image: url('{picUrl}')"
- Instead of using structural directives for looping through items, Svelte use each blocks:
{#each labels as label} <p class="border border-solid border-orange-500 rounded w-auto inline-block px-2 py-1 mr-3 text-sm mb-2"> {label} </p> {/each}
Usage
<!-- OtherSvelteComponent.Svelte -->
<script>
import PostCard from "../components/PostCard.svelte";
// This data will be sent to our post-card component
const card = {
title: "Lorem ipsum dolor sit amet, consectetur adipiscing elit",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum",
location: "New York",
picUrl: "https://images.pexels.com/photos/1060803/pexels-photo-1060803.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260",
createdAt: new Date(),
labels: ["Travel", "People"]
}
</script>
<PostCard {...card} />
Final thoughts
As you probably noticed, the Svelte implementation is smaller. To be more precise, it's 20% smaller: 2348 characters (Svelte) against 2817 (Angular). You may think that 20% isn't a big difference, but the smaller the components are, the greater will be the difference between both implementations. To prove that point, let's see a super basic example:
// Angular - HelloWorld component
import { Component } from '@angular/core';
@Component({
selector: 'hello-world',
template: '<p>{{message}}</p>',
styleUrls: ['./hello-world.component.css']
})
export class HelloWorldComponent {
message = "Hello world"
constructor() { }
}
<!-- Svelte - HelloWorld component -->
<script>
let message = "Hello world"
</script>
<p>{message}</p>
In this case, the Svelte component is 400% smaller: 224 characters (Angular) against 59. And that's the added value! By keeping your components as small and atomic as possible, you'll be writing much less code.