@HostBinding para propiedades del host
El decorador @HostBinding permite modificar dinámicamente propiedades, atributos, clases CSS y estilos del elemento host donde se aplica un componente o directiva. Esta funcionalidad es esencial para crear componentes y directivas que puedan adaptar su apariencia y comportamiento según su estado interno.
Conceptos fundamentales
El elemento host es el elemento DOM al que se aplica un componente o directiva. Con @HostBinding podemos vincular propiedades de nuestra clase TypeScript directamente a propiedades del elemento host, creando una conexión reactiva entre el estado interno y la presentación visual.
import { Component, HostBinding } from '@angular/core';
@Component({
selector: 'app-status-badge',
template: `<span>{{ status }}</span>`,
standalone: true
})
export class StatusBadgeComponent {
status = 'active';
// Vincula la propiedad className del host al valor calculado
@HostBinding('class')
get cssClass() {
return `status-badge status-${this.status}`;
}
}
Binding de clases CSS
Una de las aplicaciones más comunes de @HostBinding es la gestión dinámica de clases CSS. Podemos vincular clases individuales o múltiples clases según el estado del componente.
Binding de clase individual:
@Component({
selector: 'app-button',
template: `<ng-content></ng-content>`,
standalone: true
})
export class ButtonComponent {
@Input() disabled = false;
@Input() variant: 'primary' | 'secondary' = 'primary';
// Vincula una clase específica
@HostBinding('class.disabled')
get isDisabled() {
return this.disabled;
}
@HostBinding('class.btn-primary')
get isPrimary() {
return this.variant === 'primary';
}
@HostBinding('class.btn-secondary')
get isSecondary() {
return this.variant === 'secondary';
}
}
Binding de múltiples clases:
@Component({
selector: 'app-card',
template: `<ng-content></ng-content>`,
standalone: true
})
export class CardComponent {
@Input() elevated = false;
@Input() size: 'small' | 'medium' | 'large' = 'medium';
@HostBinding('class')
get hostClasses() {
const classes = ['card', `card-${this.size}`];
if (this.elevated) {
classes.push('card-elevated');
}
return classes.join(' ');
}
}
Binding de estilos CSS
@HostBinding también permite modificar estilos directamente en el elemento host, útil para valores dinámicos o propiedades calculadas.
@Component({
selector: 'app-progress-bar',
template: `
<div class="progress-label">{{ label }}</div>
<div class="progress-fill"></div>
`,
standalone: true
})
export class ProgressBarComponent {
@Input() progress = 0;
@Input() color = '#007bff';
@Input() label = '';
// Binding de estilo individual
@HostBinding('style.width.%')
get widthPercentage() {
return Math.min(Math.max(this.progress, 0), 100);
}
@HostBinding('style.background-color')
get backgroundColor() {
return this.color;
}
// Binding de múltiples estilos
@HostBinding('style')
get hostStyles() {
return {
'border-radius': '4px',
'height': '20px',
'transition': 'width 0.3s ease'
};
}
}
Binding de atributos HTML
Los atributos HTML también pueden ser vinculados dinámicamente, especialmente útil para atributos de accesibilidad o propiedades específicas del elemento.
@Component({
selector: 'app-tooltip',
template: `<ng-content></ng-content>`,
standalone: true
})
export class TooltipComponent {
@Input() tooltipText = '';
@Input() position: 'top' | 'bottom' | 'left' | 'right' = 'top';
@HostBinding('attr.title')
get title() {
return this.tooltipText;
}
@HostBinding('attr.data-position')
get dataPosition() {
return this.position;
}
@HostBinding('attr.role')
get role() {
return 'tooltip';
}
}
Combinando @HostBinding con signals
En Angular 20+, podemos integrar signals con @HostBinding para crear bindings completamente reactivos.
@Component({
selector: 'app-theme-switcher',
template: `
<button (click)="toggleTheme()">
{{ theme() === 'dark' ? '🌙' : '☀️' }}
</button>
`,
standalone: true
})
export class ThemeSwitcherComponent {
theme = signal<'light' | 'dark'>('light');
@HostBinding('class.theme-dark')
get isDarkTheme() {
return this.theme() === 'dark';
}
@HostBinding('class.theme-light')
get isLightTheme() {
return this.theme() === 'light';
}
toggleTheme() {
this.theme.update(current => current === 'dark' ? 'light' : 'dark');
}
}
Casos de uso prácticos
Componente de notificación con estados:
@Component({
selector: 'app-notification',
template: `
<div class="notification-content">
<span class="notification-icon">{{ getIcon() }}</span>
<span class="notification-message">
<ng-content></ng-content>
</span>
</div>
`,
standalone: true
})
export class NotificationComponent {
@Input() type: 'success' | 'warning' | 'error' | 'info' = 'info';
@Input() dismissible = false;
@HostBinding('class')
get notificationClasses() {
const classes = ['notification', `notification-${this.type}`];
if (this.dismissible) {
classes.push('notification-dismissible');
}
return classes.join(' ');
}
@HostBinding('attr.role')
get ariaRole() {
return this.type === 'error' ? 'alert' : 'status';
}
private getIcon() {
const icons = {
success: '✅',
warning: '⚠️',
error: '❌',
info: 'ℹ️'
};
return icons[this.type];
}
}
Esta aproximación permite crear componentes altamente reutilizables donde el elemento host se adapta automáticamente según el estado interno, manteniendo una separación clara entre la lógica del componente y su presentación visual.
@HostListener para eventos del host
El decorador @HostListener permite escuchar y responder a eventos DOM del elemento host donde se aplica un componente o directiva. Esta funcionalidad es fundamental para crear componentes interactivos que reaccionen a la interacción del usuario de manera directa y eficiente.
Conceptos fundamentales
@HostListener establece una conexión directa entre eventos DOM del elemento host y métodos de nuestra clase TypeScript. A diferencia de los event bindings en templates, @HostListener actúa sobre el propio elemento host, permitiendo crear comportamientos que se integran naturalmente con el elemento contenedor.
import { Component, HostListener } from '@angular/core';
@Component({
selector: 'app-interactive-card',
template: `
<div class="card-content">
<h3>{{ title }}</h3>
<p>Hover over this card!</p>
</div>
`,
standalone: true
})
export class InteractiveCardComponent {
title = 'Interactive Card';
// Escucha eventos de mouse en el host element
@HostListener('mouseenter')
onMouseEnter() {
console.log('Mouse entered the card');
}
@HostListener('mouseleave')
onMouseLeave() {
console.log('Mouse left the card');
}
}
Eventos de teclado
Los eventos de teclado son especialmente útiles para crear componentes accesibles y con navegación avanzada. @HostListener puede capturar eventos como keydown, keyup y keypress.
@Component({
selector: 'app-modal',
template: `
<div class="modal-backdrop">
<div class="modal-content">
<h2>{{ title }}</h2>
<button (click)="close()">Close</button>
<ng-content></ng-content>
</div>
</div>
`,
standalone: true
})
export class ModalComponent {
@Input() title = 'Modal';
@Output() closed = new EventEmitter<void>();
// Escucha la tecla Escape para cerrar el modal
@HostListener('keydown.escape')
onEscapePressed() {
this.close();
}
// Navegación con flechas
@HostListener('keydown.arrowup', ['$event'])
@HostListener('keydown.arrowdown', ['$event'])
onArrowNavigation(event: KeyboardEvent) {
event.preventDefault();
// Lógica de navegación por elementos focusables
this.navigateFocusableElements(event.key === 'ArrowUp' ? -1 : 1);
}
close() {
this.closed.emit();
}
private navigateFocusableElements(direction: number) {
// Implementación de navegación por teclado
const focusableElements = this.getFocusableElements();
const currentIndex = focusableElements.indexOf(document.activeElement as HTMLElement);
const nextIndex = (currentIndex + direction + focusableElements.length) % focusableElements.length;
focusableElements[nextIndex]?.focus();
}
private getFocusableElements(): HTMLElement[] {
const selector = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
return Array.from(document.querySelectorAll(selector)) as HTMLElement[];
}
}
Eventos de mouse y touch
Los eventos de mouse permiten crear interacciones sofisticadas y experiencias de usuario enriquecidas.
@Component({
selector: 'app-draggable',
template: `
<div class="draggable-content">
<span class="drag-handle">⋮⋮</span>
<ng-content></ng-content>
</div>
`,
standalone: true
})
export class DraggableComponent {
private isDragging = false;
private startX = 0;
private startY = 0;
@HostListener('mousedown', ['$event'])
onMouseDown(event: MouseEvent) {
if ((event.target as HTMLElement).classList.contains('drag-handle')) {
this.isDragging = true;
this.startX = event.clientX;
this.startY = event.clientY;
event.preventDefault();
}
}
@HostListener('document:mousemove', ['$event'])
onMouseMove(event: MouseEvent) {
if (this.isDragging) {
const deltaX = event.clientX - this.startX;
const deltaY = event.clientY - this.startY;
// Aplicar transformación
(event.currentTarget as HTMLElement).style.transform =
`translate(${deltaX}px, ${deltaY}px)`;
}
}
@HostListener('document:mouseup')
onMouseUp() {
this.isDragging = false;
}
// Soporte para dispositivos táctiles
@HostListener('touchstart', ['$event'])
onTouchStart(event: TouchEvent) {
const touch = event.touches[0];
this.startX = touch.clientX;
this.startY = touch.clientY;
this.isDragging = true;
}
}
Gestión de foco y accesibilidad
@HostListener es especialmente útil para la accesibilidad, permitiendo crear componentes que responden correctamente a la navegación por teclado.
@Component({
selector: 'app-dropdown',
template: `
<button class="dropdown-trigger"
(click)="toggle()"
[attr.aria-expanded]="isOpen()">
{{ placeholder }}
</button>
@if (isOpen()) {
<ul class="dropdown-menu" role="listbox">
@for (option of options; track option.id) {
<li role="option"
[class.selected]="selectedOption()?.id === option.id"
(click)="selectOption(option)">
{{ option.label }}
</li>
}
</ul>
}
`,
standalone: true
})
export class DropdownComponent {
@Input() options: Array<{id: string, label: string}> = [];
@Input() placeholder = 'Select an option';
isOpen = signal(false);
selectedOption = signal<{id: string, label: string} | null>(null);
// Cierra el dropdown al hacer click fuera
@HostListener('document:click', ['$event'])
onDocumentClick(event: Event) {
const target = event.target as HTMLElement;
if (!target.closest('app-dropdown')) {
this.isOpen.set(false);
}
}
// Manejo de navegación por teclado
@HostListener('keydown.enter')
@HostListener('keydown.space')
onEnterOrSpace() {
if (!this.isOpen()) {
this.isOpen.set(true);
}
}
@HostListener('keydown.escape')
onEscape() {
if (this.isOpen()) {
this.isOpen.set(false);
}
}
@HostListener('keydown.tab')
onTab() {
this.isOpen.set(false);
}
toggle() {
this.isOpen.update(current => !current);
}
selectOption(option: {id: string, label: string}) {
this.selectedOption.set(option);
this.isOpen.set(false);
}
}
Combinando con signals y estado reactivo
En Angular 20+, podemos combinar @HostListener con signals para crear componentes completamente reactivos.
@Component({
selector: 'app-focus-tracker',
template: `
<div class="focus-indicator">
Focus count: {{ focusCount() }}
<br>
Currently focused: {{ isFocused() ? 'Yes' : 'No' }}
</div>
<ng-content></ng-content>
`,
standalone: true
})
export class FocusTrackerComponent {
isFocused = signal(false);
focusCount = signal(0);
@HostListener('focus')
onFocus() {
this.isFocused.set(true);
this.focusCount.update(count => count + 1);
}
@HostListener('blur')
onBlur() {
this.isFocused.set(false);
}
// Efecto reactivo basado en el estado de foco
focusEffect = effect(() => {
if (this.isFocused()) {
console.log(`Element focused ${this.focusCount()} times`);
}
});
}
Directivas host con @HostListener
Las host directives de Angular 15+ pueden aprovechar @HostListener para crear comportamientos composables y reutilizables.
@Directive({
selector: '[appClickOutside]',
standalone: true
})
export class ClickOutsideDirective {
@Output() clickOutside = new EventEmitter<void>();
@HostListener('document:click', ['$event'])
onDocumentClick(event: Event) {
const target = event.target as HTMLElement;
const hostElement = this.elementRef.nativeElement;
if (!hostElement.contains(target)) {
this.clickOutside.emit();
}
}
constructor(private elementRef: ElementRef) {}
}
// Uso como host directive
@Component({
selector: 'app-sidebar',
template: `<nav class="sidebar-content"><ng-content></ng-content></nav>`,
hostDirectives: [
{
directive: ClickOutsideDirective,
outputs: ['clickOutside']
}
],
standalone: true
})
export class SidebarComponent {
isOpen = signal(true);
@HostListener('clickOutside')
onClickOutside() {
this.isOpen.set(false);
}
}
Mejores prácticas
Gestión de memoria y performance:
@Component({
selector: 'app-scroll-tracker',
template: `<div>Scroll position: {{ scrollY() }}px</div>`,
standalone: true
})
export class ScrollTrackerComponent implements OnDestroy {
scrollY = signal(0);
private destroyed = signal(false);
@HostListener('window:scroll', ['$event'])
onWindowScroll() {
// Throttle para mejorar performance
if (!this.destroyed()) {
requestAnimationFrame(() => {
this.scrollY.set(window.scrollY);
});
}
}
ngOnDestroy() {
this.destroyed.set(true);
}
}
@HostListener proporciona una interfaz limpia y declarativa para manejar eventos del elemento host, permitiendo crear componentes que responden naturalmente a la interacción del usuario mientras mantienen un código organizado y fácil de mantener.

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 el uso de @HostBinding para modificar propiedades, clases, estilos y atributos del elemento host.
- Aprender a utilizar @HostListener para escuchar y manejar eventos DOM del elemento host.
- Integrar signals de Angular 20+ con @HostBinding y @HostListener para lograr reactividad avanzada.
- Aplicar @HostBinding y @HostListener en casos prácticos para mejorar la reutilización y accesibilidad de componentes.
- Conocer buenas prácticas para la gestión de memoria y rendimiento al usar estos decoradores.