Angular 17 - Nuevo Control de Flujo Integrado.
Tags: angular, spanish, webdev, programming
Published: 2024-05-20 12:14:27

Una de las principales novedades introducidas en `Angular 17` es la nueva sintaxis de Control de Flujo integrada en los templates, que viene a hacer innecesario el uso de directivas estructurales como  `*ngIf`, `*ngFor`, o `*ngSwitch`.

Esta nueva sintaxis nos ofrece tres bloques principales de los cuales hablaremos a lo largo de este artículo:

- **@‍if**
- **@‍switch**
- **@‍for**

> Si lo prefieres, el contenido de este artículo también lo tienes
en formato video [aquí.](https://youtu.be/zHGUC6tguPA)
> 
> 
> {% embed https://youtu.be/zHGUC6tguPA %}
> 

---

## @‍if (condition)

El bloque `@if`, como podemos intuir, nos permitirá renderizar una parte del template cuando se cumpla una determinada condición. 

Su **sintaxis es muy similar a la que encontramos en JavaScript**:

- Indicamos la condición a chequear entre paréntesis.
- Y entre un par de llaves incluimos el contenido a renderizar cuando dicha condición se cumpla.

```tsx
@if (price > 100) {
	<p>El precio es mayor que 100</p>
}
```

Para los **casos en los que necesitemos exportar el resultado de la condición**, como por ejemplo sucede a la hora de usar la pipe `async`, podremos seguir utilizando `as <nombre_variable_local>`, del mismo modo que hacíamos en el `*ngIf`.

```tsx
@if (user$ | async; as user) {
  <p>{{ user.name }}</p>
  <p>{{ user.email }}</p>
}
```
‍
### @‍else
Una de las mejorías más significativas de esta nueva sintaxis en comparación con el uso de `*ngIf` es a la hora de **definir un contenido alternativo cuando la condición no se cumpla**. 

Y es que ahora simplemente tendremos que añadir un bloque `@else` con dicho contenido alternativo.

```tsx
@if (price > 100) {
  <p>El precio es mayor que 100</p>
} @else {
  <p>El precio NO es mayor que 100</p>
}
```
‍
### @‍else if
Y eso no es todo, ya que ahora **tenemos incluso la opción de añadir bloques `@else if` intermedios** en el caso de necesitar condiciones adicionales.

```tsx
@if (price > 100) {
  <p>El precio es mayor que 100</p>
} @else if (price < 100) {
  <p>El precio es menor que 100</p>
} @else {
  <p>El precio es 100</p>
}
```

---

## @‍switch (expression)

Y esto de las múltiples condiciones nos da pie para hablar del siguiente bloque, que es el `@switch`. Este bloque nos permite evaluar una expresión y en función del resultado de la misma, renderizar unos elementos u otros.

Por ejemplo imaginemos que tenemos un componente con una propiedad media que puede ser del tipo `Photo | Video | Audio` 

```tsx
interface Photo { type: 'photo'; ...}
interface Video { type: 'video'; ...}
interface Audio { type: 'audio'; ...}

@Component({...})
export class SomeComponent {
  media!: Photo | Video | Audio;
}
```

A la hora de renderizar esta propiedad en el template, podríamos utilizar una combinación de  bloques `@if`, `@else if` y `@else`, para cubrir las 3 diferentes posibilidades. 

```tsx
@if (media.type === "photo") {
  <img [src]="media.url" />
} @else if (media.type === "video") {
  <video>
    <source [src]="media.url" />
  </video>
} @else {
  <audio>
    <source [src]="media.url" />
  </audio>
}
```

Pero para estos casos concretos en los que una misma expresión puede evaluar a una serie de diferentes valores una opción más sencilla y expresiva es utilizar un `@switch`.

Su uso es muy sencillo:

- Añadimos un bloque `@switch` indicando entre paréntesis la expresión a evaluar (en este caso `media.type`).
- Y entre sus llaves añadimos un bloque `@case` para cada uno de los posibles valores que incluya el contenido a mostrar en cada uno de los casos.

```tsx
@switch (media.type) {
  @case ("photo") {
    <img [src]="media.url" />
  }
  @case ("video") {
    <video>
      <source [src]="media.url" />
    </video>
  }
  @case ("audio") {
    <audio>
      <source [src]="media.url" />
    </audio>
  }
}
```
‍

### @‍default
Si el resultado de la expresión del `@switch` no coincidiera con ninguno de los `@case` definidos, por supuesto, no se renderizaría nada. Pero si preferimos mostrar un contenido alternativo en el caso de que esto suceda, **tenemos la opción de añadir un bloque `@default` con ese contenido por defecto**.

```tsx
@switch (media.type) {
  @case ("photo") {
    <img [src]="media.url" />
  }
  
  ...
  
  @default {
    <p>Tipo no reconocido</p>
  }
}
```

> A diferencia de lo que ocurre en las sentencias `switch-case` de JavaScript, aquí, al menos de momento, no hay lo que se conoce como *fall-through*. Por lo que no necesitaremos hacer `break` en los diferentes cases. Ni tampoco tenemos la opción de definir dos o más `@case` para un mismo bloque.
> 

‍
### Mejor Inferencia de Tipos
Este nuevo formato nos ofrece una ventaja en comparación con el antiguo `*ngSwitch`. Y es que ahora **los bloques `@case` son conscientes del contexto.** 

```tsx
// A nivel del Switch media es del tipo 'Photo | Video | Audio' 
@switch (media.type) {
  @case ("photo") {
    // Dentro de este @case es capaz de inferir 
    // que media solo puede ser del tipo 'Photo'
    <img [src]="media.url" />
  }
  ...
}
```

---

## @‍for (item of iterable; track expression)

El bloque `@for` es esencial a la hora de renderizar el contenido de listas, ya que nos permite repetir una parte del template para cada uno de los elementos de las mismas. 

```tsx
<ul>
  @for (user of users; track user.id) {
	  // este contenido se repetirá para usuario que
	  // que contenga la propiedad users.
    <li>{{ user.name }}</li> 
  }
</ul>
```
‍

### track
En el `@for` tendremos que proporcionar obligatoriamente una *expresión de trackeo*, para que este pueda identificar cada uno de los elementos del listado entre actualizaciones, para así, realizar las mínimas operaciones a la hora de actualizar el DOM.

Por ejemplo: 

1. Si tenemos una **lista de primitivas donde cada valor es único**, podemos usar **el propio elemento como identificador**.
    
    ```tsx
    @for (month of months; track month) 
    ```
    
2. Si tenemos **una lista de entidades, la propiedad identificadora** de las mismas. 
    
    ```tsx
    @for (user of users; track user.id)
    ```
    
3. Si estamos refactorizando una aplicación antigua  **queremos reutilizar un método `trackBy` previamente definido**, podemos llamar a este pasando el índice y elemento de cada iteración.
    
    ```tsx
    @for (user of users; track trackByUserId($index, user))
    ```
    
4. Y **si no tenemos una manera clara de identificar cada elemento**, podemos revertir por defecto **el índice de la iteración**. (Usando esta opción las animaciones de los elementos podrían no funcionar correctamente)
    
    ```tsx
    @for(value of numbers; track $index)
    ```
    
‍

### Propiedades del bucle
Cómo vemos en los últimos ejemplos estamos usando el índice directamente sin tener que exportarlo a una variable manualmente. Y es que ahora todas las propiedades internas del bucle se exportan automáticamente a una variable con el nombre de las mismas precedidas por el símbolo del dólar `$index, $count, $even, $odd, $first, $last`.

Eso sí para los casos en los que tengamos bucles anidados tenemos la opción de seguir exportando dichas propiedades a variables con nombres alternativos para evitar conflictos de nombres.

```tsx
// exportamos el índice como userIndex en el loop externo
@for (user of users; track user.id; let userIndex = $index) { 
  ...
  @for (address of user.addresses; track address) {
    <li>
       {{ address }}
       // usamos userIndex en el loop anidado
       <button (click)="deleteAddress(userIndex, $index)">delete</button> 
    </li>
  }
  ... 
}
```
‍

### @‍empty
La nueva sintaxis incluye además **un nuevo bloque `@empty` que nos permite definir directamente un contenido alternativo para los casos en los que el listado este vacío.**

```tsx
<ul>
  @for (user of users; track user.id) {
    <li>{{ user.name }}</li>
  } @empty {
    <li>No hay usuarios.</li>
  }
</ul>
```

---

## Migración automática

Si necesitas refactorizar una aplicación antigua a este nuevo procedimiento, no te preocupes porque el equipo de `Angular` ha incluido una migración para realizar este proceso de manera automática. 

Ejecutando el comando:

```tsx
ng generate @angular/core:control-flow
```

La cli transformará todas las directivas estructurales de tu aplicación al nuevo formato. 

Y aunque esta transformación no es perfecta ya que por ejemplo no es capaz de inferir cuando utilizar los nuevos bloques `@empty` o `@else if`, si te quitará la mayor parte del trabajo de refactorización.

---

## Conclusión

Esta nueva sintaxis del Control de Flujo nos ofrece a los desarrolladores un procedimiento mucho más intuitivo y simple a la hora de definir el contenido condicional y repetitivo de nuestras aplicaciones.

---

*Si deseas apoyar la creación de más contenido de este tipo, puedes hacerlo a través nuestro [Paypal](https://www.paypal.com/donate/?hosted_button_id=8HJNHL8VTB8ZG)*

---

[YouTube](https://youtube.com/akotech) · [X](https://twitter.com/_akotech) · [GitHub](https://github.com/ako-tech)