简体   繁体   English

是否可以在服务中使用 HostListener? 或者如何在 Angular 服务中使用 DOM 事件?

[英]Is it possible to use HostListener in a Service? Or how to use DOM events in an Angular service?

I want to create a service which detects all keyboard input, translates the key strokes into actions based on a configurable mapping, and exposes observables which various elements can bind to to react to specific key presses.我想创建一个服务来检测所有键盘输入,将击键转换为基于可配置映射的操作,并公开各种元素可以绑定到的可观察对象以对特定按键做出反应。

The following is a simplification of my code so far, it worked when HostListener was in a component, but now I've moved it into a service it never fires even though it is definitely initialised.以下是迄今为止我的代码的简化,当 HostListener 在组件中时它可以工作,但现在我已经将它移动到它永远不会触发的服务中,即使它确实已初始化。 Is it not possible to detect input like this in a service?是否无法在服务中检测到这样的输入?

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

import { Subject } from 'rxjs/Subject';

@Injectable()
export class InputService {

    @HostListener('window:keydown', ['$event'])
    keyboardInput(event: any) {
        console.log(event);
    }
}

Seems like its not possible to use HostListener in a service.似乎不可能在服务中使用HostListener

UPDATE更新

like Stanislasdrg Reinstate Monica wrote, there's a more elegant and more angular way using the renderer ..就像Stanislasdrg Reinstate Monica写的那样,使用渲染器有一种更优雅、更有棱角的方式..

@Injectable()
export class MyMouseService implements OnDestroy {
  private _destroy$ = new Subject();

  public onClick$: Observable<Event>;

  constructor(private rendererFactory2: RendererFactory2) {
    const renderer = this.rendererFactory2.createRenderer(null, null);

    this.createOnClickObservable(renderer);
  }

  ngOnDestroy() {
    this._destroy$.next();
    this._destroy$.complete();
  }

  private createOnClickObservable(renderer: Renderer2) {
    let removeClickEventListener: () => void;
    const createClickEventListener = (
      handler: (e: Event) => boolean | void
    ) => {
      removeClickEventListener = renderer.listen("document", "click", handler);
    };

    this.onClick$ = fromEventPattern<Event>(createClickEventListener, () =>
      removeClickEventListener()
    ).pipe(takeUntil(this._destroy$));
  }
}

live-demo: https://stackblitz.com/edit/angular-so4?file=src%2Fapp%2Fmy-mouse.service.ts现场演示: https ://stackblitz.com/edit/angular-so4?file=src%2Fapp%2Fmy-mouse.service.ts

OLD老的

You could use the old way window.addEventListener like @yurzui pointed out already.您可以像 @yurzui 指出的那样使用旧的方式window.addEventListener

https://plnkr.co/edit/tc53cvQDfLHhaR68ilKr?p=preview https://plnkr.co/edit/tc53cvQDfLHhaR68ilKr?p=preview

import {Component, NgModule, HostListener, Injectable} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'

@Injectable()
export class MyService {

  constructor() {
    window.addEventListener('keydown', (event) => {
      console.dir(event);
    });
  }

}

@Component({
  selector: 'my-app',
  template: `
    <div>
      <h2>Hello {{name}}</h2>
    </div>
  `,
})
export class App {

  constructor(private _srvc: MyService) {
    this.name = 'Angular2'
  }
}

@NgModule({
  imports: [ BrowserModule ],
  declarations: [ App ],
  providers: [MyService],
  bootstrap: [ App ]
})
export class AppModule {}

Caution:警告:
Lookout for memory leaks as the listeners don't automatically stop listening.注意内存泄漏,因为侦听器不会自动停止侦听。

Original answer:原答案:
There is an other way of doing so, by using RendererFactory2 and Renderer2 .还有另一种方法,使用RendererFactory2Renderer2 I am using such a service to monitor idleness and logout the user accordingly.我正在使用这样的服务来监控空闲状态并相应地注销用户。 Here is part of the code :这是代码的一部分:

@Injectable()
export class IdleService {

  renderer: Renderer2;
  lastInteraction: Date = new Date();
  definedInactivityPeriod = 10000;

  constructor(
    private rendererFactory2: RendererFactory2,
    private auth: AuthService,
    private router: Router
  ) {
    this.renderer = this.rendererFactory2.createRenderer(null, null);
    this.renderer.listen('document', 'mousemove', (evt) => {
      console.log('mousemove');
      this.lastInteraction = new Date();
    });
    // Subscribing here for demo only
    this.idlePoll().subscribe();
  }

  idlePoll() {
    return interval(1000)
      .pipe(
        tap(() => console.log('here', new Date().getTime() - this.lastInteraction.getTime())),
        takeWhile(() => {
          if ((new Date().getTime() - this.lastInteraction.getTime()) > this.definedInactivityPeriod) {
            this.auth.logout();                        
          }
          return (new Date().getTime() - this.lastInteraction.getTime()) < this.definedInactivityPeriod;
        })
      );
  }

}

By passing null to renderer factory this.rendererFactory2.createRenderer(null, null) you get a hold of the default DOMrenderer and can therefore listen to window events.通过将 null 传递给渲染器工厂this.rendererFactory2.createRenderer(null, null)您可以获得默认的 DOMrenderer,因此可以监听窗口事件。

Another way that could be used is to use the fromEvent function provided by rxjs .另一种可以使用的方法是使用 rxjs 提供的fromEvent rxjs

@Injectable()
export class InputService implements OnDestroy {
  // Watch for events on the window (or any other element).
  keyboardInput = fromEvent(window, 'keydown').pipe(
    tap(i => console.log('here', i))
  )
  // Hold a reference to the subscribption.
  keyboardSub?: Subscription;

  constructor() {
    // Subscribe to the property or use the async pipe.
    // Remember to unsubscribe when you are done if you don't use the async pipe.
    this.keyboardSub = this.keyboardInput.subscribe();
  }

  ngOnDestroy() {
    // Destroy the subscription.
    this.keyboardSub?.unsubscribe();
  }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM