¿Que debo aprender primero?

Si ya dominas Html, Css y JavaScript y estas decidido a dar el siguiente paso en tu carrera como desarrollador web aprendiendo a utilizar una de las librerias para construir aplicaciones web modernas, llegaste al lugar correcto.

¿Por donde empezar?, ¿Cual debes aprender?, ¿Cual es el mas facil?
Aqui comparamos React, Vue, Svelte y Angular creando la misma aplicación para que averigues todo lo necesitas saber de estas increibles herramientas.

Si deseas aprender React, Vue, Svelte o Angular puedes seguir un tutorial especifico donde aprenderas todos los conceptos basicos 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 que se parece tu libreria favorita a la libreria de la que todos estan hablando estas en el lugar correcto para explorar los Pros y los Contras, puedes seguir esta lectura mientras comparas dos o mas librerias. Aqui vamos a comparar como las utilizamos mas alla de si utilizan un DOM virtual o si utilizan JSX.

Libreria o Framework.

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

Libreria: Son un conjunto de funciones que podemos llamar dentro de nuestro codigo para realizar tareas comunes y nos ahorran el tener que escribir multiples funciones para resolver los problemas. Son flexibles y utilizan la sintasis que ya conoces. React es una libreria.

Framework: Es un conjunto estandarizado de conceptos, practicas y criterios que nos ayudan a resolver un problema con patrones de diseño especificos. Angular, Svelte y Vue son Frameworks.

Tanto las librerias como los frameworks nos ayudan a resolver problemas complejos con soluciones probadas.

CLIs

Un CLI o Command Line Interface es una manera comoda, facil y rapida de inicializar un nuevo proyecto con las mejores practicas utilizando un solo comando para configuar y comenzar a crear un aplicación increible en unos segundos.

Contienen toda la configuración necesaria para que puedas utilizar las ultimas 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.

Tambien suelen incluir un par de componentes y una landing page basica para que la tomes como referencia, los unicos 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 Estlint por debajo, aunque siempre puedes añadir configuraciones personalizadas con herramientas especificas.

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

Comenzar a utilizar Svelte es super facil gracias al comando:

npx degit sveltejs/template mi-nueva-aplicacion

Su instalación y configuración es de las mas rapidas ademas de que te permite agregar con comandos adicionale TypeScript u otras tecnologias facilmente.

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

npm install -g @vue/cli

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

O puedes decirdir crear una aplicaion, en ese caso necesitas correr el comando:

vue create mi-nueva-aplicacion

A continuacion tendras 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 tecnologias y sus variantes a tu proyecto:

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

Ademas al brindarte los archivos de configuración, puedes agregar mas cosas facilmente.

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

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

El CLI de angular tiene super poderes y continua siendo de gran utilidad durante el desarrollo ya que a travez 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 libreria 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 basicos para iniciar una app como App.js o index.js, ademas de ciertos archivos para configurar el Unit Testing.

Tambien genera una carpeta public donde tenemos los iconos, el manifest y el Html base de nuestra aplicacion.

El resto de nuestro codigo, nuestros estilos y nuestras imagenes lo colocaremos en sus respectivas carpetas dentro de src

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

Estructura de archivos de Svelte

En public ademas del favicon.ico y nuestro html base tambien debemos colocar nuestros archivos estaticos como imagenes, logos e icónos para que sean utilizados por Svelte.

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

Otra gran diferencia es que utiliza rollup para crear el servidor de desarrollo y el build de produccion, 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 codigo, estilos, images 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 facilmente.

Estructura de archivos de Angular

La estructura de carpetas en Angular parece mas compleja a primera vista sin embargo contiene mas 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 raiz del projecto 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 asi 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 ultimo detalle es que hay archivos para implementar servicios como peticiones Http.

¿Como se inicializa la aplicación?

Para inicializa una aplicación con React solo necesitas importar react y react-dom en el archivo raiz 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 virual DOM de React dentro de algun 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 indicandole donde se renderizara 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 indicandole el componente a renderizar y desoues utilizar $mount() para indicarle en que elemento Html sera 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 super poderoso que crea la decena de archivos que necesita.

Mientras que con React, Vue o Svelte puedes configurar por tu cuenta tu propio proyecto facilmente para asegurar que puedes personalizar cada detalle de tu aplicación o incluso utilizar las tecnologias directamente desde un cdn en un script tag, hacerlo con Angular seria una tarea algo complicada y 0 recomendada.

Sin embargo asi 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));

¿Como creo mis componentes?

A partir de aqui iremos creando una Pokedex con las 4 diferentes teconologias.

La Pokedex tendra 3 componentes:

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

Puedes crearlos despues de mirar el ejemplo que hicimos con <Pokedex /> y mas adelante exploraremos la logica 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 devolvera nuestro codigo 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 viviran dentro de un archivo Pokedex.svelte que tendra tres secciones principales.

En la primera importaremos nuestro componentes hijos y tambien colocaremos la logica 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 Pokedex en Vue se encontrara dentro de Pokedex.vue con una seccion template donde colocaremos el html de la aplicación.

Notese que en Vue, en vez de utilizar PokedexScreen dentro del template utilizamos pokedex-screen, esto para lograr una sintaxis mas unirme 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 seccion, el la etiqueta script donde importaremos y declararemos los componentes que utilizaremos. En el futuro colocaremos todos nuestros metodos 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>

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

ng generate component pokedex

Esto creara una carpeta llamada pokedex dentro de la applicación con 4 archivos:

  1. pokedex.component.css sera para los estilos.
  2. pokedex.component.html sera para el codigo html.
  3. pokedex.component.spect.ts sera para implementar nuestros test.
  4. pokedex.component.ts sera el archivo donde ira toda la logica asociada al componente.

Dentro del archivo pokedex.component.ts encontraremos este codigo, como puedes ver, todo esta configurado y listo para usarse!. Lo unico que debemos de tomar en cuenta es que selector sera 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 codigo 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, notaras que en angular no podemos usar etiquetas que se cierran a si mismas como <div />, ademas otra gran diferencia es que aqui no estamos importando nuestro componente app-pokedex-screen ni app-pokedex-form en ningun 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 { }

¿Como utilizo los estilos?

Llego el momento de añadirle estilos a nuestras aplicaciones.

Puedes encontrar todos los estilos de la Pokedex y agreagarlos a tu app aqui.

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

Para agregarles estilos a tus componentes en react solo necesitas importar tus ojas de estilos donde las necesites.

create-react-app utiliza por defecto archivos css pero pudes añadir facilmente tu preprocesador favorito ya sea Sass, Less o Stylus.

import './styles/pokedex.css';

import './styles/pokedex.sass';

Si quieres que tus estilos sean mas 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 despues 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 despues 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 esten 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 crar tus componentes:

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

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

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

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

¿Como utilizo los archivos estaticos?

En esta sección vamos a aprender como agregar imagenes, iconos, ilustraciones vecotriales o cualquier otro tipo de imagenes dentro de nuestros componentes.

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

Error Pokedex ErrorError Pokedex Preview

Para utilizar archivos estaticos en React como .jpg, .png, .svg, .gif necesitas importarlos y despues 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 estaticos dentro del directorio public y declarar la ruta a seguir dentro de esta carpeta.

Tus archivos estaticos 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 en relación a las carpetas señaladas en tu archivo de configració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"
/>

¿Como creo y actualizo el estado?

El estado de nuesta pokedex tendra cuatro piezas basicas:

  • loading: Mientras estamos buscando un Pokemon, sera verdadero.
  • error: Si ocurre un error en API o un error de red, este sera verdadero.
  • pokemon: Guardara todos los datos de nuestro pokemon.
  • pokemonID: Guardara el ID o el nombre el pokemon que estamos buscando.

Dentro de las 4 versiones de la pokedex encontraras un codigo muy similar a este:

// Creamos un numero 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 pokemon desde el momento en que se renderiza la pokedex y utilizamos un numero entre el 1 y el 807 por que solo existen 807 pokemones.

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 crearo 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

Despues de crear todos los estados necesarios para la pokedex, nuestro componente se ve asi:

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 mas sencilla posible, solo necesitas crear variables con let y despues puedes cambiar su valor como normalmente lo harias 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 facilmente
  error = true
  pokemonId = 25

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

El estado en Vue es creado dentro de un objeto que devuelve la funcion 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 referenciandolas como this.value.

Como angular soporta de manera nativa TypeScript puedes especificar cual sera el tipo de valor que tendra 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

¿Como traigo datos de un API?

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

Utilizaremos la PokeAPI y la ruta que nos permitira buscar a nuestros pokemones por nombre o por id sera https://pokeapi.co/api/v2/pokemon/${pokemonId}

Recuerda que debemos cambiar el estado de la Pokedex dependiendo de la respuesta de nuestra API.

Todas los frameworks tienen sus propios metodos para implementar este tipo de soluciones, pero aqui decidimos hacerlo asi para practicar mas conceptos sin instalar ningun paquete adicional.

Llamar a una API con React es super sencillo. Solo necesitamos crear la función que hara el llamado a la API y luego ejecutarla cuando se cree el componente. Esto se logra utilizando el hook de useEffect y pasandole un array vacio como segundo parametro.

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

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 pokemon
  const [ pokemonID, setPokemonId ] = useState(RandomId)

  // Solamente esta cargando mientras hacemos la peticion,
  // cuando esta se resuelve o fue un exito u un error.
  useEffect(() => {
    fetch(`https://pokeapi.co/api/v2/pokemon/${pokemonID}`)
      .then(res => res.json())
      .then(data => {
        // Si todo esta cool, actualizamos el pokemon
        // 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 hara 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 pokedex, esto luce asi:

  // Solamente esta cargando mientras hacemos la peticion,
  // cuando esta se resuelve o fue un exito 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 a la API dentro de los metodos y despues debe ser llamada dentro de la función created para que se ejecute cuando se cree 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 exito 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 tambien nos permite crear todo lo necesatio 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 pokemon, 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}`)
  }
}

Tambien debemos de declarar el modulo 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   // Add here
  ],

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 exito o un error. Nosotros colocamos todo esto dentro de la función searchPokemon pues en el futuro necesitaremos llamar a la API cuando se envie 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
  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
        }
      )
  }
}

¿Como 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 codigo mucho mas modular y mas legible.

Dentro de la Pokedex debemos de pasar los datos que nos devuelve la API, asi como el estado de Carga y error para que <PokedexScren /> muestre la información necesario.

Dentro de todos los Snippets de codigo encontraras un ul vacio donde en la siguiente sección iteraremos sobre la lista de estadisticas de los pokemones.

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}
  />
//...

¿Como recibimos los valores en nuestro componente hijo?

Recibimos los valores en <PokedexScreen /> extrayendolos de las props.

Para mostrarlos den pantalla, dentro JSX podemos colocar cualquier expresión de JavaScript entre llaves {}, esta tecnica 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">
          // Aqui iteraremos sobre la lista de estadisticas
        </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}
/>

¿Como 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 mostar 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 tambien asignarles un valor por defecto para que la aplicación no se rompa si no recibimos ningun 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
    }
  }

¿Como mostramos los valores en pantalla?

En vue mostramos los valores de 2 maneras diferentes:

  • Cuando queremos mostrar texto, como el nombre del pokemon 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 componentes y asignarles un valor como en este ejemplo:

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

¿Como 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>

¿Como renderizo una lista?

Llego 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 tecnologias.

Despues analizaremos como renderizar las estadisticas de un pokemon para conocer sus fortalezas y sus debilidades.

Pokedex 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 metodos de los array como map.

Cada uno de los elementos de la lista debe de tener un un identificador unico, 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 asi:

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 estamos programando un aplicación con Vue y necesitamos iterar en un array para renderizar un serie de elementos, Vue no da la función v-for para iterar sobre el array y v-bind:key para asignarle un identificador unico 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 luciria asi:

// ...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>

Si quieres iterar sobre un array y mostrar un componente para cada uno de los elementos, Svelte te proporciona un metodo {#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 pametro y sera 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>

Angular nos da *ngFor para iterar sobre una lista y matar un componente para cada elemento de ella y trackBy para darle un identificador unico 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 recordaras, 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 esta buscanco un pokemon, si lo ha encontrado o si ha ocurrido alguno error.

En realidad existen librerias especializadas para manejar los estados de las peticiones a una API, aqui lo haremos utilizando el estado ya que el objetivo era crear una aplicación sencilla pero donde utilizaramos la mayoria de las piezas basicas de una aplicación moderna.

React es super versatil para renderizar componentes de manera condicional, gracias a ser una libreria, ya que nos permite utilizar todas las estructuras logicas de JavaScript como if/else, switch, y sus abreviaciones del tipo ? / : o &&. Asi que en realidad podemos lograr el mismo fin de muchas maneras diferentes. Aqui te mostraremos dos de las opciones posibles.

function PokedexScreen({ pokemon, loading, error }){
  // Si hay un error en la peticion a la API, devuelve este componente.
  // Recuerda que al hacer un return, el resto de codigo, no se ejecutara.
  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 logica 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 librerias adicionales gracias a que tiene las herramientas {#await}, {:then}, {:catch}. Pero aqui usaremos condiconales para comparar mejor las librerias/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 pokedex, 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 sigueinte 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 ni esta 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

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

Dentro de la Pokedex nuestro objetivo es que una de las luces este parpadeando cuando estemos cargando un nuevo pokemon sin embargo podriamos lograr cientos de efectos y animaciones aplicando este concepto.

Agregar estilos condicionales en React es facil 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 facilmente.

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 basta 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 mas elegante de asignarle clases o atributos condicionales a tus elementos, puedes hacerlo con [class] o [ngClass] si quieres hacerlo a travez de las clases de css, [style.property] si deseas cambiar solo una propiedad o [ngStyle] si deseas cambiar multiples 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 para mandar que nos ayudara a buscar pokemones.

La PokeApi te permite buscar pokemones por su nombre en minusculas y por ID, asi que vamos a manejar ambos casos.

//Ambas son validas
'pokeapi.co/api/v2/pokemon/25'

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

En algun momento tendremos que validar si el usuario ingreso un numero o un nombre y en caso de que haya sido un nombre, debemos de ponerlo en minusculas ya que el autocorrector puede hacer que escribamos "Pikachu".

Manejando Inputs y Eventos del DOM en React

React al ser una libreria, no nos da ninguna función que actualize el valor del estado segun 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 ejecutara cuando suceda. Al mandar el formulario ejecutamos la función preventDefault() del evento para evitar que el formulario realize su función de mandar alguna query a travez 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 cunado 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 algun 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 funcion a ejecutar usando {}, por ejemplo: on:submit={handleSubmit}.

Asi 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 algun input de texto y algun valor de nuestro estado asi: 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 asignales el metodo que ejecutaran.

Ambos conceptos lucen asi 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 algun valor de nuestro estado utilizamos el atributo [(ngModel)] y le asignamos algun valor de nuestro estado. Es muy importante añadir el modulo 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 ajecutar como (ngSubmit)="handleSubmit($event)".

Asi luce nuestro codigo de PokedexForm despues 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

Nuestra 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 devolvera datos al componente <PokedexScreen/>.

Aqui 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 pokemon, mientras que Vue y Angular tienen que emitir funciones y valores desde el componente hijo que a su vez actualizaran todo lo necesario en el componente padre como el ID del pokemon.

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 travez de las props, recibirlas en <PokedexForm /> y ejecutarlas de manera normal.

Tambien le pasaremos setPokemonId() para actualizar el ID del pokemon 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 hara una peticion 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 vacio, hay un error
  }

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

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

¿Por que? Por que la función dentro de useEffect se ejecutara cuando cambie el valor de alguno de los elementos incluidos en el Array, si el array esta vacio, solo se ejecutara 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 /> utlizando Svelte, basta con mandarle los estados a <PokedexForm /> con el prefijo bind: para que queden enlazados los valores y despues podremos cambiar los valores de manera normal.

Tambien le pasaremos pokemonId para actualizar el ID del pokemon 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 sera true por que hara 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 vacio, 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 pokemon en la API. Para lograrlo, solo necesitamos agregar un par de lineas de codigo.

//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 pokemon 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 Pokemon, 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 metodos 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, ¿Como escuchamos el evento submit desde el componente padre?.

Primero debemos de recibirlo dentro de nuestro template en pokedex.component.html y asignarle la función que se ejecutara 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
  }
}