@let en templates

Intermedio
Angular
Angular
Actualizado: 24/09/2025

Declaración de variables con @let

La directiva @let de Angular 18+ permite declarar variables locales dentro de templates de forma declarativa. Esta funcionalidad introduce un nuevo primitivo para la gestión de variables temporales en templates, complementando el arsenal de herramientas modernas de Angular.

Sintaxis básica de @let

La sintaxis de @let sigue un patrón simple y familiar. Se declara una variable precedida por el símbolo @ y se le asigna una expresión:

@let userName = user.firstName + ' ' + user.lastName;
<p>Bienvenido, {{ userName }}</p>

La variable declarada con @let está disponible inmediatamente después de su declaración dentro del mismo contexto de template:

@let isLoggedIn = authService.isAuthenticated();
@let userRole = isLoggedIn ? user.role : 'guest';

@if (isLoggedIn) {
  <nav>
    <span>Rol: {{ userRole }}</span>
  </nav>
}

Reglas de scope y visibilidad

Las variables @let siguen reglas de scope estrictas que difieren del hoisting tradicional de JavaScript. Una variable @let solo es visible después de su declaración y dentro de su contexto contenedor:

<!-- INCORRECTO: userName no está definido aquí -->
<!-- <p>{{ userName }}</p> -->

@let userName = user.name;
<!-- CORRECTO: userName está disponible aquí -->
<p>{{ userName }}</p>

El scope de una variable @let está limitado al bloque donde se declara. Esto significa que variables declaradas dentro de bloques condicionales no son accesibles fuera de ellos:

@let globalVar = 'visible en todo el template';

@if (condition) {
  @let localVar = 'solo visible dentro del @if';
  <p>{{ globalVar }} - {{ localVar }}</p>
}

<!-- localVar NO está disponible aquí -->
<p>{{ globalVar }}</p>

Diferencias con template reference variables

Las variables @let operan de manera fundamentalmente diferente a las template reference variables tradicionales. Mientras que las template reference variables (#variable) referencian elementos DOM o componentes, @let almacena valores computados:

Template reference variables:

<input #emailInput type="email">
<button (click)="processEmail(emailInput.value)">Procesar</button>

Variables @let:

@let emailValue = emailInput.value.toLowerCase().trim();
<input #emailInput type="email">
<button (click)="processEmail(emailValue)">Procesar</button>

Las template reference variables proporcionan acceso directo a elementos y sus propiedades, mientras que @let permite crear derivaciones y transformaciones de datos para uso en el template.

Expresiones y reactividad

Las variables @let pueden contener cualquier expresión válida de Angular, incluyendo llamadas a métodos, operadores ternarios y acceso a propiedades:

@let itemCount = shoppingCart.items.length;
@let totalPrice = calculateTotal(shoppingCart.items);
@let hasDiscount = user.membershipLevel === 'premium';
@let finalPrice = hasDiscount ? totalPrice * 0.9 : totalPrice;

<div class="cart-summary">
  <p>{{ itemCount }} artículos</p>
  <p>Total: {{ finalPrice | currency }}</p>
</div>

Es importante entender que las variables @let se reevalúan automáticamente cuando cambian sus dependencias, manteniendo la reactividad característica de Angular sin necesidad de configuración adicional.

Casos de uso prácticos y scope management

Optimización del async pipe

Uno de los casos más frecuentes donde @let aporta valor inmediato es la optimización de múltiples suscripciones al async pipe. En lugar de crear múltiples suscripciones a la misma fuente de datos, @let permite almacenar el resultado una sola vez:

Antes (múltiples suscripciones):

<div>
  <h2>{{ (userProfile$ | async)?.name }}</h2>
  <p>{{ (userProfile$ | async)?.email }}</p>
  <span>{{ (userProfile$ | async)?.lastLogin | date }}</span>
</div>

Después (una sola suscripción):

@let profile = userProfile$ | async;
@if (profile) {
  <div>
    <h2>{{ profile.name }}</h2>
    <p>{{ profile.email }}</p>
    <span>{{ profile.lastLogin | date }}</span>
  </div>
}

Esta optimización reduce significativamente las suscripciones redundantes y mejora el rendimiento general del template.

Simplificación de expresiones complejas

Las variables @let destacan cuando necesitamos trabajar con expresiones complejas que se reutilizan en múltiples lugares del template:

@let discountedPrice = product.price * (1 - product.discount / 100);
@let hasStock = product.inventory > 0;
@let canPurchase = hasStock && discountedPrice > 0 && !product.discontinued;

<div class="product-card">
  <h3>{{ product.name }}</h3>
  <p class="price">
    @if (product.discount > 0) {
      <span class="original">{{ product.price | currency }}</span>
      <span class="discounted">{{ discountedPrice | currency }}</span>
    } @else {
      <span>{{ product.price | currency }}</span>
    }
  </p>
  
  <button [disabled]="!canPurchase">
    {{ canPurchase ? 'Comprar' : 'No disponible' }}
  </button>
</div>

Scope anidado y contextos complejos

El manejo de scope anidado con @let permite crear jerarquías de variables que respetan el contexto donde se declaran:

@let baseConfig = appSettings.theme;

<div class="app-container">
  @for (module of modules; track module.id) {
    @let moduleTheme = module.customTheme || baseConfig;
    @let isActiveModule = activeModuleId === module.id;
    
    <div class="module" [class]="moduleTheme">
      @if (isActiveModule) {
        @let detailsVisible = showDetails && module.hasDetails;
        
        <div class="module-details">
          @if (detailsVisible) {
            @let sortedItems = module.items.sort((a, b) => a.order - b.order);
            
            @for (item of sortedItems; track item.id) {
              <div>{{ item.title }}</div>
            }
          }
        </div>
      }
    </div>
  }
</div>

En este ejemplo, cada nivel de anidamiento mantiene su propio scope, permitiendo que detailsVisible y sortedItems solo existan dentro de sus contextos respectivos.

Type narrowing y validación de datos

Las variables @let facilitan el type narrowing en TypeScript, especialmente útil cuando trabajamos con datos que pueden ser null o undefined:

@let currentUser = authService.currentUser();

@if (currentUser) {
  @let userPermissions = currentUser.permissions;
  @let canEdit = userPermissions.includes('edit');
  @let canDelete = userPermissions.includes('delete');
  
  <div class="user-actions">
    @if (canEdit) {
      <button (click)="editItem()">Editar</button>
    }
    @if (canDelete) {
      <button (click)="deleteItem()">Eliminar</button>
    }
  </div>
}

Una vez que confirmamos que currentUser existe dentro del bloque @if, TypeScript reconoce automáticamente que no puede ser null, permitiendo acceso seguro a sus propiedades.

Cuándo usar @let vs signals

La decisión entre @let y signals depende del alcance y la naturaleza de los datos:

Usa @let para:

  • Variables temporales específicas del template
  • Transformaciones de datos que solo se usan en la vista
  • Optimización de expresiones complejas puntuales
  • Cálculos derivados simples
@let formattedDate = new Date(timestamp).toLocaleDateString();
@let isExpired = timestamp < Date.now();

Usa signals para:

  • Estado que necesita persistir entre renders
  • Datos que se comparten entre múltiples componentes
  • Lógica de negocio compleja
  • Estado que requiere effects o computed signals
// En el componente
readonly expirationStatus = computed(() => {
  return this.timestamp() < Date.now() ? 'expired' : 'active';
});

Mejores prácticas de scope management

Mantén las variables @let cerca de su uso:

<!-- MEJOR: declaración próxima al uso -->
<div class="product-list">
  @for (product of products; track product.id) {
    @let isOnSale = product.discount > 0;
    @let finalPrice = isOnSale ? product.price * 0.8 : product.price;
    
    <div class="product">
      <span [class.sale]="isOnSale">{{ finalPrice | currency }}</span>
    </div>
  }
</div>

Evita el scope excesivamente anidado:

<!-- EVITAR: demasiados niveles -->
@let level1 = someExpression;
@if (condition1) {
  @let level2 = level1.property;
  @if (condition2) {
    @let level3 = level2.transform();
    <!-- Difícil de mantener -->
  }
}

<!-- MEJOR: aplana la lógica -->
@let baseData = someExpression;
@let transformedData = baseData?.property?.transform();
@if (condition1 && condition2 && transformedData) {
  <div>{{ transformedData }}</div>
}

Usa nombres descriptivos que reflejen el contexto:

@let filteredProducts = products.filter(p => p.category === selectedCategory);
@let sortedByPrice = filteredProducts.sort((a, b) => a.price - b.price);
@let displayProducts = sortedByPrice.slice(0, pageSize);

@for (product of displayProducts; track product.id) {
  <!-- Template del producto -->
}

Esta aproximación hace que el flujo de datos sea claro y mantenible, facilitando futuras modificaciones y debug del template.

Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Angular es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de Angular

Explora más contenido relacionado con Angular y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

  • Comprender la sintaxis y uso básico de la directiva @let en templates de Angular.
  • Entender las reglas de scope y visibilidad de variables declaradas con @let.
  • Diferenciar entre variables @let y template reference variables.
  • Aplicar @let para optimizar suscripciones y simplificar expresiones complejas en templates.
  • Gestionar scopes anidados y realizar type narrowing con @let para mejorar la seguridad y claridad del código.