Angular2 @HostListener will not work in a derived component

I have an application with the Angular2 client side. I have a base class:

abstract class BaseClass { @HostListener('window:beforeunload') beforeUnloadHandler() { console.log('bla'); } } 

and two very similar derived classes:

 @Component({ selector: 'derived-one', templateUrl: './templates/app/+derived-one/derived-one.component.html' }) export class DerivedOne extends BaseClass { } @Component({ selector: 'derived-two', templateUrl: './templates/app/+derived-two/derived-two.component.html' }) export class DerivedTwo extends BaseClass { } 

The problem is that, for example, in DerivedOne beforeUnloadHandler works fine, and in DerivedTwo it does not receive a call at all.

I know that it is difficult to find the reason why this happens, just looking at the information above, but maybe someone may suspect that it might cause such strange behavior.

A few more notes:

If I use the following:

 abstract class BaseClass constructor(){ window.onbeforeunload = function(){ console.log('bla'); } } } 

everything works fine, but I would still like to find a solution based on Angular2;

If i write

 abstract class BaseClass { beforeUnloadHandler() { console.log('bla'); } } 

and in derived-two.component.html

 <div (window.beforeunload)="beforeUnloadHandler()"></div> 

everything works fine, but it looks like an ugly hack,

Again, if I write

 abstract class BaseClass { beforeUnloadHandler() { console.log('bla'); } } 

and

 @Component({ selector: 'derived-two', host: {'window:beforeunload': 'beforeUnloadHandler' } templateUrl: './templates/app/+derived-two/derived-two.component.html' }) export class DerivedTwo extends BaseClass { } 

he will not work.

Finally, if I use @HostListener in DerivedTwo and DerivedOne , it works, but I would like to avoid code duplication.

Let's hope that the information above is enough for the job (at least have some guesses).

+6
source share
1 answer

Update 2.3.0

Now you can use object inheritance for components.

You can see more detailed information in this commit https://github.com/angular/angular/commit/f5c8e0989d85bc064f689fc3595207dfb29413f4

Old version

1) If you have a class:

 abstract class BaseClass { @HostListener('window:beforeunload') beforeUnloadHander() { console.log('bla'); } } 

then he will work

Plunger example (put spaces somewhere in the editor and view console)

, but be careful , because Angular2 does not support full inheritance - Binding problem and @ViewChild a>

But it is still unclear why the solution with @HostListener did not work in the first place

In particular, if you have a property decorator on your derived component, it will not work. For example, let's say we have the following code:

 abstract class BaseClass { @HostListener('window:beforeunload') beforeUnloadHander() { console.log(`bla-bla from${this.constructor.name}`); } } @Component({ selector: 'derived-one', template: '<h2>derived-one</h2>' }) export class DerivedOne extends BaseClass { @Input() test; } 

Plunker

It will be converted to javascript, for example:

 var core_1 = require('@angular/core'); var BaseClass = (function () { function BaseClass() { } BaseClass.prototype.beforeUnloadHander = function () { console.log("bla-bla from" + this.constructor.name); }; __decorate([ core_1.HostListener('window:beforeunload'), __metadata('design:type', Function), __metadata('design:paramtypes', []), __metadata('design:returntype', void 0) ], BaseClass.prototype, "beforeUnloadHander", null); return BaseClass; }()); var DerivedOne = (function (_super) { __extends(DerivedOne, _super); function DerivedOne() { _super.apply(this, arguments); } __decorate([ core_1.Input(), __metadata('design:type', Object) ], DerivedOne.prototype, "test", void 0); DerivedOne = __decorate([ core_1.Component({ selector: 'derived-one', template: '<h2>derived-one</h2>' }), __metadata('design:paramtypes', []) ], DerivedOne); return DerivedOne; }(BaseClass)); 

We are interested in the following lines:

  __decorate([ core_1.HostListener('window:beforeunload'), __metadata('design:type', Function), __metadata('design:paramtypes', []), __metadata('design:returntype', void 0) ], BaseClass.prototype, "beforeUnloadHander", null); ... __decorate([ core_1.Input(), __metadata('design:type', Object) ], DerivedOne.prototype, "test", void 0); 

HostListener and Input are property decorators ( propMetadata key). This method will define two metadata records - on BaseClass and on DerivedOne enter image description here enter image description here

Finally, when angular2 will retrieve metadata from the DerivedOne class, it will only use its own metadata:

enter image description here

To get all the metadata, you can write your own decorator, for example:

 function InheritPropMetadata() { return (target: Function) => { const targetProps = Reflect.getMetadata('propMetadata', target); const parentTarget = Object.getPrototypeOf(target.prototype).constructor; const parentProps = Reflect.getMetadata('propMetadata', parentTarget); const mergedProps = Object.assign(targetProps, parentProps); Reflect.defineMetadata('propMetadata', mergedProps, target); }; }; @InheritPropMetadata() export class DerivedOne extends BaseClass { 

Here is a demo

2) If you have done the following:

 abstract class BaseClass constructor(){ window.onbeforeunload = function(){ console.log('bla'); }; } } 

then it will only be called once because you redefine the window.onbeforeunload handler each time. Instead, you should use the following:

 abstract class BaseClass { constructor(){ window.addEventListener('beforeunload', () =>{ console.log(`bla-bla from${this.constructor.name}`); }) } } 

Plunger example

3) Finally, if you have a base class, as shown below:

 abstract class BaseClass { beforeUnloadHander() { console.log(`bla-bla from${this.constructor.name}`); } } 

then you should use the correct syntax ( which you are missing the bracket ) in the decorator property:

 host: {'(window:beforeunload)': 'beforeUnloadHander()' } 

Plunger example

Hope this helps you!

+8
source

All Articles