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>
}
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>
}
@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>
}
@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>
}
@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;
}
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>
}
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 casomedia.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>
}
}
@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>
}
}
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 hacerbreak
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" />
}
...
}
@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>
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.
// 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.
<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:
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
Top comments (1)
Hi akotech,
Your tips are very useful
Thanks for sharing