typescript - Angular - Crear un decorador común que envuelva @HostListener
Estoy tratando de encontrar una manera de crear un decorador reutilizable para algunos de mis componentes que están usando HostListener.
Lo que tengo actualmente son varias características (componentes) que son muy similares y todas tienen el mismo bloque @HostListener:
@Component({...})
export class MySearchComponent implements OnInit, OnDestroy {
@HostListener('window:scroll', [])
onScroll(): void {
this.loading = true;
this.commonService.getData(
this.tab,
this.query,
...
).subscribe(results => {
this.results = results;
this.loading = false;
})
}
}
El método HostListener está llamando a alguna función en un servicio (para obtener datos del backend) y actualiza variables locales. El mismo servicio se inyecta en todos los componentes y las mismas variables están disponibles en todos ellos. De hecho, la lógica es EXACTA y se repite en todos esos componentes.
Lo que me gustaría hacer es encontrar una manera de crear un decorador personalizado que envuelva el HostListener repetido, tal como:
@Component({...})
@WithScrollHostListener()
export class MySearchComponent implements OnInit, OnDestroy {
}
Si es necesario, crearé una interfaz para que esos componentes declaren el servicio común y la variable local que serán utilizados por el decorador.
¿Alguna idea, orientación o asistencia sobre cómo implementar tal decorador?
Gracias de antemano.
Respuesta #1
Puedes usar un decorador personalizado para implementarlo sin @HostListener
ejemplo en línea
Cómo implementarlo
- crear una función para implementar un decorador personalizado (
WithScrollHostListener)
function WithScrollHostListener() {
return function decorator(constructor) {
...
}
}- extender la devolución de llamada personalizada en el gancho de Angular (ver función decoradora)
function WithScrollHostListener() {
// required
function extendHook(arg: {
hookName: string;
target: {
prototype;
};
fn: (hookArg: { componentInstance }) => void;
}) {
const original = arg.target.prototype[arg.hookName];
arg.target.prototype[arg.hookName] = function(...args) {
arg.fn({
componentInstance: this
});
original && original.apply(this, args);
};
}
// required
return function decorator(constructor) {
extendHook({
// hook's name according to you (e.x. ngOnInit , ngAfterViewInit)
hookName: "ngOnInit",
target: constructor,
// setup your custom logic
fn: hookArg => {
window.addEventListener("scroll", () =>
scrollFn({
commonComponent: hookArg.componentInstance
})
);
}
});
};
}Códigos completos
import { Component, Injectable } from "@angular/core";
import { CommonService } from "./common.service";
// optional (shared the same structure with other component)
export interface CommonComponent {
loading?;
tab?;
query?;
results?;
commonService?: CommonService;
}
function WithScrollHostListener() {
// custom logic
function scrollFn(arg: { commonComponent: CommonComponent }) {
arg.commonComponent.loading = true;
arg.commonComponent.commonService
.getData(arg.commonComponent.tab, arg.commonComponent.query)
.subscribe(results => {
console.log(results);
arg.commonComponent.results = results;
arg.commonComponent.loading = false;
});
}
// required
function extendHook(arg: {
hookName: string;
target: {
prototype;
};
fn: (hookArg: { componentInstance }) => void;
}) {
const original = arg.target.prototype[arg.hookName];
arg.target.prototype[arg.hookName] = function(...args) {
arg.fn({
componentInstance: this
});
original && original.apply(this, args);
};
}
// required
return function decorator(constructor) {
extendHook({
// hook's name according to you (e.x. ngOnInit , ngAfterViewInit)
hookName: "ngOnInit",
target: constructor,
// setup your custom logic
fn: hookArg => {
window.addEventListener("scroll", () =>
scrollFn({
commonComponent: hookArg.componentInstance
})
);
}
});
};
}
@Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
@WithScrollHostListener()
export class AppComponent implements CommonComponent {
constructor(public commonService: CommonService) {
}
}