¿Qué debo aprender primero?

Si ya dominas HTML, CSS y JavaScript y estás decidido a dar el siguiente paso en tu carrera como desarrollador web aprendiendo a utilizar una de las librerías para construir aplicaciones web modernas, llegaste al lugar correcto.

¿Por dónde empezar?, ¿Cuál debes aprender?, ¿Cuál es el más fácil?
Aquí comparamos React, Vue, Svelte y Angular creando la misma aplicación para que averigües todo lo necesitas saber de estas increíbles herramientas.

Si deseas aprender React, Vue, Svelte o Angular puedes seguir un tutorial específico donde aprenderás todos los conceptos básicos como hacer peticiones a una API, iterar sobre una listas de elementos o manejar los eventos del DOM.

Si eres desarrollador Frontend y te has preguntado en qué se parece tu librería favorita a la librería de la que todos están hablando, estás en el lugar correcto para explorar los Pros y los Contras, puedes seguir esta lectura mientras comparas dos o más librerías. Aquí vamos a comparar como las utilizamos más allá de si utilizan un DOM virtual o si utilizan JSX.

Librería o Framework.

Antes de comenzar nos gustaría aclarar que aunque en el futuro llamaremos a todas las tecnologías por igual, si somos estrictos y puristas, hay una diferencia importante entre ellas.

Librería: Son un conjunto de funciones que podemos llamar dentro de nuestro código para realizar tareas comunes y nos ahorran el tener que escribir múltiples funciones para resolver los problemas. Son flexibles y utilizan la síntesis que ya conoces. React es una librería.

Framework: Es un conjunto estandarizado de conceptos, prácticas y criterios que nos ayudan a resolver un problema con patrones de diseño específico. Angular, Svelte y Vue son frameworks.

Tanto las librerías como los frameworks nos ayudan a resolver problemas complejos con soluciones probadas.

CLIs

Un CLI o Command Line Interface es una manera cómoda, fácil y rápida de inicializar un nuevo proyecto con las mejores prácticas utilizando un solo comando para configurar y comenzar a crear una aplicación increíble en unos segundos.

Contienen toda la configuración necesaria para que puedas utilizar las últimas funciones de JavaScript y tengas una gran experiencia de desarrollo gracias a que incluyen herramientas muy variadas como TypeScript, ESLint, PostCSS, soporte para una PWA, o lo necesario para implementar Unit Testing o End-to-end Testing.

También suelen incluir un par de componentes y una landing page básica para que la tomes como referencia, los únicos requerimientos para utilizarlos son tener node y npm instalado en tu computadora.

Puedes inicializar un nuevo proyecto con React simplemente corriendo este comando:

npx create-react-app mi-nueva-aplicacion

Es un CLI que contiene Babel, Webpack y ESlint por debajo, aunque siempre puedes añadir configuraciones personalizadas con herramientas específicas.

Sin embargo existen decenas de templates para comenzar a jugar con casi cualquier tecnología en segundos, puedes verlas, todas aquí.

Comenzar a utilizar Svelte es superfácil gracias al comando:

npx degit sveltejs/template mi-nueva-aplicacion

Su instalación y configuración es de las más rápidas además de que te permite agregar con comandos adiciónales TypeScript u otras tecnologías fácilmente.

Para crear un nuevo proyecto con Vue necesitas instalar @vue/cli.

npm install -g @vue/cli

A continuación, con Vue tienes la opción de poder crear un prototipo rápido solamente con un archivo .js o .vue una vez que instales @vue/cli-service-global.

O puedes decidir crear una aplicación, en ese caso necesitas correr el comando:

vue create mi-nueva-aplicacion

A continuación tendrás la opción de escoger entre la configuración default y una configuración personalizada a tus necesidades, con un click puedes añadir las siguientes tecnologías y sus variantes a tu proyecto:

  • Babel
  • TypeScript
  • Soporte de PWA
  • Router
  • Vuex
  • Preprocesadores CSS
  • Linter
  • Unit testing
  • E2E testing

Además al brindarte los archivos de configuración, puedes agregar más cosas fácilmente.

Crear una aplicación con Angular es muy fácil gracias a @angular/cli.

npm install -g @angular/cli
ng new mi-nueva-aplicacion

El CLI de angular tiene superpoderes y continúa siendo de gran utilidad durante el desarrollo, ya que a través de comandos podemos crear componentes, rutas, servicios, test y pipes gracias al comando ng generate.

Estructura de la aplicación

Aunque la organización de los archivos y de las carpetas depende mucho del desarrollador, cada librería tiene ciertas especificaciones muy generales para que la aplicación funcione.

De cualquier modo, las 4 tecnologías logran el mismo fin por diferentes caminos.

Estructura de archivos de React

El CLI nos genera los archivos básicos para iniciar una app como App.js o index.js, además de ciertos archivos para configurar el Unit Testing.

También genera una carpeta public donde tenemos los iconos, el manifest y el HTML base de nuestra aplicación.

El resto de nuestro código, nuestros estilos y nuestras imágenes lo colocaremos en sus respectivas carpetas dentro de src.

Un detalle es que a simple vista no tenemos ningún archivo para modificar la configuración de nuestro linter, webpack o babel.

Estructura de archivos de Svelte

En public además del favicon.ico y nuestro HTML base también debemos colocar nuestros archivos estáticos como imágenes, logos e iconos para que sean utilizados por Svelte.

Svelte tambien nos da un archivo global.css para nuestros estilos globales.

Otra gran diferencia es que utiliza rollup para crear el servidor de desarrollo y el build de producción, el resto utiliza webpack.

Estructura de archivos de Vue

Vue al igual que React solo mantiene el favicon y el html base en public.

El resto del código, estilos, imagen y archivos se encuentran en el folder src.

Lo maravilloso de Vue es que nos devuelve archivos como babel.config.js o .eslintrc.js para que modifiques la configuración de tu entorno de desarrollo muy fácilmente.

Estructura de archivos de Angular

La estructura de carpetas en Angular parece más compleja a primera vista sin embargo contiene más herramientas y funcionalidades al momento de crear el proyecto con el CLI.

Contamos con una carpeta e2e donde se incluyen todos los archivos y configuraciones necesarias para realizar End To End Testing, aparte todos los componentes se crean con un archivo .spect.ts para realizar Unit Testing.

En la raíz del proyecto podemos encontrar varios archivos json para la configuración de TypeScript y Angular. Entre ellos podemos encontrar angular.json donde viene el especificado el path del template html, el favicon, los assets del proyecto así como el linter y otras configuraciones.

Otra carpeta que genera el CLI es enviroments donde viene especificada la configuración del ambiente de desarrollo y de producción.

El último detalle es que hay archivos para implementar servicios como peticiones HTTP.

¿Cómo se inicializa la aplicación?

Para inicializar una aplicación con React solo necesitas importar react y react-dom en el archivo raíz de tu aplicación.

import React from 'react';
import ReactDOM from 'react-dom';

Y a continuación debes renderizar el componente principal de tu aplicación en el virtual DOM de React dentro de algún elemento HTML.

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

Inicializar una app con Svelte es super sencillo, solo necesitas crear una nueva instancia del componente principal de tu aplicación al que le pasaras un objeto de configuración con la propiedad target indicándole donde se renderizará la aplicación.

import App from './App.svelte';

const app = new App({
    target: document.body,
});

export default app;

En este framework necesitas crear una nueva instancia de Vue indicándole el componente a renderizar y después utilizar $mount() para indicarle en que elemento HTML será renderizada.

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
  render: h => h(App)
}).$mount('#app')

Angular es un caso muy especial pues es un framework muy robusto que utiliza es por eso que tiene un CLI superpoderoso que crea la decena de archivos que necesita.

Mientras que con React, Vue o Svelte puedes configurar por tu cuenta tu propio proyecto fácilmente para asegurar que puedes personalizar cada detalle de tu aplicación o incluso utilizar las tecnologías directamente desde un CDN en una etiqueta script, hacerlo con Angular sería una tarea algo complicada y 0 recomendada.

Sin embargo así luce el archivo main.ts

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

// AppModule contiene todas las declaraciones de Componentes
// Y los Modulos de Angular a utilizar etc.
import { AppModule } from './app/app.module';

// Tienes diferentes archivos de configuración segun el entorno
import { environment } from './environments/environment';

if (environment.production) {
  //Corre con caracteristicas especificas de producción
  enableProdMode();
}

// No tenemos que decirele donde renderizar la aplicación
// Podemos utilzar esta etiqueta directamente en el html:
// <app-root></app-root>
platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(err => console.error(err));

¿Cómo creó mis componentes?

A partir de aquí iremos creando una Pokédex con las 4 diferentes tecnologías.

La Pokédex tendrá 3 componentes:

  • <Pokedex />: Contiene el estado y hace las peticiones a la API.
  • <PokedexScreen />: Muestra la información del Pokémon y si ha ocurrido un error.
  • <PokedexForm />: Nos ayuda a encontrar cualquier Pokémon.
  • <Stat />: Su función es renderizar cada una de las estadísticas del Pokémon.

Puedes crearlos después de mirar el ejemplo que hicimos con <Pokedex /> y más adelante exploraremos la lógica y el HTML de cada uno de ellos.

Para crear un componente en React creamos un archivo Pokedex.js donde importamos React y todo lo que vayamos a necesitar.

Posteriormente creamos una función Pokedex que devolverá nuestro código similar a HTML llamado JSX y la exportamos para poder utilizarlo en cualquier otro sitio.

import React from 'react'
import PokedexScreen from './PokedexScreen'
import PokemonForm from './PokemonForm'

function Pokedex(){
  return (
    <div className="pokedex">
      <div className="pokedex-left">
        <div className="pokedex-left-top">
          <div className='light is-sky is-big'/>
          <div className="light is-red" />
          <div className="light is-yellow" />
          <div className="light is-green" />
        </div>
        <div className="pokedex-screen-container">
          <PokedexScreen />
        </div>
        <div className="pokedex-left-bottom">
          <div className="pokedex-left-bottom-lights">
            <div className="light is-blue is-medium" />
            <div className="light is-green is-large" />
            <div className="light is-orange is-large" />
          </div>
          <PokemonForm />
        </div>
      </div>
      <div className="pokedex-right-front" />
      <div className="pokedex-right-back" />
    </div>
  )
}

export default Pokedex

Nuestros componentes vivirán dentro de un archivo Pokedex.svelte que tendrá tres secciones principales.

En la primera importaremos nuestros componentes hijos y también colocaremos la lógica del componente dentro de una etiqueta script.

<script>
  import PokedexScreen from './PokedexScreen.svelte'
  import PokemonForm from './PokemonForm.svelte'
</script>

En la siguiente sección colocaremos el HTML de nuestro componente.

<div class="pokedex">
  <div class="pokedex-left">
    <div class="pokedex-left-top">
      <div class="light is-sky is-big"/>
      <div class="light is-red" />
      <div class="light is-yellow" />
      <div class="light is-green" />
    </div>
    <div class="pokedex-screen-container">
      <PokedexScreen />
    </div>
    <div class="pokedex-left-bottom">
      <div class="pokedex-left-bottom-lights">
        <div class="light is-blue is-medium" />
        <div class="light is-green is-large" />
        <div class="light is-orange is-large" />
      </div>
      <PokemonForm />
    </div>
  </div>
  <div class="pokedex-right-front" />
  <div class="pokedex-right-back" />
</div>

Y finalmente colocaremos los estilos del componente en la etiqueta correspondiente

<style>
  .pokedex {
    perspective: 1000px;
    position: relative;
    overflow: hidden;
    height: 31rem;
    width: 23rem;
    margin: 0 auto;
    overflow: initial;
    box-shadow: 0 2px 12px -2px rgba(255, 0, 0, 0.4);
  }
</style>

Nuestro componente Pokédex en Vue se encontrará dentro de Pokedex.vue con una sección témplate donde colocaremos el HTML de la aplicación.

Nótese que en Vue, en vez de utilizar PokedexScreen dentro del templarte utilizamos pokedex-screen, esto para lograr una sintaxis más unanimé con el resto del HTML.

<template>
  <div class="pokedex">
    <div class="pokedex-left">
      <div class="pokedex-left-top">
        <div class="light is-sky is-big in-animated"/>
        <div class="light is-red" />
        <div class="light is-yellow" />
        <div class="light is-green" />
      </div>
      <div class="pokedex-screen-container">
        <pokedex-screen />
      </div>
      <div class="pokedex-left-bottom">
        <div class="pokedex-left-bottom-lights">
          <div class="light is-blue is-medium" />
          <div class="light is-green is-large" />
          <div class="light is-orange is-large" />
        </div>
        <pokedex-form/>
      </div>
    </div>
    <div class="pokedex-right-front" />
    <div class="pokedex-right-back" />
  </div>
</template>

En la siguiente sección, en la etiqueta script donde importaremos y declararemos los componentes que utilizaremos. En el futuro colocaremos todos nuestros métodos y nuestro estado dentro del objeto exportado por default.

<script>
import PokedexScreen from './PokedexScreen.vue'
import PokedexForm from './PokedexForm.vue'

export default {
  name: 'Pokedex',
  components: {
    PokedexScreen,
    PokedexForm
  },
}
</script>

Aquí utilizaremos una herramienta de Angular CLI para generar la estructura y los archivos necesarios para nuestros componentes.

ng generate component pokedex

Esto creará una carpeta llamada pokedex dentro de la aplicación con 4 archivos:

  1. pokedex.component.css será para los estilos.
  2. pokedex.component.html será para el código HTML.
  3. pokedex.component.spect.ts será para implementar nuestros test.
  4. pokedex.component.ts será el archivo donde irá toda la lógica asociada al componente.

Dentro del archivo pokedex.component.ts encontraremos este código, como puedes ver, todo está configurado y listo para usarse!. Lo único que debemos de tomar en cuenta es que selector será la etiqueta que usaremos para referirnos a este componente del HTML.

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'pokedex',
  templateUrl: './pokedex.component.html',
  styleUrls: ['./pokedex.component.css']
})
export class PokedexComponent implements OnInit {
  constructor() { }

  ngOnInit() { }
}

Y dentro de pokedex.component.html colocaremos el código HTML del componente.

<div class="pokedex">
  <div class="pokedex-left">
    <div class="pokedex-left-top">
      <div class="light is-sky is-big"></div>
      <div class="light is-red"></div>
      <div class="light is-yellow"></div>
      <div class="light is-green"></div>
    </div>
    <div class="pokedex-screen-container">
      <app-pokedex-screen></app-pokedex-screen>
    </div>
    <div class="pokedex-left-bottom">
      <div class="pokedex-left-bottom-lights">
        <div class="light is-blue is-medium"></div>
        <div class="light is-green is-large"></div>
        <div class="light is-orange is-large"></div>
      </div>
      <app-pokedex-form></app-pokedex-form>
    </div>
  </div>
  <div class="pokedex-right-front"></div>
  <div class="pokedex-right-back"></div>
</div>

Si has analizado React, Vue o Svelte anteriormente, notarás que en Angular no podemos usar etiquetas que se cierran a sí mismas como <div />, además otra gran diferencia es que aquí no estamos importando nuestro componente app-pokedex-screen ni app-pokedex-form en ningún sitio.

Angular necesita que importes y declares todos tus componentes dentro del archivo app.module.ts que fue creado al inicializar la aplicación:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { PokedexComponent } from './pokedex/pokedex.component';
import { PokedexScreenComponent } from './pokedex-screen/pokedex-screen.component';
import { PokedexFormComponent } from './pokedex-form/pokedex-form.component';

@NgModule({
  declarations: [
    AppComponent,
    PokedexComponent,
    PokedexScreenComponent,
    PokedexFormComponent
  ],
  imports: [
    BrowserModule,
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

¿Cómo utilizo los estilos?

Llegó el momento de añadirle estilos a nuestras aplicaciones.

Puedes encontrar todos los estilos de la Pokédex y agregarlos a tu app aquí.

Puedes agregarlos de manera global o dividirlos por componentes si deseas.

Para agregarles estilos a tus componentes en React solo necesitas importar tus hojas de estilos donde las necesites.

create-react-app utiliza por defecto archivos CSS, pero puedes añadir fácilmente tu preprocesador favorito ya sea Sass, Less o Stylus.

import './styles/pokedex.css';

import './styles/pokedex.sass';

Si quieres que tus estilos sean más poderosos puedes agregar styled-components.

import styled from 'styled-components'

const PokedexStyled = styled.div`
  height: 31rem;
  width: 23rem;
`

Es importante recordar que en React utilizamos className, en vez de class para asignarle una clase de CSS a nuestros elementos debido a que class es una palabra reservada en JavaScript

Para agregarle estilos a tus componentes en Svelte solo necesitas agregarlos dentro de una etiqueta style antes o después de la estructura de tu componente.

<div class="pokedex">
</div>

<style>
  .pokedex {
    height: 31rem;
    width: 23rem;
  }
<style>

Si deseas utilizar estilos globales, solo necesitas declararlos dentro del archivo global.css en la carpeta public.

En Vue generalmente le agregas los estilos a tu componente después de la etiqueta template y la etiqueta script

<template>
  <div class="pokedex">
  </div>
</template>

<style>
  .pokedex {
    height: 31rem;
    width: 23rem;
  }
<style>

Si quieres que tus estilos estén limitados a este componente usas <style scoped> y si quieres que los estilos sean globales solo necesitas ponerlos dentro de una etiqueta style en el componente principal de tu aplicación.

En Angular declaras las Url's de tus hojas de estilos al momento de crear tus componentes:

@Component({
  styleUrls: ['./pokedex.component.css']
})

Como viste anteriormente, si usas ng generate todo esto lo hace Angular por ti para que comiences a codear en unos segundos!

Si deseas utilizar estilos globales, solo necesitas declarar su ubicación dentro de angular.json:

"styles": [
    "src/styles.css"
  ],

¿Cómo utilizo los archivos estáticos?

En esta sección vamos a aprender como agregar imágenes, iconos, ilustraciones vectoriales o cualquier otro tipo de imágenes dentro de nuestros componentes.

Dentro de la Pokédex utilizaremos dos archivos .gif que nos ayudaran a mostrar en la pantalla de la Pokédex el estado de carga y error mientras hacemos una petición a la PokeAPI.

Error Pokédex ErrorError Pokédex Preview

Para utilizar archivos estáticos en React como .jpg, .png, .svg, .gif necesitas importarlos y después asignarlos en el atributo src correspondiente

import ErrorPokemon from '../img/error.gif'

<div className="pokedex-screen">
  <img
    src={ErrorPokemon}
    alt="Hubo un error buscando tu pokemon"
    className="pokedex-no-screen"
  />
</div>

En Svelte solo es necesario que declares la ruta del archivo que necesitas

<img
  src='img/error.gif'
  alt="Hubo un error buscando tu pokemon"
  class="pokedex-no-screen"
/>

Sin embargo no es una ruta relativa. En Svelte, siempre debes de colocar tus archivos estáticos dentro del directorio public y declarar la ruta a seguir dentro de esta carpeta.

Tus archivos estáticos en Vue son utilizados dentro de tu componente, indicando la ruta relativa del archivo dentro del atributo src correspondiente.

<img
  v-if="error"
  src='../assets/error.gif'
  alt="Hubo un error buscando tu pokemon"
  class="pokedex-no-screen"
/>

Utilizando Angular debes indicar la ruta absoluta de los archivos con relación a las carpetas señaladas en tu archivo de configuración angular.json

// Dentro de `angular.json`
"assets": [
  "src/favicon.ico",
  "src/assets"
],

// Dentro de tu componente
<img
  *ngIf="error"
  src="/assets/error.gif"
  alt="Hubo un error buscando tu pokemon"
  class="pokedex-no-screen"
/>

¿Cómo creó y actualizo el estado?

El estado de nuestra Pokédex tendrá cuatro piezas básicas:

  • loading: Mientras estamos buscando un Pokémon, será verdadero.
  • error: Si ocurre un error en API o un error de red, este será verdadero.
  • pokemon: Guardará todos los datos de nuestro Pokémon.
  • pokemonID: Guardará el ID o el nombre el Pokémon que estamos buscando.

Dentro de las 4 versiones de la Pokédex encontrarás un código muy similar a este:

// Creamos un número aleatorio entre el 1 y el 807
const RandomId = Math.floor(Math.random() * 806 + 1)

Nosotros creamos un ID aleatorio para tener los datos de Pokémon desde el momento en que se renderiza la Pokédex y utilizamos un número entre el 1 y el 807 porque solo existen 807 Pokémones.

Para crear el estado de un componente en React es necesario importar el hook de useState de react.

import React, { useState } from 'react'

function Pokedex(){
  const [ error, setError ] = useState(false)
}

Para crearlo a useState le pasamos el valor inicial y de lo que devuelve extraemos el nombre del estado y la función que modificara al estado que usamos de la siguiente manera,

setError(true)
//error = true

Después de crear todos los estados necesarios para la Pokédex, nuestro componente se ve así:

function Pokedex(){
  const [ error, setError ] = useState(false)
  const [ loading, setLoading ] = useState(true)
  const [ pokemon, setPokemon ] = useState(null)
  const RandomId = Math.floor(Math.random() * 806 + 1)
  const [ pokemonID, setPokemonId ] = useState(RandomId)

  return ( //...
}

Svelte maneja el estado de la manera más sencilla posible, solo necesitas crear variables con let y después puedes cambiar su valor como normalmente lo harías en JavaScript.

  let error = false
  let loading = false
  let pokemon = null
  const RandomId = Math.floor(Math.random() * 806 + 1)
  let pokemonId = RandomId.toString()


  // Puedes cambiar los valores en tu estado muy fácilmente
  error = true
  pokemonId = 25

Debido a que Svelte ya soporta TypeScript, esto podría cambiar un poco.

El estado en Vue es creado dentro de un objeto que devuelve la función data.

export default {
  name: 'Pokedex',
  components: {
    PokedexScreen,
    PokedexForm
  },
  data () {
    return {
      error: false,
      loading: true,
      pokemon: null,
      pokemonId: Math.floor(Math.random() * 806 + 1).toString()
    }
  },
}

Puedes actualizarlo directamente haciendo referencia a alguno de los valores ya declarados.

this.error = true
// error = true

Crear el estado de tu componente es muy sencillo, solo necesitas declarar las variables al principio de tu componente y en el futuro puedes cambiar sus valores directamente referenciándolas como this.value.

Como angular soporta de manera nativa TypeScript puedes especificar cuál será el tipo de valor que tendrá cada elemento del estado.

public error:Boolean = false
public loading:Boolean = true
public pokemon:Object = null
public pokemonID:String = Math.floor(Math.random() * 806 + 1).toString()


// Para cambiar el estado
this.error = true
// error = true

¿Cómo traigo datos de una API?

En esta sección aprenderemos como hacer una petición a una API cuando nuestro componente se renderiza.

Utilizaremos la PokeAPI y la ruta que nos permitirá buscar a nuestros pokémones por nombre o por ID será https://pokeapi.co/api/v2/pokemon/${pokemonId}

Recuerda que debemos cambiar el estado de la Pokédex dependiendo de la respuesta de nuestra API.

Todas los frameworks tienen sus propios métodos para implementar este tipo de soluciones, pero aquí decidimos hacerlo así para practicar más conceptos sin instalar ningún paquete adicional.

Llamar a una API con React es super sencillo. Solo necesitamos crear la función que hará el llamado a la API y luego ejecutarla cuando se creé el componente. Esto se logra utilizando el hook de useEffect y pasándole un array vacío como segundo parámetro.

Si aplicamos estos conceptos en nuestra aplicación, luce así:

import React, { useState, useEffect } from 'react'
// El resto de imports

function Pokedex(){
  const [ error, setError ] = useState(false)
  const [ loading, setLoading ] = useState(true)
  const [ pokemon, setPokemon ] = useState(null)
  const RandomId = Math.floor(Math.random() * 806 + 1)
  // Inicamos con ID random para siempre tener un pokemón
  const [ pokemonID, setPokemonId ] = useState(RandomId)

  // Solamente esta cargando mientras hacemos la petición,
  // cuando esta se resuelve o fue un éxito u un error.
  useEffect(() => {
    fetch(`https://pokeapi.co/api/v2/pokemon/${pokemonID}`)
      .then(res => res.json())
      .then(data => {
        // Si todo esta cool, actualizamos el pokemón
        // Y le indicamos que no hay error
        setPokemon(data)
        setLoading(false)
        setError(false)
      })
      .catch(err => {
        setLoading(false)
        setError(true)
      })
  }, [])
// Pokedex.js
}

Para realizar llamados a una API con Svelte necesitamos crear la función que hará la petición y luego ejecutarla cuando se monte el componente. Esto se logra utilizando la función onMount() de Svelte.

Al aplicarlo a la Pokédex, esto luce así:

  // Solamente esta cargando mientras hacemos la petición,
  // cuando esta se resuelve o fue un éxito u un error.
  const getPokemon = () => {
    fetch(`https://pokeapi.co/api/v2/pokemon/${pokemonId}`)
      .then(res => res.json())
      .then(data => {
        pokemon = data
        loading = false
      })
      .catch(err => {
        loading = false
        error = true
      })
  }

  onMount(() => {
    getPokemon()
  })

En Vue debemos crear la función que hace una petición una API dentro de los métodos y después debe ser llamada dentro de la función created para que se ejecute cuando se creé o se monte el componente.

<script>
import PokedexScreen from './PokedexScreen.vue'
import PokedexForm from './PokedexForm.vue'

export default {
  name: 'Pokedex',
  components: {
    PokedexScreen,
    PokedexForm
  },

  data () {
    return {
      error: false,
      loading: true,
      pokemon: null,
      pokemonId: Math.floor(Math.random() * 806 + 1).toString()
    }
  },

  created () {
    this.getPokemon()
  },

  methods: {
    // Mientras se llama a la API, loading es verdadero,
    // cuando la API nos da una respuesta, el estado se actualiza
    // dependiendo de si todo fue un éxito o un fracaso
    getPokemon () {
      fetch(`https://pokeapi.co/api/v2/pokemon/${this.pokemonId}`)
        .then(res => res.json())
        .then(data => {
          this.pokemon = data
          this.loading = false
        })
        .catch(err => {
          this.loading = false
          this.error = true
        })
    },
  }
}
</script>

Angular también nos permite crear todo lo necesario para hacer peticiones a nuestra API desde el CLI, solo debes correr el comando:

ng generate service http    // abreviado como ng g s http

Este comando nos genera un par de archivos necesarios, entre ellos http.service.ts que justamente donde agregaremos la función que usaremos para buscar a nuestros Pokémon, pero primero debemos de agregar el Cliente HTTP dentro de nuestro servicio HTTP, que nos permitirá comunicarnos con una API pública.

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http'; // Añadimos el cliente

@Injectable({
  providedIn: 'root'
})
export class HttpService {

  constructor(private http: HttpClient) { } // Añadimos el cliente

  // Añadimos el metodo que traera nuestros datos
  getPokemon(pokemonId) {
    return this.http.get(`https://pokeapi.co/api/v2/pokemon/${pokemonId}`)
  }
}

También debemos de declarar el módulo de HTTP dentro de app.module.ts para indicarle a Angular que lo estamos usando:

import { HttpClientModule } from '@angular/common/http';  // Añadimos

@NgModule({
  imports: [
    BrowserModule,
    HttpClientModule   // Lo añadimos a la aplicación
  ],

Finalmente ya podemos usar nuestra función getPokemon, dentro de pokedex.component.ts a la cual debemos de suscribirnos con suscribe() y pasarle dos funciones que se ejecutaran dependiendo de si todo es un éxito o un error. Nosotros colocamos todo esto dentro de la función searchPokemon pues en el futuro necesitaremos llamar a la API cuando se envíe el formulario.

//importamos nuestro servicio en pokedex.component.ts
import { HttpService } from '../http.service'

export class PokedexComponent implements OnInit {
  public error:Boolean = false
  public loading:Boolean = true
  public pokemon:Object = null
  public pokemonID:String = Math.floor(Math.random() * 806 + 1).toString()

  // Lo declaramos en el constructor de nuestro componente.
  constructor(private _http: HttpService) { }

  ngOnInit(): void {
    this.searchPokemon()
  }

  // Nuestra función
  searchPokemon(): void {
    // Este es el metodo creado en http.service.ts
    this._http.getPokemon(this.pokemonID)
      .subscribe(
        data => {
          this.pokemon = data
          this.loading = false
          this.error = false
        },
        error => {
          this.pokemon = null
          this.loading = false
          this.error = true
        }
      )
  }
}

¿Cómo le paso información a los hijos?

Siempre queremos transmitir información de componentes padres a componentes hijos para separar las responsabilidades y para tener un código mucho más modular y más legible.

Dentro de la Pokédex debemos de pasar los datos que nos devuelve la API, así como el estado de carga y error para que <PokedexScreen /> muestre la información necesaria.

Dentro de todos los snippets de código encontrarás un ul vacío donde en la siguiente sección iteraremos sobre la lista de estadísticas de los pokémones.

Pasar datos de padres a hijos con React se hace de una manera muy intuitiva. Solo tenemos que agregarle las propiedades y asignarles un valor. Generalmente el valor y la propiedad llevan el mismo nombre.

//...
  <PokedexScreen
    pokemon={pokemon}
    loading={loading}
    error={error}
  />
//...

¿Cómo recibimos los valores en nuestro componente hijo?

Recibimos los valores en <PokedexScreen /> extrayéndolos de las props.

Para mostrarlos en pantalla, dentro JSX podemos colocar cualquier expresión de JavaScript entre llaves {}, esta técnica aplica tanto al texto que mostraremos en pantalla como {pokemon.name}, como para los atributos de nuestros elementos html como src o alt etc.

import React from 'react'

function PokedexScreen({ pokemon, loading, error }){
  return (
    <div className="pokedex-screen">
      <div className="pokemon-info">
        <h2 className="pokemon-name">{pokemon.name}</h2>
        <img
          className="pokemon-img"
          src={pokemon.sprites.front_default}
          alt={pokemon.name}
        />
        <ul className="pokemon-stats">
          // Aquí iteraremos sobre la lista de estadísticas
        </ul>
      </div>
    </div>
  )
}

export default PokedexScreen

Cuando estamos trabajando con Svelte y queremos pasar datos de un componente padre a un componente Hijo, solo debemos crear nuevas propiedades para el componente y asignarles un valor. Usualmente el nombre y el valor es el mismo.

<PokedexScreen
  pokemon={pokemon}
  loading={loading}
  error={error}
/>

¿Cómo mostramos los valores en pantalla?

Primero los recibimos dentro de la etiqueta script con la sintaxis: export let nombre-del-valor.

<script>
  export let error
  export let loading
  export let pokemon
</script>

Y posteriormente lo integramos a nuestro HTML, colocando los valores entre llaves {}, esto funciona para mostrar texto y para asignar atributos a los elementos.

<div class="pokemon-info">
  <h2 class="pokemon-name">{pokemon.name}</h2>
  <img class="pokemon-img" src={pokemon.sprites.front_default} alt={pokemon.name} />
  <ul class="pokemon-stats">
    // Aqui iteraremos sobre la lista de estadisticas
  </ul>
</div>

Las propiedades que deseamos pasar a nuestros componentes hijos deben "enlazarse" utilizando el prefijo v-bind:

<pokedex-screen
  v-bind:pokemon="pokemon"
  v-bind:loading="loading"
  v-bind:error="error"
/>

¿Como recibimos los valores en nuestro componente hijo?

Los datos los recibimos dentro del objeto props, Vue nos permite especificar que tipo de dato son y también asignarles un valor por defecto para que la aplicación no se rompa si no recibimos ningún valor desde el padre por una excepción o por un error.

  props: {
    // PropTypes on Vue
    pokemon: {
      type: Object,
      default: () => {}
    },
    error: {
      type: Boolean,
      default: () => false
    },
    loading: {
      type: Boolean,
      default: () => false
    }
  }

¿Cómo mostramos los valores en pantalla?

En Vue mostramos los valores de 2 maneras diferentes:

  • Cuando queremos mostrar texto, como el nombre del Pokémon pokemon.name por ejemplo, usamos llaves dobles {{pokemon.name}}}.

  • Cuando queremos asignar atributos usamos el prefijo v-bind: o su abreviación :.

<template>
  <div class="pokedex-screen">
    <div class="pokemon-info" v-else>
      <h2 class="pokemon-name">{{pokemon.name}}</h2>
      <img
        class="pokemon-img"
        v-bind:src="pokemon.sprites.front_default"
        :alt="pokemon.name" />
      <ul class="pokemon-stats">
      // Aqui iteraremos sobre las estadisticas
      </ul>
    </div>
  </div>
</template>

En Angular pasar datos de padres a hijos es sumamente sencillo. Solo debemos asignarle ciertos atributos a nuestro componente y asignarles un valor como en este ejemplo:

<app-pokedex-screen
  [loading]='loading'
  [error]='error'
  [pokemon]='pokemon'
></app-pokedex-screen>

¿Cómo recibimos los valores en nuestro componente hijo?

Utilizando la función Input de Angular podemos recuperar las valores dentro de pokedex-screen.component-ts y de paso podemos declarar que tipos de valores son aceptados gracias a TypeScript.

import { Component, OnInit, Input } from '@angular/core';

@Component({
  selector: 'app-pokedex-screen',
  templateUrl: './pokedex-screen.component.html',
  styleUrls: ['./pokedex-screen.component.css']
})
export class PokedexScreenComponent implements OnInit {
  @Input() public loading: boolean;
  @Input() public error: boolean;
  @Input() public pokemon: object;

  constructor() { }

  ngOnInit() {}

}

¿Como mostramos los valores en pantalla?

En Angular para asignar valores dentro de nuestro html siempre usamos llaves dobles {{}} ya sea para mostar texto como {{pokemon.name}} o para asignar atributos como src={{pokemon.sprites.front_default}}.

<div class="pokedex-screen">
  <div class="pokemon-info">
    <h2 class="pokemon-name">{{pokemon.name}}</h2>
    <img class="pokemon-img" src={{pokemon.sprites.front_default}} alt={pokemon.name}} />
    <ul class="pokemon-stats">

    </ul>
  </div>
</div>

¿Cómo renderizo una lista?

Llegó el momento de aprender a generar una serie de componentes a partir de la información que recibimos en un array.

Primero vamos a analizar un ejemplo sencillo donde iteraremos una lista de gatos para mostrar el nombre de cada gatito con cada una de las tecnologías.

Después analizaremos como renderizar las estadísticas de un Pokémon para conocer sus fortalezas y sus debilidades.

Pokédex Statas

Para iterar sobre los elementos de una lista y mostrar cada uno de los datos en pantalla con React puedes utilizar alguno de los métodos de los array como map.

Cada uno de los elementos de la lista debe de tener un identificador único, como un id, llamado key.

<ul>
  {cats.map(cat => (
    <li key={cat.id}>
      {cat.name}
    </li>
  )}
</ul>

Cuando añadimos este concepto a la aplicación, luce así:

import Stat from './Stat.js'
// Pokedex.js ...
<ul className="pokemon-stats">
  {pokemon.stats.map(item => (
    <Stat key={item.stat.name} item={item}/>
  ))}
</ul>
// Pokedex.js ...


// Stat.js...
import React from 'react'

function Stat({ item }){
  return (
    <li className="pokemon-stat">
      <span className="stat-name"><b>{item.stat.name}: </b></span>
      <span>{item.base_stat}</span>
    </li>
  )
}

export default

Si quieres iterar sobre un array y mostrar un componente para cada uno de los elementos, Svelte te proporciona un método {#each} que se utiliza de la siguiente manera.

<ul>
  // Para cada gato de la lista de gatos imprime una etiqueta <li> con el nombre del gato.
  // El identicador unico del componente es el segundo parametro y será el id del gato.
    {#each cats as cat (cat.id)}
    <li>{cat.name}</li>
    {/each}
</ul>

Este concepto en nuestra aplicación nos queda de la siguiente manera.

// Pokedex.svelte ...
<script>
  import Stat from './Stat.svelte'
</script>
// Pokedex.svelte...
<ul class="pokemon-stats">
  {#each pokemon.stats as item (item.stat.name)}
    <Stat item={item}/>
  {/each}
</ul>
// Pokedex.svelte...


// Debtro de Stat.svelte...
<script>
  export let item
</script>

<li class="pokemon-stat">
  <span class="stat-name">
    <b>{item.stat.name}: </b>
  </span>
  <span>{item.base_stat}</span>
</li>

Si estamos programando una aplicación con Vue y necesitamos iterar en un array para renderizar una serie de elementos, Vue no da la función v-for para iterar sobre el array y v-bind:key para asignarle un identificador único a cada elemento de la lista.


<ul>
  <li v-for="cat in cats" v-bind:key="cat.id">
    {cat.name}
  </li>
</ul>

Este concepto en nuestra aplicación luciría así:

// ...Pokedex.vue
<ul class="pokemon-stats">
  <Stat
    v-bind:item="item"
    v-for="item in pokemon.stats"
    v-bind:key="item.stat.name"
  />
</ul>
// ...Pokedex.vue
<script>
import Stat from './Stat.vue'
export default {
  name: 'PokedexScreen',
  components: {
    Stat
  },
<script>
// ...Pokedex.vue


// Dentro de Stat.vue
<template>
  <li class="pokemon-stat">
    <span class="stat-name">
      <b>{{item.stat.name}}: </b>
    </span>
    <span>{{item.base_stat}}</span>
  </li>
</template>

<script>
export default {
  name: 'Stat',
  props: {
    // PropTypes on Vue
    item: {
      type: Object,
      default: () => {}
    }
  }
}
</script>

Angular nos da *ngFor para iterar sobre una lista y matar un componente para cada elemento de ella y trackBy para darle un identificador único a cada elemento.

  <ul>
    <li *ngFor="let cat of cats; trackBy: cat.id" [cat]="cat">
      {{cat.name}}
    </li>
  </ul>

¿Como usamos *nfFor en la aplicación?

// pokedex-screen.component.html ...
<ul class="pokemon-stats">
  <app-stat>
    class="pokemon-stat"
    *ngFor="let item of pokemon.stats;"
    [item]="item"
  >
  </app-stat>
</ul>
// pokedex-screen.component.html ...


// stat.component.ts
import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-stat',
  templateUrl: './stat.component.html',
  styleUrls: ['./stat.component.css']
})
export class StatComponent {
  @Input() item;
}
// stat.component.html
<li class="pokemon-stat">
  <span class="stat-name"><b>{{item.stat.name}}: </b></span>
  <span>{{item.base_stat}}</span>
</li>

Componentes condiciones

En esta sección vamos a explorar como renderizar componentes dependiendo del estado en nuestra aplicación.

Como recordarás, en esta sección creamos estados de carga y error que le pasamos a <PokedexScreen/>. Esto lo utilizaremos para que la pantalla luzca diferente si está buscando un Pokémon, si lo ha encontrado o si ha ocurrido algún error.

En realidad existen librerías especializadas para manejar los estados de las peticiones a una API, aquí lo haremos utilizando el estado, ya que el objetivo era crear una aplicación sencilla, pero donde utilizáramos la mayoría de las piezas básicas de una aplicación moderna.

React es súper versátil para renderizar componentes de manera condicional, gracias a ser una librería, ya que nos permite utilizar todas las estructuras lógicas de JavaScript como if/else, switch, y sus abreviaciones del tipo ? / : o &&. Así que en realidad podemos lograr el mismo fin de muchas maneras diferentes. Aquí te mostraremos dos de las opciones posibles.

function PokedexScreen({ pokemon, loading, error }){
  // Si hay un error en la petición a la API, devuelve este componente.
  // Recuerda que al hacer un return, el resto de código, no se ejecutará.
  if(error){
    return (
      <div className="pokedex-screen">
        <img
          src={ErrorPokemon}
          alt="Hubo un error buscando tu pokemon"
          className="pokedex-no-screen"
        />
      </div>
    )
  }

  // Si ya pasamos la validación del error...
  return (
    <div className="pokedex-screen">
      { !pokemon || loading ? // Si no hay pokemon o si esta cargando
        <img
          src={LoadingPokemon}
          alt="Aun no hay ningun pokemon"
          className="pokedex-no-screen"
        /> : // Todo cool, entonces devuelve un lindo pokemon
        <div className="pokemon-info">
          <h2 className="pokemon-name">{pokemon.name}</h2>
          <img
            className="pokemon-img"
            src={pokemon.sprites.front_default}
            alt={pokemon.name}
          />
          <ul className="pokemon-stats">
            {pokemon.stats.map(item => <Stat key={item.stat.name} item={item}/>)}
          </ul>
        </div>
      }
    </div>
  )
}

Svelte es un framework que nos da las herramientas necesarias para crear la lógica de nuestra aplicación, en el caso de los componentes condicionales podemos usar {#if}, {:elese if} y {:else}.

Sin embargo al tratarse de peticiones a una API, Svelte nos da herramientas para manejar esto sin un estado y sin instalar librerías adicionales gracias a que tiene las herramientas {#await}, {:then}, {:catch}. Pero aquí usaremos condicionales para comparar mejor las librerías/frameworks.

<div class="pokedex-screen">
  // Habrimos un bloque condicional
  // Si ocurre un error, entonces...
  {#if error}
    <img
      src='img/error.gif'
      alt="Hubo un error buscando tu pokemon"
      class="pokedex-no-screen"
    />
  // Si no hay un pokemon o esta cargando, entonces...
  {:else if !pokemon || loading}
    <img
      src='img/loading.gif'
      alt="Aun no hay ningun pokeom"
      class="pokedex-no-screen"
    />
  // Todo bien hasta ahora, entonces...
  {:else}
    <div class="pokemon-info">
      <h2 class="pokemon-name">{pokemon.name}</h2>
      <img class="pokemon-img" src={pokemon.sprites.front_default} alt={pokemon.name} />
      <ul class="pokemon-stats">
        {#each pokemon.stats as item (item.stat.name)}
          <Stat item={item}/>
        {/each}
      </ul>
    </div>
  {/if}
  // Cerramos el {#if}
</div>

Vue, al ser un Framework, nos da los atributos v-if, v-else-if y v-else para manejar nuestros componentes condicionales.

En la Pokédex, los utilizaremos de la siguiente manera:

<template>
  <div class="pokedex-screen">
    <img
    // Si hay un error, entonces...
      v-if="error"
      src='../assets/error.gif'
      alt="Hubo un error buscando tu pokemon"
      class="pokedex-no-screen"
    />
    <img
    // Si esta cargando, entonces...
      v-else-if="loading"
      src='../assets/loading.gif'
      alt="Estamos buscando tu pokemon"
      class="pokedex-no-screen"
    />
    // Si todo esta bien, esta es la opción por defecto...
    <div class="pokemon-info" v-else>
      <h2 class="pokemon-name">{{pokemon.name}}</h2>
      <img
        class="pokemon-img"
        v-bind:src="pokemon.sprites.front_default"
        v-bind:alt="pokemon.name" />
      <ul class="pokemon-stats">
        <Stat
          v-bind:item="item"
          v-for="item in pokemon.stats"
          v-bind:key="item.stat.name"
        />
      </ul>
    </div>
  </div>
</template>

Angular nos da *ngif para que manejemos nuestros componentes condicionales y se utiliza de la siguiente manera.


<div class="pokedex-screen">
  // Si hay un error, entonces...
  <img
    *ngIf="error"
    src="/assets/error.gif"
    alt="Hubo un error buscando tu pokemon"
    class="pokedex-no-screen"
  />
  // Si esta cargando y no hay un error, entonces...
  <img
    *ngIf="loading && !error;"
    src="/assets/loading.gif"
    alt="Aun no hay ningun pokemon"
    class="pokedex-no-screen"
  /> :
  // Si hay un pokemon y no está cargando, ni hay un error, entonces...
  <div class="pokemon-info" *ngIf="pokemon && !loading && !error">
    <h2 class="pokemon-name">{{pokemon.name}}</h2>
    <img class="pokemon-img" src={{pokemon.sprites.front_default}} alt={pokemon.name}} />
    <ul class="pokemon-stats">
      <li
        class="pokemon-stat"
        *ngFor="let item of pokemon.stats;"
      >
        <app-stat [item]="item"></app-stat>
      </li>
    </ul>
  </div>
</div>

Estilos condiciones

Llegó el momento de aprender como agregar estilos a nuestro componente dependiendo del estado.

Dentro de la Pokédex nuestro objetivo es que una de las luces esté parpadeando cuando estemos cargando un nuevo Pokémon sin embargo podríamos lograr cientos de efectos y animaciones aplicando este concepto.

Agregar estilos condicionales en React es fácil gracias a que podemos usar cualquier expresión de JavaScript, en este caso usaremos el condicional &&.

<div className="pokedex-left-top">
  <div className={`light is-sky is-big ${loading && 'is-animated'}`}  />
  <div className="light is-red" />
  <div className="light is-yellow" />
  <div className="light is-green" />
</div>

Si utilizas Styled Components en tu aplicación puedes crear estilos sumamente poderosos, muy fácilmente.

En Svelte podemos utilizar los operadores ? : o && para cambiar las clases de un elemento dependiendo del estado.

<div class="pokedex-left-top">
  <div class={`light is-sky is-big ${loading ? 'is-animated' : ''}`}/>
  <div class="light is-red" />
  <div class="light is-yellow" />
  <div class="light is-green" />
</div>

Mientras creamos una aplicación con Vue, si queremos aplicar estilos condicionalmente bastos con agregarle el prefijo v-bind: o su abreviación : al atributo clase para poder colocar clases usando ? : o &&.

<div class="light is-sky is-big" :class="loading && 'is-animated'"/>
<div >

Angular tiene la manera más elegante de asignarle clases o atributos condicionales a tus elementos, puedes hacerlo con [class] o [ngClass] si quieres hacerlo a través de las clases de CSS, [style.property] si deseas cambiar solo una propiedad o [ngStyle] si deseas cambiar múltiples propiedades.

Agrega el atributo class con el valor is-animated si loading es verdadero.

<div class="pokedex-left-top">
  <div class="light is-sky is-big" [class.is-animates]="loading"></div>
  <div class="light is-red"></div>
  <div class="light is-yellow"></div>
  <div class="light is-green"></div>
</div>

Eventos del DOM y formularios

Vamos a crear un pequeño formulario con un input de texto y un botón que nos ayudará a buscar pokémones.

La PokéApi te permite buscar pokémones por su nombre en minúsculas y por ID, así que vamos a manejar ambos casos.

//Ambas son válidas
'pokeapi.co/api/v2/pokemon/25'

'pokeapi.co/api/v2/pokemon/pikachu'

En algún momento tendremos que validar si el usuario ingreso un número o un nombre y en caso de que haya sido un nombre, debemos de ponerlo en minúsculas, ya que el autócorrector puede hacer que escribamos "Pikachu".

Manejando Inputs y Eventos del DOM en React

React al ser una librería, no nos da ninguna función que actualice el valor del estado según el input del usuario, necesitamos crearla manualmente.

Para manejar el input de texto, le asignamos el valor de nuestro estado con value={pokemon} y cuando se produzca un cambio, escuchamos el evento con onChange, (todos los eventos van en camelCase), y asignamos el valor actual a nuestro de estado con setPokemon(e.target.value).

Para manejar un evento del DOM en React solo necesitas agregarlo a tu elemento y asignarle la función que se ejecutará cuando suceda. Al mandar el formulario ejecutamos la función preventDefault() del evento para evitar que el formulario realice su función de mandar alguna query a través de la URL de la página.

import React, { useState } from 'react'

function PokemonForm(){
  const [ pokemon, setPokemon ] = useState('')

  const handleSubmit = event => {
    event.preventDefault()

  }

  return (
    <form className="pokemon-form" onSubmit={handleSubmit}>
      <input
        className="pokemon-input"
        type="text"
        name="pokemon"
        value={pokemon}
        placeholder="Busca tu pokemon"
        //Actualizas el valor del input cuando el usuario teclea
        onChange={e => setPokemon(e.target.value)}
        autoComplete="off"/>
      <input type="submit" className="pokemon-btn" value=""/>
    </form>
  )
}

export default PokemonForm

Manejando Inputs con Svelte

Para tomar los datos de un input, solo necesitamos enlazar su valor con el valor de algún elemento de nuestro estado utilizando bind:value={state}.

Manejando events del DOM con Svelte

Para manejar un evento del estilo onclick, onsubmit, onchange, solo debemos de colocar el evento con la sintaxis on:click, on:submit, etc. y asignarle una función a ejecutar usando {}, por ejemplo: on:submit={handleSubmit}.

Así lucen ambos conceptos en nuestra aplicación.

<script>
  let pokemonName

  const handleSubmit = e => {
    e.preventDefault()

  }
</script>

<form class="pokemon-form" on:submit={handleSubmit}>
  <input
    class="pokemon-input"
    type="text"
    name="pokemon"
    bind:value={pokemonName}
    placeholder="Busca tu pokemon"
    autocomplete="off"/>
  <input type="submit" class="pokemon-btn" value=""/>
</form>

Manejando Inputs con Vue

Vue nos da un atributo v-model para enlazar algún input de texto y algún valor de nuestro estado así: v-model="stateValue".

Manejando eventos del DOM con Vue

Para manejar eventos como onclick u onsubmit, solo tenemos que ponerlos en el formato v-on:click o v-on:submit y asígnales el método que ejecutaran.

Ambos conceptos lucen así en nuestra aplicación:

<template>
  <form class="pokemon-form" v-on:submit={handleSubmit}>
    <input
      class="pokemon-input"
      type="text"
      name="pokemon"
      v-model="pokemonName"
      placeholder="Busca tu pokemon"
      autocomplete="off"/>
    <input
      type="submit"
      class="pokemon-btn"
      value=""
    />
  </form>
</template>

<script>
export default {
  name: 'PokedexForm',

  data () {
    return {
      pokemonName: ''
    }
  },

  methods: {
    handleSubmit (e) {
      e.preventDefault() // Evitamos que el formulario mande una query por la url
    }
  }
}
</script>

Manejando Inputs con Angular

Para enlazar un input con algún valor de nuestro estado utilizamos el atributo [(ngModel)] y le asignamos algún valor de nuestro estado. Es muy importante añadir el módulo de Formularios, FormsModule, a app.module.ts.

// imports
import { FormsModule } from '@angular/forms';

@NgModule({
  //...
  imports: [
    BrowserModule,
    FormsModule,       // add
  ],
})

Manejando eventos del DOM con Angular

Para manejar los eventos como onclick u onsubmit, Angular nos da atributos equivalentes a los cuales les asignamos la función a ejecutar como (ngSubmit)="handleSubmit($event)".

Asi luce nuestro código de PokedexForm después de añadir el manejo de eventos a la aplicación.

//pokedex-form.compontent.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-pokedex-form',
  templateUrl: './pokedex-form.component.html',
  styleUrls: ['./pokedex-form.component.css']
})
export class PokedexFormComponent{
  pokemonId: string = ''

  constructor() { }

  onSubmit(e) {
    e.preventDefault()
    // Llamamos a la API
    this.pokemonId = '' // Reiniciamos el formulario
  }
}


//pokedex-form.compontent.html
<form class="pokemon-form" (ngSubmit)="handleSubmit($event)">
  <input
    class="pokemon-input"
    type="text"
    [(ngModel)]="pokemonId"
    name="pokemonId"
    placeholder="Busca tu pokemon"
    autoComplete="off">
  <input type="submit" class="pokemon-btn" value="">
</form>

Pasando datos a padres

Nuestro objetivo es pasar los datos de nuestro componente <PokedexForm/> hacia <Pokedex/> para que ejecute la petición necesaria a la API que a su vez le devolverá datos al componente <PokedexScreen/>.

Aquí hay dos tipos de enfoques para lograr que esto suceda, React y Svelte pueden desencadenar eventos siempre que determinada pieza del estado cambia, en este caso el ID de nuestro Pokémon, mientras que Vue y Angular tienen que emitir funciones y valores desde el componente hijo que a su vez actualizarán todo lo necesario en el componente padre como el ID del Pokémon.

Con React, para actualizar el estado de carga y de error en <Pokedex /> cuando mandemos el formulario podemos hacerlo usando nuestras funciones setError() y setLoading(), ya que podemos pasarlas a través de las props, recibirlas en <PokedexForm /> y ejecutarlas de manera normal.

También le pasaremos setPokemonId() para actualizar el ID del Pokémon que estamos buscando.

// ...Pokedex.js
<PokemonForm
  setPokemonId={setPokemonId}
  setLoading={setLoading}
  setError={setError}
/>
//Pokedex.js...


// Dentro de PokedexForm.js
// Recibimos las funciones que actualizan el estado desde las props
function PokemonForm({ setPokemonId, setLoading, setError }){
  const handleSubmit = e => {
    e.preventDefault()
    if(pokemon !== ''){
      // Estara cargando por que hará una petición a la API
      setError(true)
      setLoading(true)
      const pokemonID = window.isNaN(parseInt(pokemon)) ? pokemon.toLowerCase() : pokemon
      setPokemonId(pokemonID)
      setPokemon('')
      return
    }
    setError(true) //Si manda el formulario vacío, hay un error
  }

  return (
    <form className="pokemon-form" onSubmit={handleSubmit}>

Ahora que ya actualizamos el estado de carga y la ID del Pokémon que estamos buscando. Solo necesitamos agregar pokemonID al array de dependencias de nuestro useEffect().

¿Por qué? Porque la función dentro de useEffect se ejecutará cuando cambie el valor de alguno de los elementos incluidos en el array, si el array esta vacío, solo se ejecutará cuando el componente se monte.

useEffect(() => {
  fetch(`https://pokeapi.co/api/v2/pokemon/${pokemonID}`)
    .then(res => res.json())
    .then(data => {
      setPokemon(data)
      setLoading(false)
      setError(false)
    })
    .catch(err => {
      setLoading(false)
      setError(true)
    })
}, [pokemonID])

Para actualizar el estado de carga y de error en <Pokedex /> utilizando Svelte, basta con mandarle los estados a <PokedexForm /> con el prefijo bind: para que queden enlazados los valores y después podremos cambiar los valores de manera normal.

También le pasaremos pokemonId para actualizar el ID del Pokémon que estamos buscando con los datos del formulario.

// ...Pokedex.svelte
<PokemonForm
  bind:pokemonId={pokemonId}
  bind:loading={loading}
  bind:error={error}
/>


// Dentro de PokedexForm.svelte
<script>
  let pokemonName
  // Recibimos los valores de nuestro estado
  export let pokemonId
  export let loading
  export let error

  const handleSubmit = e => {
    e.preventDefault()
    // Loading será true por que hará una petición a la API
    if(pokemonName !== ''){
      error = false
      loading = true
      const newPokemonId = window.isNaN(parseInt(pokemonName)) ? pokemonName.toLowerCase() : pokemonName
      pokemonId = newPokemonId
      pokemonName = '' // Reiniciamos el formulario
      return
    }
    error = true // Si mandas el formulario vacío, hay un error
  }
</script>

<form class="pokemon-form" on:submit={handleSubmit}>

Lo siguiente que queremos hacer, es que si cambia el valor de pokemonID, se debe de ejecutar otra vez la función getPokemon() encargada de buscar nuestro Pokémon en la API. Para lograrlo, solo necesitamos agregar un par de líneas de código.

//Si cambia, entonces..
$: if(pokemonId){
  getPokemon()
}

Cuando completamos el formulario en Vue, debemos emitir un evento con datos hacia el padre.

En nuestro caso emitiremos el evento submit y el ID del Pokémon ingresado.

<template>
  <form class="pokemon-form" on:submit={handleSubmit}>

  </form>
</template>


<script>
export default {
  name: 'PokedexForm',
  data () {
    return {
      pokemonName: ''
    }
  },
  methods: {
    search (e) {
      e.preventDefault()
      const pokemonName = this.pokemonName
      const newPokemonId = window.isNaN(parseInt(pokemonName)) ? pokemonName.toLowerCase() : pokemonName
      this.$emit('submit', newPokemonId)
    }
  }
}
</script>

Ahora necesitamos definir dentro de <Pokedex /> que vamos a escuchar el evento submit y la función que ejecutaremos cuando esto ocurra.

// ...Pokedex.vue
<pokedex-form
  v-on:submit="handleSubmit($event)"
/>
// Pokedex.vue..

Finalmente creamos la función handleSubmit() que validara el ID del Pokémon, actualizara el estado y finalmente realizara la petición a la API usando this.getPokemon().

<script>
//...
export default {
  //...
  methods: {
    getPokemon () {
      //...
    },
    handleSubmit (pokemonId) {
      if (pokemonId !== '') {
        this.error = false
        this.loading = true
        this.pokemonId = pokemonId
        this.getPokemon()
        return
      }
      this.error = true
    }
  }
}
</script>

Para emitir eventos desde un componente hijo hacia el padre, utilizamos dos métodos de angular EventEmitter y Output.

// Importamos lo necesario de `@angular/core`
import { Component, EventEmitter, Output } from '@angular/core';

@Component({
  selector: 'app-pokedex-form',
  templateUrl: './pokedex-form.component.html',
  styleUrls: ['./pokedex-form.component.css']
})
export class PokedexFormComponent{
  //Creamos un nuevo emisor de Eventos
  @Output() submit = new EventEmitter<string>();
  pokemonId: string = ''

  constructor() { }

  onSubmit(e) {
    e.preventDefault()
    // Al mandar el formulario, emitimos el evento con el valor actual del formulario
    this.submit.emit(this.pokemonId)
    this.pokemonId = ''
  }
}

Ahora, ¿Cómo escuchamos el evento submit desde el componente padre?.

Primero debemos de recibirlo dentro de nuestro, témplate en pokedex.component.html y asignarle la función que se ejecutará cuando ocurra.

<app-pokedex-form
  (searchPokemon)='handleSubmit($event)'
></app-pokedex-form>

Finalmente creamos la función handleSubmit() que se encargara de cambiar el estado y de llamar searchPokemon para traer datos desde la API.

export class PokedexComponent implements OnInit {
  // ...
  ngOnInit(): void {
    this.searchPokemon()
  }

  searchPokemon(): void {
    // ...
  }

  handleSubmit(pokemonId) {
    if(pokemonId !== ''){
      this.error = false
      this.loading = true
      this.pokemonID = window.isNaN(parseInt(pokemonId)) ? pokemonId.toLowerCase() : pokemonId
      this.searchPokemon()
      return
    }
    this.error = true
  }
}