Angular AoT Custom Decorator在静态解析符号值时遇到错误

Bru*_*oLM 8 typescript angular

我创建了一个装饰器来帮助我处理桌面/移动事件

import { HostListener } from '@angular/core';

type MobileAwareEventName =
  | 'clickstart'
  | 'clickmove'
  | 'clickend'
  | 'document:clickstart'
  | 'document:clickmove'
  | 'document:clickend'
  | 'window:clickstart'
  | 'window:clickmove'
  | 'window:clickend';

export const normalizeEventName = (eventName: string) => {
  return typeof document.ontouchstart !== 'undefined'
    ? eventName
        .replace('clickstart', 'touchstart')
        .replace('clickmove', 'touchmove')
        .replace('clickend', 'touchend')
    : eventName
        .replace('clickstart', 'mousedown')
        .replace('clickmove', 'mousemove')
        .replace('clickend', 'mouseup');
};

export const MobileAwareHostListener = (
  eventName: MobileAwareEventName,
  args?: string[],
) => {
  return HostListener(normalizeEventName(eventName), args);
};
Run Code Online (Sandbox Code Playgroud)

问题是当我尝试编译时--prod,我得到以下错误

typescript error
Error encountered resolving symbol values statically. Function calls are not supported. Consider replacing
the function or lambda with a reference to an exported function (position 26:40 in the original .ts file),
resolving symbol MobileAwareHostListener in
.../event-listener.decorator.ts, resolving symbol HomePage in
.../home.ts

Error: The Angular AoT build failed. See the issues above
Run Code Online (Sandbox Code Playgroud)

怎么了?我该如何解决这个问题?

Est*_*ask 9

这意味着错误说明了什么.您正在执行此操作的位置不支持函数调用.不支持 Angular内置装饰器行为的扩展.

AOT编译(由--prod选项触发)允许静态分析现有代码并用评估的预期结果替换一些部分.这些地方的动态行为意味着AOT不能用于应用程序,这是应用程序的主要缺点.

如果您需要自定义行为,则HostListener不应使用.由于它基本上在元素上设置了一个监听器,因此应该使用渲染器提供程序手动完成,这是优于DOM的Angular抽象.

这可以通过自定义装饰器来解决:

interface IMobileAwareDirective {
  injector: Injector;
  ngOnInit?: Function;
  ngOnDestroy?: Function;
}

export function MobileAwareListener(eventName) {
  return (classProto: IMobileAwareDirective, prop, decorator) => {
    if (!classProto['_maPatched']) {
      classProto['_maPatched'] = true;
      classProto['_maEventsMap'] = [...(classProto['_maEventsMap'] || [])];

      const ngOnInitUnpatched = classProto.ngOnInit;
      classProto.ngOnInit = function(this: IMobileAwareDirective) {
        const renderer2 = this.injector.get(Renderer2);
        const elementRef = this.injector.get(ElementRef);
        const eventNameRegex = /^(?:(window|document|body):|)(.+)/;

        for (const { eventName, listener } of classProto['_maEventsMap']) {
          // parse targets
          const [, eventTarget, eventTargetedName] = eventName.match(eventNameRegex);
          const unlisten = renderer2.listen(
            eventTarget || elementRef.nativeElement,
            eventTargetedName,
            listener.bind(this)
          );
          // save unlisten callbacks for ngOnDestroy
          // ...
        }

        if (ngOnInitUnpatched)
          return ngOnInitUnpatched.call(this);
      }
      // patch classProto.ngOnDestroy if it exists to remove a listener
      // ...
    }

    // eventName can be tampered here or later in patched ngOnInit
    classProto['_maEventsMap'].push({ eventName, listener:  classProto[prop] });
  }
}
Run Code Online (Sandbox Code Playgroud)

使用如下:

export class FooComponent {
  constructor(public injector: Injector) {}

  @MobileAwareListener('clickstart')
  bar(e) {
    console.log('bar', e);
  }

  @MobileAwareListener('body:clickstart')
  baz(e) {
    console.log('baz', e);
  }  
}
Run Code Online (Sandbox Code Playgroud)

IMobileAwareDirective界面在这里起着重要作用.它强制一个类拥有injector属性,这种方式可以访问它的注入器和自己的依赖项(包括ElementRef,这是本地的,显然在根注入器上不可用).此约定是装饰器与类实例依赖项交互的首选方法.class ... implements IMobileAwareDirective也可以添加表达力.

MobileAwareListener不同之处在于HostListener后者接受参数名称列表(包括魔法$event),而前者只接受事件对象并绑定到类实例.这可以在需要时更改.

这是一个演示.

此处还有几个问题需要解决.应删除事件侦听器ngOnDestroy.类继承可能存在潜在问题,需要另外测试.