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.
<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.
<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.
<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.
<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
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.
<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.
@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.
@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.
@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.
@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:
Si tenemos una lista de primitivas donde cada valor es único, podemos usar el propio elemento como identificador.
@for (month of months; track month)
Si tenemos una lista de entidades, la propiedad identificadora de las mismas.
@for (user of users; track user.id)
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))
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.
@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.
@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:
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