11

El componente component en svelte

Eduardo
eperedo
66631

No, no es un error en el título del post. Svelte viene por defecto con un componente llamado component.

Profesor Professorson Community

Yep, naming is hard.


Con este componente podemos renderizar otros componentes de svelte de manera dinámica mediante su atributo this. Pero ¿en qué casos quisiera hacer algo como esto? Para este post vamos a usar este componente para darle un funcionamiento a una barra de navegación de nuestra aplicación.
Tenemos una aplicación con 3 distintas rutas: Home, Contact y About. Cada una de estas rutas puede mostrar distinto contenido. Lo que deseamos es tener 1 botón por cada ruta y que al ser presionados nos muestren el contenido de dicha ruta.

Primero vamos a crear los componentes para cada ruta:

// Home.svelte
<h1>This is the Home page</h1>
// Contact.svelte
<h1>This is the Contact page</h1><form><inputtype="text"placeholder="Write a nice message" /></form>
// About.svelte
<h1>This is the About page</h1><p>Created by Eduardo P. Rivero</p>

Si no tuvieramos este componente component ¿Cómo podríamos solucionar nuestra navegación?

Ya que he creado el proyecto usando svelte/template mi componente de entrada es App.svelte, sobre este vamos a importar cada uno de nuestros componentes que representan cada ruta y también una variable que nos permite darle el valor de la ruta que queremos que se muestre por defecto.

// App.svelte
<&script>
  import Home from "./Home.svelte";
  import Contact from "./Contact.svelte";
  import About from "./About.svelte";

  let currentPage = "home";
<&script>

Ahora vamos con nuestro template que va a mostrar un botón por cada ruta.

// App.svelte
<style>.nav {
    display: grid;
    grid-gap: 5px;
    grid-template-columns: repeat(3, 100px);
    justify-content: center;
  }
</style><divclass="nav"><button>Home</button><button>Contact</button><button>About</button></div>

{#if currentPage === 'home'}
<Home />
{:else if currentPage === 'contact'}
<Contact />
{:else}
<About />
{/if}

Vemos como usamos el componente if para verificar el valor de la variable currentPage y según eso mostrar cada uno de nuestros componentes.

Para que el valor vaya cambiando al presionar los botones nos falta agregar el evento click a cada uno de ellos.

// App.svelte: omití el resto del código para
// mostrar únicamente las partes que cambiaron
<&script>
  function changePage(newPage) {
    currentPage = newPage;
  }
<&script><divclass="nav"><buttonon:click={() => changePage('home')}>Home</button><buttonon:click={() => changePage('contact')}>Contact</button><buttonon:click={() => changePage('about')}>About</button></div>

Dentro de cada evento click de los botones llamamos a la función changePage y le enviamos un valor que actualizará la variable currentPage. Al cambiar el valor nuestro componente #if-else se encargará de mostrar la ruta correspondiente.

gif mostrando el cambio de rutas
<h3>El componente svelte:component</h3>

Ahora vamos con la implementación usando svelte:component, para ello tenemos que reemplazar en nuestro template el condicional #if-else

// App.svelte
<&script>
  function changePage(newPage) {
    currentPage = newPage;
  }
<&script><divclass="nav"><buttonon:click={() => changePage('home')}>Home</button><buttonon:click={() => changePage('contact')}>Contact</button><buttonon:click={() => changePage('about')}>About</button></div><svelte:componentthis={currentPage} />

Al hacer este cambio nuestro navegador nos mostrará un error porque el prop this espera un componente y actualmente nuestra variable currentPage es un string así que necesitamos cambiar eso.

En lugar del string “home” le asignaremos el componente Home.

// App.svelte
<&script>
  import Home from "./Home.svelte";
  import Contact from "./Contact.svelte";
  import About from "./About.svelte";

  let currentPage = Home;

  function changePage(newPage) {
    currentPage = newPage;
  }
<&script><divclass="nav"><buttonon:click={() => changePage('home')}>Home</button><buttonon:click={() => changePage('contact')}>Contact</button><buttonon:click={() => changePage('about')}>About</button></div><svelte:componentthis={currentPage} />

Ok ahora nuestro componente muestra el contenido de Home, pero si hacemos click en cada uno de los botones tenemos un error porque nuevamente estamos asignando un string a la variable currentPage. Necesitamos actualizar los valores en cada uno de los botones tambien.

// App.svelte
<&script>
  import Home from "./Home.svelte";
  import Contact from "./Contact.svelte";
  import About from "./About.svelte";

  let currentPage = Home;

  function changePage(newPage) {
    currentPage = newPage;
  }
<&script><divclass="nav"><buttonon:click={() => changePage(Home)}>Home</button><buttonon:click={() => changePage(Contact)}>Contact</button><buttonon:click={() => changePage(About)}>About</button></div><svelte:componentthis={currentPage} />

Listo, ahora si probamos el funcionamiento haciendo click en cada uno de los botones vemos como se muestra el contenido de los componentes de manera correcta.

gif mostrando el cambio de rutas
<h3>Props usando svelte:component</h3>

Hemos visto que cada uno de nuestros componentes tiene un título dentro de una etiqueta h1. Vamos a hacer un poco más dinámico este título agregando como parámetro el nombre de la ruta. Primero necesitamos modificar nuestros componentes para agregarle un nuevo prop llamado name.

// Home.svelte
<&script>
  export let name;
<&script><h1>This is the {name} page</h1>
// Contact.svelte
<&script>
  export let name;
<&script><h1>This is the {name} Page</h1><form><inputtype="text"placeholder="Write a nice message" /></form>
// About.svelte
<&script>
  export let name;
<&script><h1>This is the {name} Page</h1><p>Created by Eduardo P. Rivero</p>

Si verificamos nuestro navegador vamos a ver como el título de cada componente es: This is the undefined page.

image mostrando el titulo sin parametro

Pasarle un parámetro a un svelte:component es tan simple como indicarle un atributo más:

<divclass="nav"><buttonon:click={() => changePage(Home)}>Home</button><buttonon:click={() => changePage(Contact)}>Contact</button><buttonon:click={() => changePage(About)}>About</button></div><svelte:componentthis={currentPage}name="Home" />

Le estoy asignando directamente el valor de Home, pero esto solo funcionaría para una de las rutas. Una solución rápida sería crear otra variable currentTitle y cambiarla al hacer click en cada botón, pero vamos a definir una estructura un poco más escalable porque al final del día somos programadores y complicar las cosas es lo que nos apasiona.

Lo primero es que vamos a definir un arreglo dónde asignaremos nuestras rutas. Cada ruta tendrá la propiedad name que usaremos para el título y la propiedad component que la pasaremos al prop this de svelte:component.

La sección script de nuestro App.svelte queda así:

<&script>
  import Home from "./Home.svelte";
  import Contact from "./Contact.svelte";
  import About from "./About.svelte";

  const routes = [
    {
      name: "Home",
      component: Home,
    },
    {
      name: "Contact",
      component: Contact,
    },
    {
      name: "About",
      component: About,
    },
  ];
<&script>

Ahora asignamos la ruta home como valor por defecto para nuestra variable currentPage.

<&script>
  const routes = [
    {
      name: "Home",
      component: Home,
    },
    {
      name: "Contact",
      component: Contact,
    },
    {
      name: "About",
      component: About,
    },
  ];

  let currentPage = routes[0];
<&script>

Ahora adecuamos nuestro template según nuestra estructura de datos. Primero los botones, ahora vamos a renderizarlos haciendo un bucle a nuestro arreglo.

<divclass="nav">
  {#each routes as route}
    <buttonon:click={() => changePage(route)}>
      {route.name}
    </button>
  {/each}
</div>

Usando each recorremos nuestro arreglo y mostramos un botón por cada elemento. En el evento click enviamos el objeto que representa la ruta y para el texto del botón usamos la propiedad name.

Ahora tendremos que preparar el svelte:component de acuerdo a nuestra nueva estructura. Nuestro template quedaría de la siguiente manera:

<divclass="nav">
  {#each routes as route}
    <buttonon:click={() => changePage(route)}>
      {route.name}
    </button>
  {/each}
</div><svelte:componentthis={currentPage.component}name={currentPage.name} />

Listo, ahora en caso tengamos más rutas solo tenemos que agregarla a nuestro arreglo y sin modificaciones en nuestro template se podrá visualizar en nuestra navegación.

<h3>Bonus Track</h3>

Para mejorar un poco la navegación vamos a cambiar un poco el estilo del botón que tenga la ruta activa. Para eso vamos a verificar que el nombre de la ruta sea igual a la que actualmente se encuentra seleccionada. Ya que tenemos un bucle each renderizando nuestros botones con una simple línea de código podemos lograr esto

<style>.active {
    background-color: blue;
    color: white;
  }
</style><divclass="nav">
  {#each routes as route}
    <buttonclass:active={route.name ===currentPage.name}
      on:click={() => changePage(route)}>
      {route.name}
    </button>
  {/each}
</div>

Listo, agregamos una clase active que le asignará el color de fondo a azul y la letra de color blanco. Y luego la asignamos de manera condicional usando class:active con la validación mencionada anteriormente.

Navegación que muestra la ruta activa

Incluso podemos cambiar la url para simular una navegación más real. Tan simple como llamar al método pushState del objeto history en la función changePage.

<&script>
  import Home from "./Home.svelte";
  import Contact from "./Contact.svelte";
  import About from "./About.svelte";

  function changePage(route) {
    currentPage = route;
    history.pushState(null, currentPage.name, `/${currentPage.name}`);
  }
<&script>

Listo, si volvemos a probar nuestra aplicación vemos como el url cambia cada vez que presionamos uno de los botones de la navegación.

Url de browser cambia al presionar los botones

Obviamente esto debe complementarse con otras técnicas de enrutamiento. Si deseas aprender más sobre pushState puedes tomar el curso de router para single page apps y con lo aprendido implementar tu propio svelte-router.

<h3>Conclusiones</h3>

En este post vimos lo siguiente:

  • La utilidad de svelte:component cuando queremos renderizar distintos componentes de manera dinámica basada en acciones de nuestros usuarios.

  • Cómo adaptar este componente según una estructura de datos que nos permite generar funcionalidades con el mínimo de cambios en nuestro template.

Saludos 😄

Escribe tu comentario
+ 2
Ordenar por:
1
6450Puntos
5 años

Brutal, guardo en favoritos para próximas consultas.