I have an application with Angular2-based client side. I have a base class:
abstract class BaseClass {
@HostListener('window:beforeunload') beforeUnloadHandler() {
console.log('bla');
}
}
and two very similar to each other 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 while in DerivedTwo
it doesn’t receive a call at all.
I know it’s hard to find the reason why it happens just looking on the information above, but maybe someone might have a suspicion what could 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 still would like to find an Angular2-based solution;
If I write
abstract class BaseClass {
beforeUnloadHandler() {
console.log('bla');
}
}
and in derived-two.component.html
<div (window.beforeunload)="beforeUnloadHandler()"></div>
everything works fine too, 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 {
}
it won’t work.
Finally, if I use @HostListener
in DerivedTwo
and in DerivedOne
, it works, but I would like to avoid using duplicate code.
Hopefully, the information above would be enough to work with (at least to have some guesses).
Update 2.3.0
You can now take advantage of object inheritance for components.
More details you can see 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 it will work
Plunker Example (put whitespace somewhere in editor and watch console)
but be careful since Angular2 doesn't support the full inheritance - Issue with binding and @ViewChild
But it still unclear why the solution with @HostListener didn't work in first place
Specifically if you have a property decorator on your derived component it won't 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 transformed to javascript like:
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 way will define two metadata entries - on BaseClass
and on DerivedOne
Finally when angular2 will extract metadata from DerivedOne
class it will only use its own metadata:
To get all the metadata you can write custom decorator like:
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's a working demo
2) If you done as follows:
abstract class BaseClass
constructor(){
window.onbeforeunload = function(){
console.log('bla');
};
}
}
then it will be invoked only one time because you're overriding window.onbeforeunload
handler everytime
You should use the following instead:
abstract class BaseClass {
constructor(){
window.addEventListener('beforeunload', () =>{
console.log(`bla-bla from${this.constructor.name}`);
})
}
}
Plunker Example
3) Finally if you have base class as shown below:
abstract class BaseClass {
beforeUnloadHander() {
console.log(`bla-bla from${this.constructor.name}`);
}
}
then you have to use the correct syntax (you're missing brackets) in decorator property:
host: {'(window:beforeunload)': 'beforeUnloadHander()' }
Plunker Example
Hope it helps you!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With