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:
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
.
Aúnque 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.
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.
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.
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.
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.
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));
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:
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 { }
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"
],
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.
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"
/>
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
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
}
)
}
}
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>
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.
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>
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="Aún 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="Aún 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="Aún 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>
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>
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".
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
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}
.
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>
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"
.
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>
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
],
})
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>
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
}
}