Eventos con paréntesis ()
El event binding en Angular nos permite responder a eventos del DOM de forma reactiva, capturando las interacciones del usuario y ejecutando lógica en nuestros componentes. La sintaxis utiliza paréntesis para envolver el nombre del evento, creando un enlace directo entre los eventos del navegador y los métodos de nuestro componente.
Sintaxis básica del event binding
La sintaxis fundamental del event binding sigue el patrón (evento)="método()"
, donde el evento va entre paréntesis y se asigna a una expresión TypeScript:
// contador.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-contador',
template: `
<div>
<p>Contador: {{ valor }}</p>
<button (click)="incrementar()">Incrementar</button>
<button (click)="decrementar()">Decrementar</button>
<button (click)="resetear()">Reset</button>
</div>
`
})
export class ContadorComponent {
valor = 0;
incrementar() {
this.valor++;
}
decrementar() {
this.valor--;
}
resetear() {
this.valor = 0;
}
}
Eventos DOM más comunes
Angular puede capturar cualquier evento DOM utilizando la misma sintaxis de paréntesis. Los eventos más utilizados incluyen:
Eventos de mouse:
@Component({
selector: 'app-eventos-mouse',
template: `
<div class="zona-eventos">
<button (click)="alHacerClick()">Click</button>
<button (dblclick)="alDobleClick()">Doble Click</button>
<div (mouseover)="alEntrarMouse()"
(mouseout)="alSalirMouse()"
class="area-hover">
Pasa el mouse aquí
</div>
</div>
<p>{{ mensaje }}</p>
`
})
export class EventosMouseComponent {
mensaje = 'Esperando interacción...';
alHacerClick() {
this.mensaje = 'Botón clickeado';
}
alDobleClick() {
this.mensaje = 'Doble click detectado';
}
alEntrarMouse() {
this.mensaje = 'Mouse sobre el área';
}
alSalirMouse() {
this.mensaje = 'Mouse fuera del área';
}
}
Eventos de teclado:
@Component({
selector: 'app-eventos-teclado',
template: `
<div>
<input type="text"
(keyup)="alTeclaUp()"
(keydown)="alTeclaDown()"
placeholder="Escribe algo...">
<p>Última acción: {{ ultimaAccion }}</p>
<p>Contenido: {{ contenido }}</p>
</div>
`
})
export class EventosTecladoComponent {
ultimaAccion = '';
contenido = '';
alTeclaUp() {
this.ultimaAccion = 'Tecla liberada';
}
alTeclaDown() {
this.ultimaAccion = 'Tecla presionada';
}
}
Acceso al objeto $event
Angular proporciona acceso al objeto de evento nativo a través de la variable especial $event
, que contiene toda la información del evento DOM:
@Component({
selector: 'app-evento-objeto',
template: `
<div>
<input type="text"
(input)="alCambiarTexto($event)"
placeholder="Escribe aquí...">
<button (click)="mostrarDetalles($event)">
Ver detalles del click
</button>
<p>Texto actual: {{ textoActual }}</p>
<p>Información del evento: {{ infoEvento }}</p>
</div>
`
})
export class EventoObjetoComponent {
textoActual = '';
infoEvento = '';
alCambiarTexto(evento: Event) {
const input = evento.target as HTMLInputElement;
this.textoActual = input.value;
}
mostrarDetalles(evento: MouseEvent) {
this.infoEvento = `Click en X: ${evento.clientX}, Y: ${evento.clientY}`;
}
}
Pasar parámetros personalizados
Además del objeto $event
, podemos pasar parámetros personalizados a nuestros métodos de manejo de eventos:
@Component({
selector: 'app-parametros-eventos',
template: `
<div>
<h3>Colores disponibles</h3>
<button (click)="cambiarColor('rojo')" class="btn-rojo">Rojo</button>
<button (click)="cambiarColor('azul')" class="btn-azul">Azul</button>
<button (click)="cambiarColor('verde')" class="btn-verde">Verde</button>
<div class="resultado" [style.background-color]="colorSeleccionado">
Color actual: {{ colorSeleccionado }}
</div>
<h3>Lista de tareas</h3>
@for (tarea of tareas; track tarea.id) {
<div class="tarea">
<span>{{ tarea.nombre }}</span>
<button (click)="eliminarTarea(tarea.id)">Eliminar</button>
</div>
}
</div>
`
})
export class ParametrosEventosComponent {
colorSeleccionado = 'transparent';
tareas = [
{ id: 1, nombre: 'Comprar comida' },
{ id: 2, nombre: 'Estudiar Angular' },
{ id: 3, nombre: 'Hacer ejercicio' }
];
cambiarColor(color: string) {
this.colorSeleccionado = color;
}
eliminarTarea(id: number) {
this.tareas = this.tareas.filter(tarea => tarea.id !== id);
}
}
Combinando $event con parámetros
Es posible combinar el objeto $event con parámetros personalizados para crear manejadores de eventos más sofisticados:
@Component({
selector: 'app-evento-mixto',
template: `
<div>
<h3>Formulario de usuario</h3>
<input type="text"
(blur)="validarCampo('nombre', $event)"
placeholder="Nombre">
<input type="email"
(blur)="validarCampo('email', $event)"
placeholder="Email">
<input type="number"
(input)="actualizarRango('edad', $event)"
min="1" max="100"
placeholder="Edad">
@if (errores.length > 0) {
<div class="errores">
@for (error of errores; track error) {
<p class="error">{{ error }}</p>
}
</div>
}
<p>Edad seleccionada: {{ edadActual }}</p>
</div>
`
})
export class EventoMixtoComponent {
errores: string[] = [];
edadActual = 0;
validarCampo(campo: string, evento: Event) {
const input = evento.target as HTMLInputElement;
const valor = input.value.trim();
// Limpiar errores previos de este campo
this.errores = this.errores.filter(error => !error.includes(campo));
if (!valor) {
this.errores.push(`El campo ${campo} es obligatorio`);
} else if (campo === 'email' && !valor.includes('@')) {
this.errores.push('El email debe tener un formato válido');
}
}
actualizarRango(campo: string, evento: Event) {
const input = evento.target as HTMLInputElement;
if (campo === 'edad') {
this.edadActual = parseInt(input.value) || 0;
}
}
}
Eventos en elementos anidados
Los eventos se pueden aplicar a cualquier elemento HTML, no solo a botones e inputs. Esto permite crear interfaces interactivas complejas:
@Component({
selector: 'app-elementos-anidados',
template: `
<div class="galeria">
<h3>Galería de imágenes</h3>
@for (imagen of imagenes; track imagen.id) {
<div class="imagen-container"
(click)="seleccionarImagen(imagen)">
<img [src]="imagen.url" [alt]="imagen.nombre">
<p>{{ imagen.nombre }}</p>
<button (click)="marcarFavorita(imagen.id, $event)"
class="btn-favorito">
{{ imagen.favorita ? '❤️' : '🤍' }}
</button>
</div>
}
@if (imagenSeleccionada) {
<div class="detalle">
<h4>Imagen seleccionada: {{ imagenSeleccionada.nombre }}</h4>
<p>Descripción: {{ imagenSeleccionada.descripcion }}</p>
</div>
}
</div>
`
})
export class ElementosAnidadosComponent {
imagenSeleccionada: any = null;
imagenes = [
{
id: 1,
nombre: 'Paisaje',
url: '/assets/paisaje.jpg',
descripcion: 'Hermoso paisaje montañoso',
favorita: false
},
{
id: 2,
nombre: 'Ciudad',
url: '/assets/ciudad.jpg',
descripcion: 'Vista nocturna de la ciudad',
favorita: true
}
];
seleccionarImagen(imagen: any) {
this.imagenSeleccionada = imagen;
}
marcarFavorita(id: number, evento: Event) {
// Prevenir que el click se propague al contenedor padre
evento.stopPropagation();
const imagen = this.imagenes.find(img => img.id === id);
if (imagen) {
imagen.favorita = !imagen.favorita;
}
}
}
La sintaxis de paréntesis para event binding proporciona una forma intuitiva y declarativa de manejar la interactividad en nuestras aplicaciones Angular, permitiendo crear experiencias de usuario ricas y responsivas con un código limpio y mantenible.
Manejo de eventos DOM
El manejo avanzado de eventos DOM en Angular va más allá de la simple captura de eventos, incluyendo el control de la propagación, la prevención de comportamientos predeterminados y la manipulación eficiente del DOM. Angular proporciona herramientas específicas para gestionar estos aspectos de forma elegante y performante.
Prevención del comportamiento predeterminado
Muchos elementos HTML tienen comportamientos predeterminados que pueden interferir con la lógica de nuestra aplicación. Angular permite prevenir estos comportamientos usando preventDefault()
en el objeto evento:
@Component({
selector: 'app-prevencion-default',
template: `
<div>
<h3>Formulario personalizado</h3>
<form (submit)="procesarFormulario($event)">
<input type="text" [(ngModel)]="usuario" placeholder="Usuario" required>
<input type="password" [(ngModel)]="password" placeholder="Contraseña" required>
<button type="submit">Iniciar Sesión</button>
</form>
<p>Estado: {{ estadoFormulario }}</p>
<h3>Enlaces personalizados</h3>
<a href="https://ejemplo.com"
(click)="manejarEnlace($event)">
Enlace interceptado
</a>
<div class="menu-contextual"
(contextmenu)="mostrarMenuContextual($event)">
Click derecho aquí
</div>
@if (menuVisible) {
<div class="menu" [style.left.px]="menuX" [style.top.px]="menuY">
<button (click)="accionMenu('opcion1')">Opción 1</button>
<button (click)="accionMenu('opcion2')">Opción 2</button>
</div>
}
</div>
`
})
export class PrevencionDefaultComponent {
usuario = '';
password = '';
estadoFormulario = 'Esperando datos...';
menuVisible = false;
menuX = 0;
menuY = 0;
procesarFormulario(evento: Event) {
// Prevenir el envío automático del formulario
evento.preventDefault();
if (this.usuario && this.password) {
this.estadoFormulario = 'Procesando login...';
// Aquí iría la lógica de autenticación
setTimeout(() => {
this.estadoFormulario = 'Login exitoso';
}, 1000);
} else {
this.estadoFormulario = 'Faltan datos obligatorios';
}
}
manejarEnlace(evento: Event) {
// Prevenir la navegación del enlace
evento.preventDefault();
console.log('Enlace interceptado, navegación personalizada');
// Aquí podríamos implementar navegación con Angular Router
}
mostrarMenuContextual(evento: MouseEvent) {
// Prevenir el menú contextual del navegador
evento.preventDefault();
this.menuX = evento.clientX;
this.menuY = evento.clientY;
this.menuVisible = true;
}
accionMenu(opcion: string) {
console.log(`Ejecutando ${opcion}`);
this.menuVisible = false;
}
}
Control de la propagación de eventos
El event bubbling es el proceso por el cual los eventos se propagan desde el elemento hijo hacia sus elementos padre. Angular permite controlar esta propagación usando stopPropagation()
:
@Component({
selector: 'app-propagacion-eventos',
template: `
<div class="contenedor-padre"
(click)="clickPadre()"
style="padding: 20px; background: lightblue;">
<p>Contenedor Padre</p>
<div class="contenedor-hijo"
(click)="clickHijo($event)"
style="padding: 15px; background: lightgreen;">
<p>Contenedor Hijo</p>
<button (click)="clickBotonNormal($event)">
Botón Normal
</button>
<button (click)="clickBotonSinPropagacion($event)">
Botón Sin Propagación
</button>
</div>
<div class="log">
<h4>Log de eventos:</h4>
@for (evento of logEventos; track $index) {
<p>{{ evento }}</p>
}
<button (click)="limpiarLog($event)">Limpiar Log</button>
</div>
</div>
`
})
export class PropagacionEventosComponent {
logEventos: string[] = [];
clickPadre() {
this.agregarLog('Click en contenedor PADRE');
}
clickHijo(evento: Event) {
this.agregarLog('Click en contenedor HIJO');
// Si descomentamos la siguiente línea, evitamos que el evento llegue al padre
// evento.stopPropagation();
}
clickBotonNormal(evento: Event) {
this.agregarLog('Click en BOTÓN NORMAL - se propaga');
}
clickBotonSinPropagacion(evento: Event) {
// Detenemos la propagación del evento
evento.stopPropagation();
this.agregarLog('Click en BOTÓN SIN PROPAGACIÓN - no se propaga');
}
limpiarLog(evento: Event) {
evento.stopPropagation();
this.logEventos = [];
}
private agregarLog(mensaje: string) {
this.logEventos.unshift(`${new Date().toLocaleTimeString()}: ${mensaje}`);
if (this.logEventos.length > 10) {
this.logEventos.pop();
}
}
}
Eventos de teclado avanzados
Angular facilita el manejo específico de teclas mediante pseudo-eventos que simplifican la captura de combinaciones de teclas:
@Component({
selector: 'app-eventos-teclado-avanzados',
template: `
<div class="editor">
<h3>Editor con atajos de teclado</h3>
<textarea
(keydown.enter)="nuevaLinea($event)"
(keydown.ctrl.s)="guardar($event)"
(keydown.ctrl.z)="deshacer($event)"
(keydown.escape)="cancelar($event)"
(keydown.tab)="sangria($event)"
[(ngModel)]="contenido"
placeholder="Escribe aquí... (Ctrl+S para guardar, Ctrl+Z para deshacer, Tab para sangría)"
rows="10" cols="50">
</textarea>
<div class="estado">
<p>Estado: {{ estadoEditor }}</p>
<p>Líneas: {{ contarLineas() }}</p>
<p>Caracteres: {{ contenido.length }}</p>
</div>
<div class="historial">
<h4>Historial de acciones:</h4>
@for (accion of historialAcciones; track $index) {
<p>{{ accion }}</p>
}
</div>
</div>
`
})
export class EventosTecladoAvanzadosComponent {
contenido = '';
estadoEditor = 'Listo';
historialAcciones: string[] = [];
historialContenido: string[] = [];
nuevaLinea(evento: KeyboardEvent) {
// Solo agregar nueva línea si no hay modificadores
if (!evento.shiftKey) {
this.agregarAccion('Nueva línea agregada');
}
}
guardar(evento: KeyboardEvent) {
evento.preventDefault(); // Prevenir el comportamiento del navegador
this.estadoEditor = 'Guardando...';
this.agregarAccion('Documento guardado');
// Simular guardado
setTimeout(() => {
this.estadoEditor = 'Guardado exitosamente';
setTimeout(() => this.estadoEditor = 'Listo', 2000);
}, 500);
}
deshacer(evento: KeyboardEvent) {
evento.preventDefault();
if (this.historialContenido.length > 0) {
this.contenido = this.historialContenido.pop() || '';
this.agregarAccion('Acción deshecha');
} else {
this.agregarAccion('No hay acciones para deshacer');
}
}
cancelar(evento: KeyboardEvent) {
this.estadoEditor = 'Operación cancelada';
this.agregarAccion('Escape presionado - operación cancelada');
}
sangria(evento: KeyboardEvent) {
evento.preventDefault();
const textarea = evento.target as HTMLTextAreaElement;
const inicio = textarea.selectionStart;
const fin = textarea.selectionEnd;
// Guardar estado para deshacer
this.historialContenido.push(this.contenido);
// Agregar sangría
this.contenido = this.contenido.substring(0, inicio) +
' ' +
this.contenido.substring(fin);
this.agregarAccion('Sangría aplicada');
// Restaurar posición del cursor
setTimeout(() => {
textarea.selectionStart = textarea.selectionEnd = inicio + 4;
});
}
contarLineas(): number {
return this.contenido.split('\n').length;
}
private agregarAccion(accion: string) {
this.historialAcciones.unshift(`${new Date().toLocaleTimeString()}: ${accion}`);
if (this.historialAcciones.length > 5) {
this.historialAcciones.pop();
}
}
}
Eventos de formulario especializados
Los eventos de formulario requieren un manejo cuidadoso para crear experiencias de usuario fluidas:
@Component({
selector: 'app-eventos-formulario',
template: `
<form class="formulario-avanzado" (submit)="enviarFormulario($event)">
<h3>Formulario con validación en tiempo real</h3>
<div class="campo">
<label>Email:</label>
<input type="email"
(input)="validarEmail($event)"
(focus)="campoEnFoco('email')"
(blur)="campoFueraDeFoco('email', $event)"
[class.valido]="validacionEmail.valido"
[class.invalido]="validacionEmail.invalido && validacionEmail.tocado">
@if (validacionEmail.invalido && validacionEmail.tocado) {
<span class="error">{{ validacionEmail.mensaje }}</span>
}
</div>
<div class="campo">
<label>Teléfono:</label>
<input type="tel"
(input)="formatearTelefono($event)"
(paste)="manejarPegado($event)"
placeholder="(xxx) xxx-xxxx">
</div>
<div class="campo">
<label>Archivo:</label>
<input type="file"
(change)="manejarArchivo($event)"
accept=".pdf,.doc,.docx">
@if (archivoSeleccionado) {
<p>Archivo: {{ archivoSeleccionado.name }}
({{ formatearTamaño(archivoSeleccionado.size) }})</p>
}
</div>
<div class="campo">
<label>Comentarios:</label>
<textarea
(input)="contarCaracteres($event)"
maxlength="500"
rows="4">
</textarea>
<small>{{ caracteresRestantes }}/500 caracteres restantes</small>
</div>
<button type="submit" [disabled]="!formularioValido()">
Enviar Formulario
</button>
<div class="estado-formulario">
<p>Campo activo: {{ campoActivo }}</p>
<p>Estado: {{ estadoFormulario }}</p>
</div>
</form>
`
})
export class EventosFormularioComponent {
validacionEmail = { valido: false, invalido: false, tocado: false, mensaje: '' };
archivoSeleccionado: File | null = null;
caracteresRestantes = 500;
campoActivo = 'ninguno';
estadoFormulario = 'Pendiente';
validarEmail(evento: Event) {
const input = evento.target as HTMLInputElement;
const email = input.value;
const regexEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
this.validacionEmail.valido = regexEmail.test(email);
this.validacionEmail.invalido = !this.validacionEmail.valido && email.length > 0;
this.validacionEmail.mensaje = this.validacionEmail.invalido ?
'Por favor ingresa un email válido' : '';
}
campoEnFoco(campo: string) {
this.campoActivo = campo;
}
campoFueraDeFoco(campo: string, evento: Event) {
if (campo === 'email') {
this.validacionEmail.tocado = true;
}
this.campoActivo = 'ninguno';
}
formatearTelefono(evento: Event) {
const input = evento.target as HTMLInputElement;
let valor = input.value.replace(/\D/g, ''); // Solo números
if (valor.length >= 6) {
valor = `(${valor.slice(0, 3)}) ${valor.slice(3, 6)}-${valor.slice(6, 10)}`;
} else if (valor.length >= 3) {
valor = `(${valor.slice(0, 3)}) ${valor.slice(3)}`;
}
input.value = valor;
}
manejarPegado(evento: ClipboardEvent) {
evento.preventDefault();
const datosPegados = evento.clipboardData?.getData('text') || '';
const soloNumeros = datosPegados.replace(/\D/g, '');
if (soloNumeros.length === 10) {
const input = evento.target as HTMLInputElement;
input.value = `(${soloNumeros.slice(0, 3)}) ${soloNumeros.slice(3, 6)}-${soloNumeros.slice(6)}`;
}
}
manejarArchivo(evento: Event) {
const input = evento.target as HTMLInputElement;
const archivo = input.files?.[0];
if (archivo) {
// Validar tamaño (máximo 5MB)
if (archivo.size > 5 * 1024 * 1024) {
alert('El archivo es demasiado grande. Máximo 5MB.');
input.value = '';
return;
}
this.archivoSeleccionado = archivo;
} else {
this.archivoSeleccionado = null;
}
}
contarCaracteres(evento: Event) {
const textarea = evento.target as HTMLTextAreaElement;
this.caracteresRestantes = 500 - textarea.value.length;
}
enviarFormulario(evento: Event) {
evento.preventDefault();
if (this.formularioValido()) {
this.estadoFormulario = 'Enviando...';
// Simular envío
setTimeout(() => {
this.estadoFormulario = 'Enviado exitosamente';
}, 1000);
}
}
formularioValido(): boolean {
return this.validacionEmail.valido && this.archivoSeleccionado !== null;
}
formatearTamaño(bytes: number): string {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const tamaños = ['Bytes', 'KB', 'MB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + tamaños[i];
}
}
Gestión eficiente de listeners
Para aplicaciones complejas, es importante gestionar eficientemente los event listeners para evitar memory leaks y mejorar el rendimiento:
@Component({
selector: 'app-gestion-listeners',
template: `
<div class="gestion-eventos">
<h3>Gestión avanzada de eventos</h3>
<div class="zona-scroll"
#zonaScroll
(scroll)="manejarScrollOptimizado($event)"
style="height: 200px; overflow-y: auto; border: 1px solid #ccc;">
@for (item of items; track item) {
<div class="item">{{ item }}</div>
}
</div>
<p>Posición scroll: {{ posicionScroll }}px</p>
<div class="zona-resize">
<p>Tamaño ventana: {{ tamañoVentana.width }}x{{ tamañoVentana.height }}</p>
<p>Eventos de resize: {{ contadorResize }}</p>
</div>
<button (click)="toggleListeners()">
{{ listenersActivos ? 'Desactivar' : 'Activar' }} Listeners
</button>
</div>
`
})
export class GestionListenersComponent {
items = Array.from({ length: 50 }, (_, i) => `Item ${i + 1}`);
posicionScroll = 0;
tamañoVentana = { width: 0, height: 0 };
contadorResize = 0;
listenersActivos = true;
private timeoutScroll: any;
private resizeListener?: () => void;
ngOnInit() {
this.inicializarListeners();
}
ngOnDestroy() {
this.limpiarListeners();
}
manejarScrollOptimizado(evento: Event) {
// Debouncing para optimizar el rendimiento
clearTimeout(this.timeoutScroll);
this.timeoutScroll = setTimeout(() => {
const elemento = evento.target as Element;
this.posicionScroll = elemento.scrollTop;
}, 16); // ~60fps
}
private inicializarListeners() {
// Listener de resize optimizado
this.resizeListener = this.throttle(() => {
this.tamañoVentana = {
width: window.innerWidth,
height: window.innerHeight
};
this.contadorResize++;
}, 250);
if (this.listenersActivos) {
window.addEventListener('resize', this.resizeListener);
this.tamañoVentana = {
width: window.innerWidth,
height: window.innerHeight
};
}
}
private limpiarListeners() {
if (this.resizeListener) {
window.removeEventListener('resize', this.resizeListener);
}
clearTimeout(this.timeoutScroll);
}
toggleListeners() {
this.listenersActivos = !this.listenersActivos;
if (this.listenersActivos) {
this.inicializarListeners();
} else {
this.limpiarListeners();
}
}
// Función throttle para optimizar eventos frecuentes
private throttle(func: Function, delay: number) {
let timeoutId: any;
let lastExecTime = 0;
return (...args: any[]) => {
const currentTime = Date.now();
if (currentTime - lastExecTime > delay) {
func.apply(this, args);
lastExecTime = currentTime;
} else {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, args);
lastExecTime = Date.now();
}, delay - (currentTime - lastExecTime));
}
};
}
}
El manejo avanzado de eventos DOM en Angular proporciona control granular sobre la interactividad, permitiendo crear aplicaciones web sofisticadas que responden de manera eficiente a las acciones del usuario mientras mantienen un rendimiento óptimo.

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 básica del event binding en Angular usando paréntesis.
- Aprender a capturar eventos comunes del DOM como clicks, movimientos del ratón y eventos de teclado.
- Utilizar el objeto $event para acceder a detalles del evento y combinarlo con parámetros personalizados.
- Gestionar la propagación y prevención de comportamientos predeterminados de eventos.
- Implementar técnicas avanzadas para optimizar el manejo de listeners y eventos en formularios y elementos anidados.