DEV Community

Cover image for Angular vs Svelte - Card Component
Mauro Garcia
Mauro Garcia

Posted on • Edited on • Originally published at maurogarcia.dev

Angular vs Svelte - Card Component

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:

Alt text of image

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...")
    }
}
Enter fullscreen mode Exit fullscreen mode
  • 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>
Enter fullscreen mode Exit fullscreen mode
  • 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>
Enter fullscreen mode Exit fullscreen mode

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() { }
  }
}

Enter fullscreen mode Exit fullscreen mode
<!-- 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>
Enter fullscreen mode Exit fullscreen mode
  • 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>
Enter fullscreen mode Exit fullscreen mode
  • 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}
Enter fullscreen mode Exit fullscreen mode

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} />
Enter fullscreen mode Exit fullscreen mode

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() { }
}
Enter fullscreen mode Exit fullscreen mode
<!-- Svelte - HelloWorld component -->

<script>
  let message = "Hello world"
</script>

<p>{message}</p>
Enter fullscreen mode Exit fullscreen mode

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.

What do you think about Svelte? Have you tried it yet?

Let me know in the comments below!

Top comments (20)

Collapse
 
gc_psk profile image
Giancarlo Buomprisco

Counting characters to compare a fairly simple components is way too naive as a comparison: I do like both Angular and Svelte, so I don't think I am biased when I say this. I'd like to see the comparison of two full-blown complex, real world apps. Only then we can probably compare them.

Collapse
 
johncarroll profile image
John Carroll

Well, as someone who's only familiar with Angular, I can say that I find these comparisons interesting and useful.

But for folks like you, there's a website delivering exactly what you're asked for: github.com/gothinkster/realworld

Collapse
 
gc_psk profile image
Giancarlo Buomprisco • Edited

I agree: they're useful, I made one myself, but I wouldn't use counting characters as a metric for comparison. There's so much more in an application than components, which in my opinion are the "easy" part. The real world app you link is hardly one: I'm talking enterprise, resilient, rock-solid stuff. Probably not something you can find in the open, I admit.

Thread Thread
 
mauro_codes profile image
Mauro Garcia

I think your point is valid, but not all the apps are enterprise or super complex. And, for those kind of apps it's helpful to see this kind of comparisons. Btw, I love Angular and I'm using it on most of my projects.

As I said on previous posts, I'm just starting with Svelte and Sapper. Maybe in the feature I'll be comparing more complex stuff, but this series are all about components.

They are probably the easy part, but they are also a big part of the code we write every date.

Collapse
 
mauro_codes profile image
Mauro Garcia

Glad it was useful for you! 😄

Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
gc_psk profile image
Giancarlo Buomprisco

As I said in another comment, that's hardly a real world app. A more complex todo app at best.

Collapse
 
adamwakif profile image
Adam WaƘiƑ

I m a react developer .. i kicked React in the trash because of Svelte .. i love it 😍.. Svelte became my new girlfriend 😂😂

Collapse
 
mateiadrielrafael profile image
Matei Adriel

Id like to do the same but the lack of good ts support made us break up

Collapse
 
mauro_codes profile image
Mauro Garcia

Did you try svelte with sapper?

Collapse
 
doomd profile image
Doomd

I'd just like to add, that it took me about 9 days to do one website with React back in December 2019. I was able to refactor the entire website in Svelte in less than a day. Obviously refactoring an existing site is alot easier than building from scratch, but I mention this to illustrate the ease with which you can transition. The new Svelte site used 63% of the code that React did, and my Google PageSpeed score for that site went from 87 (mobile) to 99 (mobile). For web projects, I LOVE Svelte.

Collapse
 
seanmclem profile image
Seanmclem

How does svelte handle css?

Collapse
 
mateiadrielrafael profile image
Matei Adriel • Edited

U just use a <style> tag (and it scopes it automatically)

Collapse
 
seanmclem profile image
Seanmclem

Nice. Does it support separate files? If that's your thing

Thread Thread
 
mateiadrielrafael profile image
Matei Adriel

Tbh i have no idea

Thread Thread
 
doomd profile image
Doomd
Collapse
 
mackelito profile image
Markus Thiel

To make it more fair should not styleUrls: ['./hello-world.component.css'] be removed?

Collapse
 
juancarlospaco profile image
Juan Carlos

Svelte is superior and a better saner approach with more future-proof concept.
I prefer Nim tho.

Collapse
 
diogenespolanco profile image
Diógenes Polanco

I love that way of developing man.

Collapse
 
mauro_codes profile image
Mauro Garcia

Thanks! 😄