DEV Community

Cover image for Angular 17 - Nuevo Control de Flujo Integrado.
akotech
akotech

Posted on

Angular 17 - Nuevo Control de Flujo Integrado.

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


@‍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.
@if (price > 100) {
    <p>El precio es mayor que 100</p>
}
Enter fullscreen mode Exit fullscreen mode

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.

@if (user$ | async; as user) {
  <p>{{ user.name }}</p>
  <p>{{ user.email }}</p>
}
Enter fullscreen mode Exit fullscreen mode

@‍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.

@if (price > 100) {
  <p>El precio es mayor que 100</p>
} @else {
  <p>El precio NO es mayor que 100</p>
}
Enter fullscreen mode Exit fullscreen mode

@‍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.

@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>
}
Enter fullscreen mode Exit fullscreen mode

@‍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

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

@Component({...})
export class SomeComponent {
  media!: Photo | Video | Audio;
}
Enter fullscreen mode Exit fullscreen mode

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.

@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>
}
Enter fullscreen mode Exit fullscreen mode

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.
@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>
  }
}
Enter fullscreen mode Exit fullscreen mode

@‍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.

@switch (media.type) {
  @case ("photo") {
    <img [src]="media.url" />
  }

  ...

  @default {
    <p>Tipo no reconocido</p>
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

// 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" />
  }
  ...
}
Enter fullscreen mode Exit fullscreen mode

@‍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.

<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>
Enter fullscreen mode Exit fullscreen mode

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.

    @for (month of months; track month) 
    
  2. Si tenemos una lista de entidades, la propiedad identificadora de las mismas.

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

    @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)

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

// 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>
  }
  ... 
}
Enter fullscreen mode Exit fullscreen mode

@‍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.

<ul>
  @for (user of users; track user.id) {
    <li>{{ user.name }}</li>
  } @empty {
    <li>No hay usuarios.</li>
  }
</ul>
Enter fullscreen mode Exit fullscreen mode

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:

ng generate @angular/core:control-flow
Enter fullscreen mode Exit fullscreen mode

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


YouTube · X · GitHub

Top comments (1)

Collapse
 
jangelodev profile image
João Angelo

Hi akotech,
Your tips are very useful
Thanks for sharing