Aprende a crear una directiva de atributo en Angular para contar clics en anchors y aprovechar datos de analytics sin complicaciones. Verás cómo usar HostListener para escuchar eventos, limitar el selector a hipervínculos y registrar información en console para debugging. Además, integra la directiva en app.module.ts y úsala en tu lista de lugares. Si luego quieres almacenar datos, lo ideal es persistirlos en un servidor con Node.
¿Qué problema resuelve esta directiva de Angular?
Esta solución te permite medir interacciones reales sobre enlaces y respaldar decisiones con datos. Es útil para presentar resultados a propietarios: visitas, clics diarios y comportamiento por elemento.
Contar clics por elemento con una directiva ligera.
Analytics accionables desde el front-end.
Escucha de eventos con HostListener de forma declarativa.
Alcance por anchor: cada enlace lleva su propio conteo.
¿Cómo se crea la directiva de atributo con HostListener?
Partimos de un archivo con la convención de Angular: nombre en pascal case para la clase, sufijo .directive.ts y un selector de atributo que aplica solo a enlaces.
// contar-clicks.directive.tsimport{Directive,HostListener}from'@angular/core';@Directive({ selector:'a[contarClicks]'})exportclassContarClicksDirective{private clickN =0;@HostListener('click',['$event.target'])onClick(btn:HTMLElement){console.log('En el anchor:', btn);console.log('Número de clicks:',this.clickN++);}}
Claves de la implementación:
@Directive con selector de atributo: 'a[contarClicks]'.
Variable de estado: clickN inicia en 0 y se incrementa en cada clic.
@HostListener('click'): escucha el evento y recibe el event target.
Salida de debugging: imprime el anchor, el mensaje y el conteo.
¿Cómo registrarla en app.module.ts?
Asegúrate de declararla para usarla en plantillas.
La directiva no recibe parámetros: no uses corchetes. Aplícala directamente sobre el anchor que envuelve el elemento.
<acontarClicks><li>La Gardenia</li></a><acontarClicks><li>Hotel La Gracia</li></a>
Tips prácticos para validar en la console del navegador:
Abre la pestaña consola y filtra niveles: desactiva errores si distraen.
Verás el mensaje con el número de clicks creciendo por elemento.
Cambia entre enlaces: cada uno conserva su propio conteo.
¿Qué debes saber de eventos y alcance?
Con HostListener el alcance es por instancia: cada anchor obtiene su propio oyente y su propio estado. Por eso, si haces clic en “La Gardenia” y luego en “Hotel La Gracia”, cada uno muestra su propio contador independiente.
Puntos clave para eventos:
Evento principal: click. Puedes usar otros como mouseover o blur.
Parámetro del oyente: $event.target para acceder al elemento objetivo.
Buenas prácticas: mantén el conteo en una propiedad privada y usa console para debugging.
Persistencia recomendada: guarda los analytics en servidor con Node cuando sea necesario.
¿Quieres que amplíe el ejemplo con envío de datos al servidor o más eventos del DOM? Cuéntame tu caso y te propongo el siguiente paso.
En realidad es un tema de visualización, cuando inicializamos clickN tiene que estar en 0 (no se ha hecho ningún click todavia), el problema es cuando se hace el console.log se muestra el valor de la variable y despues se incrementa su valor…
Perfecto, es justo lo que quería hacer.
Lo he agregado a mi código (utilizando mi nomenclatura) y ha funcionado perfectamente.
La verdad es que no tenía ni idea de como hacerlo.
De hecho lo he agregado pero aun tengo que analizarlo para saber y comprender realmente qué es lo que estoy haciendo.
Muchas gracias, muy buen aporte.
Enhorabuena.
HostListener: permiten capturar los eventos en nuestro componente.
Parámetros: evento y elemento
onClick: Función en donse se ejecuta la lógica de negocio momento de capturar el evento
Nota: La directiva fué invocada en el html sin "[]" porque no se pasan parámetros. En caso contrario es necesario colocarlos
El constructor en una clase sirve cuando para ejecutar codigo cuando esta se inicializa es decir solo con usar esa clase ya se ejecuta el codigo del constructor.
En el anterior ejemplo usan ese momento en el que se creaba una nueva instancia de resaltar para ejecutar el codigo.
En esta clase se hace de en cuanto ocurre un evento de HTML
¿Cual es la diferencia entre un @HostListener y un capturar un evento con un event binding? Me refiero, ¿no seria contraproducente usar un @HostListener para crear un evento cuando se puede hacer de una manera mas rapida y eficiente con un event binding?
Este error apareció en mi código.
TS7006: Parameter 'btn' implicitly has an 'any' type.
Lo solucione así
@HostListener('click', ['$event.target']) onClick(btn: any)
anclars?? , no seria mas facil decir la etiqueta de hipervinculo o enlaces , me pase un buen rato , para entender a que te referias , nose para que cojones se gasta dinero , solo para complicarse mas, Tu manera de enseñar deja mucho que desear .
Solo recuerden que el aumento del contador (++) tiene mucho que ver en donde lo pongas, si antes o despues de la variable. Como se está usando en el ejemplo, primero imprime y luego aumenta, por eso al primer click muestra 0. si pones
++this.clickN
aumentará el contador y luego imprimira, por lo cual, al hacer el primer click, ya te marcará en 1 y no en 0.
Tambien, si no quieres que te imprima o guarde toda la etiqueta html en la consola o en una variable (si es que así la ocupas), tambien puedes agregarle un id a la etiqueta <li> por medio de la sintaxis mustach:
@marcelohfontana, tienes razon, perooo… el problema esta en
this.clickN++
si cambias esto por
++this.clickN
veras que ya se soluciona sin tener que inciar la variable en 0,
existe una diferencia entre ++Numero y Numero++
el ++numero incrementa la variable y luego hace la operacion y el numero++ hace la operacion y luego incrementa la variable (en este caso la operacion es el console.log())
Tuve mucho interés de que los clicks (en mi caso visitas) fueran incrementadas en la misma instancia donde están los lugares, por que como esta explicado incrementa a una variable que es una instancia de la misma directiva, mas no de los datos que provienen del ts
acá dejo un ejemplo de lo que me refiero
Primero agregué un atributo de visitas a mis datos de los sitios y fue inicializado en 0
places: any =[{plan:'free',closeness:1,distance:0.50,name:"Barsoke",visits:0},
En mi anchor le agrego los [] a la directiva para pasar por parámetro la instancia del objeto
<ng-container *ngFor="let place of places"><a [count-visitors]="place">
y en mi directiva declaré ‘place: any’ para captar la instancia del objeto y así incrementar el place.visits++
@Directive({selector:'a[count-visitors]'})exportclassCountVisitors{ @Input('count-visitors') place :any; @HostListener('click',['$event.target'])printCounter(){this.place.visits++console.log("Numero de visitas",this.place.visits)}}
Nota: pensaba que tenía que llamar a la función onClick obligatoriamente, pero la verdad no es así, la anotación espera una función de JavaScript como parámetro y puede ser llamada como quiera al parecer (si sabes algo más, déjalo como respuesta)
Y por ultimo agregué el string interpolation para ver las visitas directas del databinding de la instancia y comprobar si realiza el cambio
{{place.visits}}
Y pues funcionó, no se si esto lo explica en otro vídeo pero quise probar por mis propios fundamentos!
Claro esta que estos cambios solo afectan a los datos en memoria, si de verdad se desea cambiar los mismos del lado del servidor pues optaríamos hacer una petición dentro de la función.
En ejemplo:
@Input('count-visitors') place :any;@HostListener('click',['$event.target'])doSomethingIfsClicked(){//updateVisitorsFrom(place.id, place.plan, place.name)}
<div>Lista de personas </div><ul><ng-container *ngFor="let person of persons; let i = index"><li
[ngStyle]="{ color: (person.active) ? 'black' : 'grey' }"[ngClass]="{ impar:(i+1) % 2 !== 0, par:(i+1) % 2 === 0 }"[ngSwitch]="person.active"[shade]="person.active" count-clicks>{{person.firstName}}-{{person.lastName}}-{{person.age}}<span *ngSwitchCase ="true"[ngStyle]="{color:'green'}">Activo</span><span *ngSwitchCase ="false"[ngStyle]="{color:'red'}">Borrado</span><span *ngIf ="person.active"(click)="deletePerson(person.id)"> x </span></li></ng-container></ul>
import{Directive,HostListener}from"@angular/core";@Directive({selector:'li[count-clicks]'})exportclassCountClicksDirective{ countClick =1; @HostListener("click",['$event.target'])onClick(btn){console.error('li', btn ,"numero de clicks:",this.countClick++);}}