Búsqueda de sitios web

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

  1. crear una función para implementar un decorador personalizado (WithScrollHostListener)

function WithScrollHostListener() {

  return function decorator(constructor) {
  
     ...
  }
  
 }
  1. 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) {
  }
}

Artículos relacionados