Angular2 @HostListener不适用于派生组件

Dmi*_*kov 6 typescript angular2-template angular

我有一个基于Angular2的客户端应用程序.我有一个基类:

abstract class BaseClass {
    @HostListener('window:beforeunload') beforeUnloadHandler() {
        console.log('bla');
    }
}
Run Code Online (Sandbox Code Playgroud)

和两个非常相似的派生类:

@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 {
}
Run Code Online (Sandbox Code Playgroud)

问题在于,例如,DerivedOne beforeUnloadHandler工作正常,而在DerivedTwo其中根本不接收电话.

我知道很难找到为什么只是查看上面的信息,但也许有人可能会怀疑可能导致这种奇怪行为的原因.

还有一些说明:

如果我使用以下内容:

abstract class BaseClass
    constructor(){
        window.onbeforeunload = function(){
            console.log('bla');
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

一切正常,但我仍然想找到一个基于Angular2的解决方案;

如果我写

abstract class BaseClass {
    beforeUnloadHandler() {
        console.log('bla');
    }
}
Run Code Online (Sandbox Code Playgroud)

并在 derived-two.component.html

<div (window.beforeunload)="beforeUnloadHandler()"></div>
Run Code Online (Sandbox Code Playgroud)

一切都很好,但它看起来像一个丑陋的黑客;

如果我写的话

abstract class BaseClass {
    beforeUnloadHandler() {
        console.log('bla');
    }
}
Run Code Online (Sandbox Code Playgroud)

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

它不会起作用.

最后,如果我使用@HostListenerin DerivedTwo和in DerivedOne,它可以工作,但我想避免使用重复的代码.

希望上面的信息足以与之合作(至少要有一些猜测).

yur*_*zui 10

更新2.3.0

您现在可以利用组件的对象继承.

您可以在此提交中看到更多详细信息https://github.com/angular/angular/commit/f5c8e0989d85bc064f689fc3595207dfb29413f4

旧版

1)如果你有一个班级:

abstract class BaseClass {
  @HostListener('window:beforeunload') beforeUnloadHander() {
    console.log('bla');
  }
}
Run Code Online (Sandbox Code Playgroud)

然后它会工作

Plunker示例(在编辑器和监视控制台的某处放置空格)

但要小心,因为Angular2不支持完全继承 - 绑定问题和@ViewChild

但仍然不清楚为什么@HostListener的解决方案首先不起作用

特别是如果您在派生组件上有属性装饰器,它将无法工作.例如,假设我们有以下代码:

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;
}
Run Code Online (Sandbox Code Playgroud)

Plunker

它将被转换为javascript,如:

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));
Run Code Online (Sandbox Code Playgroud)

我们对以下几行感兴趣:

 __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);
Run Code Online (Sandbox Code Playgroud)

HostListener并且Input是物业装饰者(propMetadata关键).这种方式将定义两个元数据条目 - on BaseClass和onDerivedOne 在此输入图像描述 在此输入图像描述

最后,当angular2从DerivedOne类中提取元数据时,它将只使用自己的元数据:

在此输入图像描述

要获取所有元数据,您可以编写自定义装饰器,如:

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 {
Run Code Online (Sandbox Code Playgroud)

这是一个有效的演示

2)如果你做了如下:

abstract class BaseClass
  constructor(){
    window.onbeforeunload = function(){
      console.log('bla');
    };
  }
}
Run Code Online (Sandbox Code Playgroud)

然后它将只被调用一次,因为你window.onbeforeunload每次都要覆盖处理程序你应该使用以下代码:

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

Plunker示例

3)最后,如果您有基类,如下所示:

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

那么你必须在decorator属性中使用正确的语法(你缺少括号):

host: {'(window:beforeunload)': 'beforeUnloadHander()' }
Run Code Online (Sandbox Code Playgroud)

Plunker示例

希望它能帮到你!