Ejercicio con componentes, data binding y encapsulation

¿De qué va esto?

Para practicar un poco el uso de los componentes en Angular, así como el data binding, vamos a crear un pequeño juego. Si necesitas reforzar tus conocimientos sobre esto temas, puedes consultar esta serie de posts, o esta guía para empezar con Angular si partes desde 0.

Instrucciones

1. crear 3 componentes: GameControl, Odd y Even.

2. el GameControlComponent debe tener 2 botones: uno para empezar el juego y otro para pausarlo.

3. al pulsar el botón de empezar el juego, emitir un evento cada 1 segundo

         ? pista: evento con un número que vaya incrementando

         ? pista: función setInterval

4. guardar una referencia a este contador en alguna propiedad en GameControlComponent para poder cancelarlo en el futuro.

5. el evento emitido tiene que ser accesible (en inglés, listenable) desde fuera de su componente. Tiene que poder ser usado desde el AppComponent, desde donde deberemos usar el GameControlComponent.

6. al hacer click en el botón para pausar el juego, el contador debe pararse (con clearInterval, por ejemplo).

7. añadir un nuevo OddComponent cada vez que el evento creado por el GameControlComponent emita un número impar. Lo mismo para el EvenComponent, pero con números pares.

            ? pista: ngFor

            ? en un <p>, por ejemplo, diciendo "odd number: (número impar emitido aquí)"

8. añadir al OddComponent y al EvenComponent diferentes estilos (colores, tamaño, etc).

Paso 1: Inicio y creación de los componentes de nuestra app

2. Creamos nuestros 3 componentes usando Angular CLI

ng g c game-control --skipTests

Y lo mismo para los otros dos. ✌

3. Añadimos Bootstrap para que nuestra app no se vea sumamente horrible. 

Paso 2: Creación y ubicación de los botones

1. Vamos a crearnos los botones en el template del GameControlComponent.

<button class="btn btn-primary mx-2">Start game</button>
<button class="btn btn-warning mx-2">Stop game</button>

2. Y a añadirlos al AppComponent template usando el selector del GameControlComponent. Borra primero todo el contenido por defecto con el que viene Angular.

<div class="container my-5 text-center">
  <app-game-control></app-game-control>
</div>
ejercicio componentes paso 2

Paso 3: Creación y emisión de un custom event

Aquí un esquemita para ubicar nuestro custom event y entender la estructura que queremos construir:

esquema custom event emitted

1. Añadimos un click event al botón "Start game" en el template del GameControlComponent y lo vinculamos a una función que posteriormente crearemos. 

<button class="btn btn-primary mx-2" (click)="startGame()">Start game</button>

2. Nos creamos la función en el código TypeScript (en adelante, TS) del GameControlComponent. Dentro de esta función es donde colocamos el setIntervaluna built-in function de JavaScript (en adelante, JS). Creamos también una propiedad (llamada interval) para poder acceder a la función y manipular el setInterval. 

A nuestra propiedad le asignamos el valor del setInterval dentro de la función startGame.

3. Definimos el contenido de la función setInterval. La ejecutaremos cada 1 segundo y dentro contendrá nuestro custom event que posteriormente emitiremos. Para eso, definimos otra propiedad (llamada gameStartedpor ejemplo) y le asignamos el valor de new EventEmitter.

? Recuerda importar el EventEmitter desde angular/core.

Nuestro EventEmitter será de tipo number.

4. Con esto ya podemos volver a nuestro setInterval y emitir nuestro evento.  Como lo que queremos emitir es un número creciente (0, 1, 2, etc), declaramos una propiedad llamada incrementingNum, por ejemplo, y le damos el valor de 0, para que empiece a contar desde 0.   

Al emitir el evento, le pasamos el valor de nuestro incrementingNum, incrementando en +1 a cada segundo. Hacemos un console.log para comprobar que efectivamente estamos emitiendo un número creciente cada 1 segundo. 

5. Añadimos el decorador @Output() a nuestra propiedad gameStarted. Con esta adición ya podemos usar nuestro custom event en otro componente, como el AppComponent.

interval: any;
incrementingNum = 0; // starting value

@Output() gameStarted = new EventEmitter<number>();

constructor() { }

startGame() {
  this.interval = setInterval(() => {
  this.gameStarted.emit(this.incrementingNum++);
  console.log(this.incrementingNum);
  }, 1000);
}

Paso 4: Escuchando nuestro evento desde fuera de su componente

1. Dentro del AppComponent, añadimos el evento mediante event binding al selector <app-game-control> y lo vinculamos a un evento que posteriormente nos crearemos (onStartCounterpor ejemplo). Queremos capturar cierta información cuando nuestro evento se dispare, por lo que usamos el parámetro $event

  <app-game-control (gameStarted)="onStartCounter($event)"></app-game-control>

2. Configuramos el método onStartCounter en el archivo app.component.ts. De momento sólo vamos a hacer un console.log para ver el último número emitido. Sabemos que la función recibirá un número, así que lo indicamos en el parámetro.  

  onStartCounter(incrementingNum: number) {
		console.log(incrementingNum);
  }

? No olvidemos que teníamos un console.log en el game-control.component.ts, así que si no quieres ver cómo se imprimen los números crecientes repetidos, coméntalo.

Paso 5: Pausando el juego

1. Añadimos un click event a nuestro botón del archivo game-control.component.html (al de parar el juego) y lo vinculamos a un método que crearemos posteriormente (pauseGamepara ser consistentes con respecto al otro botón).

<button class="btn btn-warning mx-2" (click)="pauseGame()">Stop game</button>

2. Vamos al archivo game-control.component.ts, donde configuraremos nuestro método pauseGame. Para conseguir que nuestro setInterval deje de emitir eventos (números), simplemente tenemos que llamar a otra built-in function de JS: clearInterval

A esta función le pasamos nuestra propiedad interval porque es la que hemos establecido como referencia a la función setInterval.  

  pauseGame() {
    clearInterval(this.interval);
  }

¡Y listo! Ve a tu navegador y comprueba en las dev tools cómo dejan de emitirse números al pulsar el botón de "stop". ? ? Si ahora vuelves a darle a "start", verás que se vuelven a emitir números desde el número en que pausaste.

Paso 6: Mostrar un componente u otro (EvenComponent OddComponent) según la naturaleza del número (par o impar)

1. Vamos al archivo odd.component.html y nos creamos un párrafo donde mostraremos nuestros números impares mediante string interpolation. La propiedad que le pasamos (llamada oddNumberpor ejemplo), la configuraremos a continuación. 

<p>Odd number: {{ oddNumber }} </p>

2. Configuramos nuestra propiedad oddNumber en el archivo odd.component.ts. Como vamos a pasar el valor de oddNumber desde fuera del componente (desde el AppComponent), le añadimos el decorador @Input

  @Input() oddNumber: number;

3. En el archivo app.component.html, inyectamos el OddComponent mediante su selector de HTML. Nuestra intención es generar un componente como este cada vez que se emita un número impar, así que este parece el caso ideal para añadirle un ngFor. ➰ 

Pero tenemos un problemilla: no tenemos ningún array para poder utilizarlo con el ngFor. Así que tendremos que crearlo. Si observamos el archivo app.component.ts, vemos que ahora estamos imprimiendo todo número, sea impar o impar. Vamos a cambiar eso. ?

4. Nos creamos dos arrays vacíos (oddNumbers evenNumbers). Ahora, dentro de la función onStartCounter comprobamos si el número que se emite es par o impar. Para eso podemos usar el modulus operator (%). El modulus operator funciona de la siguiente manera: 

Si un número X es divisible por 2, significa que el número es par. Esto en código se traduce como:

myNumber % 2 === 0

Todo esto lo hacemos en un bloque de if elseañadiendo el incrementingNumber (push) al array de evenNumbers si resulta ser par, y al otro array en caso contrario. 

  oddNumbers = [];
  evenNumbers = [];

  onStartCounter(incrementingNum: number) {
    if(incrementingNum % 2 === 0) {   // if it's even
      this.evenNumbers.push(incrementingNum);
    } else { // if it's odd
      this.oddNumbers.push(incrementingNum);
    }
  }

5. Ahora ya podemos volver al archivo app.component.html y completar nuestro ngFor. Además, para poder mostrar los números impares en el navegador, debemos vincular nuestra propiedad oddNumber (del OddComponent, recuerda) mediante property binding y asignarle el valor de nuestra variable local (la que tendremos que declarar en el ngFor).

  <hr>
  <app-odd *ngFor="let oddNum of oddNumbers" [oddNumber]="oddNum"></app-odd>

Y...¡tadáááá! ? ? Ahora se imprime un número impar cada 2 segundos (porque aún no hemos configurado cómo mostrar los números pares así que durante el segundo que se debería emitir un número par, simplemente no vemos nada?

6. Vamos a replicar la misma configuración hecha en el OddComponent sobre el EvenComponent:

         ➡️ Nos creamos un <p> en el EvenComponent y una propiedad que vinculamos con string interpolation.

<p>Even number: {{ evenNumber }} </p>

         ➡️ En el even.component.ts creamos una propiedad que recibirá información desde el parent component.

  @Input() evenNumber: number;

         ➡️ En el app.component.html, añadimos el EvenComponent mediante su selector y configuramos el ngFor.

  <app-even *ngFor="let evenNum of evenNumbers" [evenNumber]='evenNum'></app-even>

Y con esta configuración ya deberías ver que en tu navegador se muestran tanto números pares como impares ?

Aunque como habrás observado, los números impares se muestran primero y los pares, después. Esto es debido a cómo hemos posicionado nuestros componentes en el AppComponent, ya que hemos puesto uno debajo del otro:

<div class="container my-5 text-center">
  <app-game-control (gameStarted)="onStartCounter($event)"></app-game-control>
  <hr>
  <app-odd *ngFor="let oddNum of oddNumbers" [oddNumber]="oddNum"></app-odd>
  <app-even *ngFor="let evenNum of evenNumbers" [evenNumber]='evenNum'></app-even>
</div>

Paso 7: Dando distintos estilos a nuestros componentes

Angular utiliza un sistema de styles encapsulation para definir los estilos de un componente (puedes ver los detalles en este post). Así que sencillamente tenemos que usar el archivo CSS de cada componente para darle el estilo que queramos a nuestros párrafos.

Por ejemplo, en el archivo odd.component.css podríamos escribir:

p { color: blueviolet; }

Y en el archivo even.component.css:

p { color: green; }

Y, gracias a la magia del styles encapsulation, cada archivo CSS se aplicará sólo a los elementos de su componente. Con esto, deberías ver en tu navegador cómo los números pares e impares se muestran con un color distinto.

Existe otra manera de aplicarle estilos a nuestro código, y es usando la directiva ngStyle. De esta manera no tenemos que tocar los archivos CSS, porque utilizamos la directiva directamente en la template.

1. Aplicamos la directiva al even.component.html con el estilo que queramos darle, por ejemplo:

<p  [ngStyle]="{backgroundColor: 'gold'}">Even number: {{ evenNumber }} </p>

2. Hacemos lo mismo en el odd.component.html:

<p [ngStyle]="{backgroundColor: 'turquoise'}">Odd number: {{ oddNumber }} </p>

¡Y voilà!

ejercicio terminado

¡RETO CONSEGUIDO!

¡Y hasta aquí este ejercicio! Si te queda alguna duda, ¡nos vemos en los comentarios! Si crees que este post puede serle útil a alguien, ¡compártelo! Y si de verdad quieres ayudarme, cómprate algo en Amazon usando este link. Yo me llevaré una pequeña comisión y a ti no costará nada extra.

Otros artículos que pueden interesarte

Cómo aprendí a programar cuando estaba «programada» para ser de letras
[tcb-script src="https://player.vimeo.com/api/player.js"][/tcb-script]A nadie le gusta su trabajo. Eso es lo que me decía a mí misma cuando conseguí mi primer[...]
Días del 160 al 203 – ¡Primer objetivo conseguido!
“A veces podemos pasarnos años sin vivir en absoluto, y de pronto toda nuestra vida se concentra en un solo[...]
Claves para entender Angular. Qué es y cómo se utiliza
Angular es un framework creado por Google que nos permite construir Single Page Applications (SPA, por sus siglas en inglés).Frameworks¿Pero qué es[...]
Si crees que este post puede serle útil a alguien, por favor, ¡compártelo!:

2 comentarios en «Ejercicio con componentes, data binding y encapsulation»

  1. Hola Lilia.
    Efectivamente, teníamos un console.log en el game-control.component.ts, así que para no ver los números repetidos, puedes comentarlo (añadiendo // delante).

  2. Hola! Tengo una pregunta sobre el paso 3 y paso 4. Si se llevan a cabo ambos, nos dan el resultado duplicado en la consola. Entiendo que son dos formas diferentes de hacerlo, no? Dado que si se omite el paso 4, sigo obteniendo el mismo resultado sin el número duplicado. En caso de que sea necesario el paso 4, que tengo que hacer para que el resultado no me aparezca repetido?

    Gracias

Deja un comentario

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

Esta web utiliza cookies para asegurar que se da la mejor experiencia al usuario. Si continúas utilizando este sitio se asume que estás de acuerdo. más información

Los ajustes de cookies en esta web están configurados para «permitir las cookies» y ofrecerte la mejor experiencia de navegación posible. Si sigues usando esta web sin cambiar tus ajustes de cookies o haces clic en «Aceptar», estarás dando tu consentimiento a esto.

Cerrar