2020-04-25

Svelte Tutorial 14: Component Composition (English & Indonesia)

English

Slots


Just like elements can have children...

<div>
  <p>I'm a child of the div</p>
</div>

...so can components. Before a component can accept children, though, it needs to know where to put them. We do this with the <slot> element. Put this inside Box.svelte:

<style>
  .box {
    width: 300px;
    border: 1px solid #aaa;
    border-radius: 2px;
    box-shadow: 2px 2px 8px rgba(0,0,0,0.1);
    padding: 1em;
    margin: 0 0 1em 0;
  }
</style>

<div class="box">
  <slot></slot>
</div>

You can now put things in the Box component in main app:

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

<Box>
  <h2>Hello!</h2>
  <p>This is a box. It can contain anything!</p>
</Box>

Slot Fallbacks


A component can specify fallbacks for any slots that are left empty, by putting content inside the <slot> element:

<div class="box">
  <slot>
    <em>no content was provided</em>
  </slot>
</div>

We can now create instances of <Box> without any children, you can try this from previous example:

<Box>
  <h2>Hello!</h2>
  <p>This is a box. It can contain anything.</p>
</Box>

<Box/>

Named Slots


The previous example contained a default slot, which renders the direct children of a component. Sometimes you will need more control over placement, such as with this <ContactCard>. In those cases, we can use named slots.

In ContactCard.svelte, add a name attribute to each slot:

<style>
  .contact-card {
    width: 300px;
    border: 1px solid #aaa;
    border-radius: 2px;
    box-shadow: 2px 2px 8px rgba(0,0,0,0.1);
    padding: 1em;
  }

  h2 {
    padding: 0 0 0.2em 0;
    margin: 0 0 1em 0;
    border-bottom: 1px solid #ff3e00
  }

  .address, .email {
    padding: 0 0 0 1.5em;
    background:  0 0 no-repeat;
    background-size: 20px 20px;
    margin: 0 0 0.5em 0;
    line-height: 1.2;
  }

  .address { background-image: url(tutorial/icons/map-marker.svg) }
  .email   { background-image: url(tutorial/icons/email.svg) }
  .missing { color: #999 }
</style>

<article class="contact-card">
  <h2>
    <slot name="name">
      <span class="missing">Unknown name</span>
    </slot>
  </h2>

  <div class="address">
    <slot name="address">
      <span class="missing">Unknown address</span>
    </slot>
  </div>

  <div class="email">
    <slot name="email">
      <span class="missing">Unknown email</span>
    </slot>
  </div>
</article>

Then, add elements with corresponding slot="..." attributes inside the <ContactCard> component:

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

<ContactCard>
  <span slot="name"> P. Sherman </span>
  <span slot="address"> 42 Wallaby Way, Sydney </span>
</ContactCard>

Slot Props


In this app, we have a <Hoverable> component that tracks whether the mouse is currently over it. It needs to pass that data back to the parent component, so that we can update the slotted contents.

For this, we use slot props. In Hoverable.svelte, pass the hovering value into the slot by adding hovering={hovering} in slot.

<script> // this is Hoverable.svelte
  let hovering;
  function enter() { hovering = true; }
  function leave() { hovering = false; }
</script>

<div on:mouseenter={enter} on:mouseleave={leave}>
  <slot hovering={hovering}></slot>
</div>

Then, to expose hovering to the contents of the<Hoverable> component, we use the let directive:

<Hoverable let:hovering={hovering}>
  <div class:active={hovering}>
    {#if hovering} <p>I am being hovered upon.</p>
    {:else} <p>Hover over me!</p>
    {/if}
  </div>
</Hoverable>

You can rename the variable, if you want — let's call it active in the parent component. You can also have as many of these components as you like, and the slotted props will remain local to the component where they're declared.

<script> // this is App.svelte
  import Hoverable from './Hoverable.svelte';
</script>

<style>
  div {
    padding: 1em;
    margin: 0 0 1em 0;
    background-color: #eee;
  }

  .active {
    background-color: #ff3e00;
    color: white;
  }
</style>

<Hoverable let:hovering={active}>
  <div class:active>
    {#if active} <p>I am being hovered upon.</p>
    {:else} <p>Hover over me!</p>
    {/if}
  </div>
</Hoverable>

<Hoverable let:hovering={active}>

  <div class:active>
    {#if active} <p>I am being hovered upon.</p>
    {:else} <p>Hover over me!</p>
    {/if}
  </div>
</Hoverable>

<Hoverable let:hovering={active}>

  <div class:active>
    {#if active} <p>I am being hovered upon.</p>
    {:else} <p>Hover over me!</p>
    {/if}
  </div>
</Hoverable>

Named slots can also have props; use the let directive on an element with a slot="..." attribute, instead of on the component itself.




Indonesia

Slots


Sama halnya seperti elemen bisa mempunyai child...

<div>
  <p>I'm a child of the div</p>
</div>

...begitu juga komponen juga bisa. Sebelum komponen bisa menerima child, komponen perlu diberi tahu dulu di mana posisinya akan diletakkan. Kita bisa melakukan ini dengan elemen <slot>. Berikut contoh Box.svelte:

<style>
  .box {
    width: 300px;
    border: 1px solid #aaa;
    border-radius: 2px;
    box-shadow: 2px 2px 8px rgba(0,0,0,0.1);
    padding: 1em;
    margin: 0 0 1em 0;
  }
</style>

<div class="box">
  <slot></slot>
</div>

Sekarang kita bisa meletakkan sesuatu pada komponen Box pada main app:

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

<Box>
  <h2>Hello!</h2>
  <p>This is a box. It can contain anything!</p>
</Box>

Slot Fallbacks


Sebuah komponen bisa menentukan fallback untuk slot apapun yang dibiarkan kosong, dengan meletakkan isian pada elemen <slot>:

<div class="box">
  <slot>
    <em>no content was provided</em>
  </slot>
</div>

Sekarang kita bisa membuat komponen <Box> tanpa diisi child, disini kita akan coba dari contoh sebelumnya:

<Box>
  <h2>Hello!</h2>
  <p>This is a box. It can contain anything.</p>
</Box>

<Box/>

Named Slots


Contoh sebelumnya kita melihat slot default, yang akan me-render child langsung dari komponen. Tapi terkadang kita akan membutuhkan pengaturan lebih dalam peletakan, contoh pada <ContactCard> berikut. Pada kasus seperti ini kita bisa gunakan named slot (=slot yang dinamai).

Pada ContactCard.svelte, tambahkan atribut name pada setiap slot:

<style>
  .contact-card {
    width: 300px;
    border: 1px solid #aaa;
    border-radius: 2px;
    box-shadow: 2px 2px 8px rgba(0,0,0,0.1);
    padding: 1em;
  }

  h2 {
    padding: 0 0 0.2em 0;
    margin: 0 0 1em 0;
    border-bottom: 1px solid #ff3e00
  }

  .address, .email {
    padding: 0 0 0 1.5em;
    background:  0 0 no-repeat;
    background-size: 20px 20px;
    margin: 0 0 0.5em 0;
    line-height: 1.2;
  }

  .address { background-image: url(tutorial/icons/map-marker.svg) }
  .email   { background-image: url(tutorial/icons/email.svg) }
  .missing { color: #999 }
</style>

<article class="contact-card">
  <h2>
    <slot name="name">
      <span class="missing">Unknown name</span>
    </slot>
  </h2>

  <div class="address">
    <slot name="address">
      <span class="missing">Unknown address</span>
    </slot>
  </div>

  <div class="email">
    <slot name="email">
      <span class="missing">Unknown email</span>
    </slot>
  </div>
</article>

Kemudian tambahkan elemen dengan atribut slot="..." yang sesuai di dalam komponen <ContactCard>:

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

<ContactCard>
  <span slot="name"> P. Sherman </span>
  <span slot="address"> 42 Wallaby Way, Sydney </span>
</ContactCard>

Slot Props


Pada app berikut, kita mempunyai komponen <Hoverable> yang akan mendeteksi jika pointer mouse berada di atasnya. komponen ini perlu mem pass data ke komponen parent nya, supaya kita bisa meng update isi dari slot.

Di sini kita bisa menggunakan slot props. Di dalam Hoverable.svelte, pass nilai hovering ke dalam slot dengan menambahkan hovering={hovering} pada <slot>.

<script> // this is Hoverable.svelte
  let hovering;
  function enter() { hovering = true; }
  function leave() { hovering = false; }
</script>

<div on:mouseenter={enter} on:mouseleave={leave}>
  <slot hovering={hovering}></slot>
</div>

Kemudian, untuk mengekspos hovering pada isi dari komponen <Hoverable>, kita bisa menggunakan direktiv let:

<Hoverable let:hovering={hovering}>
  <div class:active={hovering}>
    {#if hovering} <p>I am being hovered upon.</p>
    {:else} <p>Hover over me!</p>
    {/if}
  </div>
</Hoverable>

Kita juga bisa mengganti nama variabel jika mau — misal diganti jadi active pada komponen parent. Kita juga bisa mempunyai lebih dari satu komponen jika mau, dan slot prop akan tetap bersifat lokal pada komponen tempat dideklarasikan.

<script> // this is App.svelte
  import Hoverable from './Hoverable.svelte';
</script>

<style>
  div {
    padding: 1em;
    margin: 0 0 1em 0;
    background-color: #eee;
  }

  .active {
    background-color: #ff3e00;
    color: white;
  }
</style>

<Hoverable let:hovering={active}>
  <div class:active>
    {#if active} <p>I am being hovered upon.</p>
    {:else} <p>Hover over me!</p>
    {/if}
  </div>
</Hoverable>

<Hoverable let:hovering={active}>

  <div class:active>
    {#if active} <p>I am being hovered upon.</p>
    {:else} <p>Hover over me!</p>
    {/if}
  </div>
</Hoverable>

<Hoverable let:hovering={active}>

  <div class:active>
    {#if active} <p>I am being hovered upon.</p>
    {:else} <p>Hover over me!</p>
    {/if}
  </div>
</Hoverable>

Named slot juga bisa memiliki properti, gunakan direktiv let pada elemen dengan atribut slot="...", dan bukan pada komponennya.