Reputation: 7200
I'm trying to find a way to make a reusable decorator for some of my components that are using HostListener.
What I have currently are several features (components) That are very similiar and all having the same @HostListener block:
@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;
})
}
}
The HostListener method is calling some function in a service (to get data from backend) and updates a local variables. The same service is injected to all components and the same variables is available in all of them. Infact - the logic is EXACT and repeated in all of those components.
What I would like to do, is to find a way to create a custom decorator that will wrap the repeated HostListener, such as:
@Component({...})
@WithScrollHostListener()
export class MySearchComponent implements OnInit, OnDestroy {
}
If required, I will create an interface for those components to declare of the common service and local variable to be used by the decorator.
Any idea, guidance or assistance on how to implement such decorator?
Thanks in advance.
Upvotes: 3
Views: 1364
Reputation: 2244
You can use custom decorator to implement it without @HostListner
How to implement it
WithScrollHostListener
)function WithScrollHostListener() {
return function decorator(constructor) {
...
}
}
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
})
);
}
});
};
}
Full codes
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) {
}
}
Upvotes: 2