Variables con #ref
Las template reference variables son una característica fundamental de Angular que permite crear referencias locales a elementos HTML, componentes o directivas directamente en el template. Estas variables proporcionan una forma elegante de acceder a propiedades y métodos de elementos desde el propio template, sin necesidad de escribir código adicional en el componente.
Sintaxis básica
Para crear una template reference variable, utilizamos el símbolo # seguido del nombre que queramos asignar a la variable. Esta sintaxis simple pero efectiva convierte cualquier elemento en una referencia reutilizable dentro del template:
<input #nombreInput type="text" placeholder="Escribe tu nombre">
<button (click)="saludar(nombreInput.value)">Saludar</button>
En este ejemplo, #nombreInput
crea una referencia al elemento input que podemos usar en cualquier parte del template. La variable nombreInput
representa el elemento DOM completo, no solo su valor.
Acceso a propiedades del elemento
Las template reference variables nos permiten acceder a todas las propiedades nativas del elemento DOM. Esto resulta especialmente útil para inputs, donde podemos leer valores, verificar estados o acceder a métodos:
<input #email type="email" placeholder="tu@email.com">
<input #password type="password" placeholder="Contraseña">
<div>
<p>Email válido: {{ email.validity.valid }}</p>
<p>Contraseña tiene valor: {{ password.value.length > 0 }}</p>
</div>
<button [disabled]="!email.validity.valid || password.value.length < 6">
Iniciar sesión
</button>
Casos de uso comunes
Las template reference variables son especialmente útiles en formularios simples donde necesitamos validación básica o interacción inmediata:
<div>
<input #cantidad type="number" min="1" max="10" value="1">
<button (click)="cantidad.stepUp()">+</button>
<button (click)="cantidad.stepDown()">-</button>
<p>Cantidad seleccionada: {{ cantidad.value }}</p>
</div>
También funcionan perfectamente con elementos de selección para crear interfaces dinámicas:
<select #categoria>
<option value="tecnologia">Tecnología</option>
<option value="deportes">Deportes</option>
<option value="musica">Música</option>
</select>
<div>
@if (categoria.value === 'tecnologia') {
<p>Últimas noticias de tecnología...</p>
} @else if (categoria.value === 'deportes') {
<p>Resultados deportivos...</p>
} @else {
<p>Novedades musicales...</p>
}
</div>
Referencias con elementos de texto
Para elementos que muestran contenido, podemos usar las referencias para manipular texto o aplicar transformaciones simples:
<textarea #comentario placeholder="Escribe tu comentario..."></textarea>
<div>
<p>Caracteres: {{ comentario.value.length }}</p>
<p>Palabras: {{ comentario.value.trim().split(' ').length }}</p>
<button (click)="comentario.value = comentario.value.toUpperCase()">
Mayúsculas
</button>
<button (click)="comentario.value = ''">
Limpiar
</button>
</div>
Múltiples referencias en formularios
Las template reference variables brillan cuando necesitamos coordinar múltiples elementos sin escribir lógica compleja en el componente:
<form>
<input #nombre type="text" placeholder="Nombre" required>
<input #apellido type="text" placeholder="Apellido" required>
<input #edad type="number" placeholder="Edad" min="18">
<div>
<p>Formulario completo: {{ nombre.value && apellido.value && edad.value }}</p>
<button
type="button"
[disabled]="!nombre.value || !apellido.value || !edad.value"
(click)="procesar(nombre.value, apellido.value, edad.value)">
Enviar datos
</button>
</div>
</form>
Ventajas de las template reference variables
El uso de #ref ofrece varias ventajas importantes:
- Simplicidad: Reducen la necesidad de escribir código TypeScript para interacciones básicas
- Reactividad: Los cambios se reflejan automáticamente en el template gracias al sistema de detección de cambios de Angular
- Acceso directo: Proporcionan acceso inmediato a propiedades y métodos nativos del DOM
- Legibilidad: Hacen que la lógica del template sea más clara y autodocumentada
Las template reference variables son una herramienta esencial para crear interfaces interactivas de forma declarativa, permitiendo que el template maneje muchas operaciones comunes sin complicar el código del componente.
Acceso a elementos DOM
Aunque las template reference variables permiten interactuar con elementos directamente desde el template, Angular también proporciona mecanismos para acceder a estos elementos desde el código TypeScript del componente. Esta capacidad es esencial cuando necesitamos realizar manipulaciones programáticas o implementar lógica más compleja.
Acceso desde el componente con @ViewChild
Para acceder a una template reference variable desde el componente, utilizamos el decorador @ViewChild. Este decorador establece una conexión entre la referencia del template y una propiedad del componente:
import { Component, ViewChild, ElementRef } from '@angular/core';
@Component({
selector: 'app-usuario',
template: `
<input #nombreInput type="text" placeholder="Introduce tu nombre">
<button (click)="enfocarInput()">Enfocar input</button>
<button (click)="limpiarYEnfocar()">Limpiar y enfocar</button>
<p>Valor actual: {{ valorActual }}</p>
`
})
export class UsuarioComponent {
@ViewChild('nombreInput') inputElement!: ElementRef<HTMLInputElement>;
valorActual = '';
enfocarInput() {
this.inputElement.nativeElement.focus();
this.valorActual = this.inputElement.nativeElement.value;
}
limpiarYEnfocar() {
this.inputElement.nativeElement.value = '';
this.inputElement.nativeElement.focus();
this.valorActual = '';
}
}
Manipulación directa de elementos
El acceso programático nos permite realizar operaciones complejas que serían difíciles de implementar únicamente desde el template:
@Component({
selector: 'app-contador',
template: `
<div #display class="contador-display">0</div>
<button (click)="incrementar()">+</button>
<button (click)="decrementar()">-</button>
<button (click)="resetear()">Reset</button>
`,
styles: [`
.contador-display {
font-size: 2rem;
padding: 20px;
border: 2px solid #333;
text-align: center;
transition: all 0.3s ease;
}
.highlight {
background-color: #ffeb3b;
transform: scale(1.1);
}
`]
})
export class ContadorComponent {
@ViewChild('display') displayElement!: ElementRef<HTMLDivElement>;
private contador = 0;
incrementar() {
this.contador++;
this.actualizarDisplay();
this.destacarCambio();
}
decrementar() {
this.contador--;
this.actualizarDisplay();
this.destacarCambio();
}
resetear() {
this.contador = 0;
this.actualizarDisplay();
this.destacarCambio();
}
private actualizarDisplay() {
this.displayElement.nativeElement.textContent = this.contador.toString();
}
private destacarCambio() {
const element = this.displayElement.nativeElement;
element.classList.add('highlight');
setTimeout(() => element.classList.remove('highlight'), 300);
}
}
Casos de uso con formularios avanzados
El acceso programático es especialmente útil para validaciones personalizadas y control fino de formularios:
@Component({
selector: 'app-formulario',
template: `
<form>
<input #email type="email" placeholder="Email" (blur)="validarEmail()">
<div #emailError class="error" style="display: none;">
Email inválido
</div>
<input #password type="password" placeholder="Contraseña" (input)="verificarSeguridad()">
<div #passwordStrength class="strength-indicator">
Seguridad: <span #strengthText>Débil</span>
</div>
<button type="submit" [disabled]="!formularioValido">Registrarse</button>
</form>
`,
styles: [`
.error { color: red; font-size: 0.8rem; }
.strength-indicator { margin: 10px 0; }
.weak { color: red; }
.medium { color: orange; }
.strong { color: green; }
`]
})
export class FormularioComponent {
@ViewChild('email') emailInput!: ElementRef<HTMLInputElement>;
@ViewChild('emailError') emailError!: ElementRef<HTMLDivElement>;
@ViewChild('password') passwordInput!: ElementRef<HTMLInputElement>;
@ViewChild('strengthText') strengthText!: ElementRef<HTMLSpanElement>;
formularioValido = false;
validarEmail() {
const email = this.emailInput.nativeElement.value;
const esValido = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
if (esValido) {
this.emailError.nativeElement.style.display = 'none';
this.emailInput.nativeElement.style.borderColor = 'green';
} else {
this.emailError.nativeElement.style.display = 'block';
this.emailInput.nativeElement.style.borderColor = 'red';
}
this.verificarFormulario();
}
verificarSeguridad() {
const password = this.passwordInput.nativeElement.value;
const strengthElement = this.strengthText.nativeElement;
// Remover clases anteriores
strengthElement.classList.remove('weak', 'medium', 'strong');
if (password.length < 6) {
strengthElement.textContent = 'Débil';
strengthElement.classList.add('weak');
} else if (password.length < 10) {
strengthElement.textContent = 'Media';
strengthElement.classList.add('medium');
} else {
strengthElement.textContent = 'Fuerte';
strengthElement.classList.add('strong');
}
this.verificarFormulario();
}
private verificarFormulario() {
const emailValido = this.emailInput.nativeElement.validity.valid;
const passwordValida = this.passwordInput.nativeElement.value.length >= 6;
this.formularioValido = emailValido && passwordValida;
}
}
Interacción con componentes hijos
Las template reference variables también funcionan con componentes personalizados, permitiendo acceder a sus métodos y propiedades públicas:
// Componente hijo
@Component({
selector: 'app-reproductor',
template: `
<div class="reproductor">
<audio #audio controls>
<source [src]="cancionUrl" type="audio/mpeg">
</audio>
<div class="info">{{ estadoActual }}</div>
</div>
`
})
export class ReproductorComponent {
@ViewChild('audio') audioElement!: ElementRef<HTMLAudioElement>;
cancionUrl = 'assets/cancion.mp3';
estadoActual = 'Detenido';
reproducir() {
this.audioElement.nativeElement.play();
this.estadoActual = 'Reproduciendo';
}
pausar() {
this.audioElement.nativeElement.pause();
this.estadoActual = 'Pausado';
}
detener() {
this.audioElement.nativeElement.pause();
this.audioElement.nativeElement.currentTime = 0;
this.estadoActual = 'Detenido';
}
}
// Componente padre
@Component({
selector: 'app-control-musica',
template: `
<app-reproductor #reproductor></app-reproductor>
<div class="controles">
<button (click)="reproductor.reproducir()">Play</button>
<button (click)="reproductor.pausar()">Pause</button>
<button (click)="reproductor.detener()">Stop</button>
<button (click)="controlDesdeComponente()">Control desde TS</button>
</div>
`
})
export class ControlMusicaComponent {
@ViewChild('reproductor') reproductorRef!: ReproductorComponent;
controlDesdeComponente() {
// Acceso programático al componente hijo
if (this.reproductorRef.estadoActual === 'Detenido') {
this.reproductorRef.reproducir();
} else {
this.reproductorRef.pausar();
}
}
}
Consideraciones importantes sobre el acceso DOM
Cuando trabajamos con acceso directo al DOM, debemos tener en cuenta algunos aspectos importantes:
- Momento de acceso: Los elementos están disponibles después de que Angular inicialice la vista
- Seguridad de tipos: Usar
ElementRef<HTMLElementType>
para obtener autocompletado - Detección de cambios: Las modificaciones directas del DOM no activan automáticamente la detección de cambios
- Rendimiento: El acceso programático debe usarse cuando realmente sea necesario
@Component({
selector: 'app-ejemplo-timing',
template: `<div #miElemento>Contenido inicial</div>`
})
export class EjemploTimingComponent {
@ViewChild('miElemento') elemento!: ElementRef<HTMLDivElement>;
ngAfterViewInit() {
// Momento correcto para acceder al elemento
console.log('Elemento disponible:', this.elemento.nativeElement.textContent);
// Modificación segura del DOM
this.elemento.nativeElement.style.color = 'blue';
}
}
El acceso programático a elementos DOM mediante @ViewChild y template reference variables proporciona un control granular sobre la interfaz de usuario, permitiendo implementar funcionalidades avanzadas que van más allá de las capacidades del template binding básico.
Fuentes y referencias
Documentación oficial y recursos externos para profundizar en Angular
Documentación oficial de Angular
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 qué son y cómo se declaran las template reference variables en Angular.
- Aprender a acceder a propiedades y métodos de elementos HTML desde el template.
- Utilizar @ViewChild para acceder programáticamente a elementos y componentes desde TypeScript.
- Aplicar template reference variables en formularios y componentes personalizados.
- Conocer buenas prácticas y consideraciones al manipular el DOM directamente en Angular.