Angular 2:使用服务广播事件

29

我正在尝试让一个组件中的按钮点击使另一个组件中的元素获得焦点。(说实话,我不明白为什么这必须如此复杂,但是我无法实现任何更简单的方法,而这种方法实际上有效。)

我正在使用一个服务。它不需要传递任何数据,除了点击发生的事实之外。我不确定监听组件应该如何响应事件。

app.component:

跳转到主要内容

import { Component } from '@angular/core';
import { SkipToContentService } from './services/skip-to-content.service';


export class AppComponent {
    constructor(
        private skipToContent: SkipToContentService
    ) {}
    }

    skipLink() {
        this.skipToContent.setClicked();
    }

}

登录组件:

<input type="text" name="username" />


import { Component, OnInit } from '@angular/core';
import { SkipToContentService } from '../../../services/skip-to-content.service';

export class BaseLoginComponent implements OnInit {

    constructor(
        private skipToContent: SkipToContentService
        ) {
    }

    ngOnInit() {
        this.skipToContent.skipClicked.subscribe(
            console.log("!")
            // should put focus() on input
        );

    }
}

跳转至内容服务:

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

@Injectable()
export class SkipToContentService {

    skipClicked: EventEmitter<boolean> = new EventEmitter();


    constructor() {
    }

    setClicked() {
        console.log('clicked');
        this.skipClicked.emit();
    };
}

我有点迷惑,不知道登录页面如何“接收”skipClicked事件。


2
你不应该在你的服务中使用 EventEmitter,请参考:https://dev59.com/lVoV5IYBdhLWcg3wkvh1。而是应该使用共享服务来广播事件:https://angular.io/guide/component-interaction#parent-and-children-communicate-via-a-service。 - eko
2个回答

27

首先,使用 BehaviorSubject 而不是 EventEmitter。将 skipClicked 的声明更改为以下内容:

skipClicked: BehaviorSubject<boolean> = new BehaviorSubject(false);

然后,您需要使用 next() 方法广播新值,如下所示:

this.skipClicked.next (true);

同时,请将您的订阅更改为:

 this.skipToContent.skipClicked.subscribe( value => {
     if (value === true) {
         console.log("!"); 
         // should put focus() on input 
     }
 });

几个问题:1] 在SafeSubscriber.schedulerFn [as _next] (event_emitter.ts:115)中出现了"generatorOrNext不是一个函数"的错误。 2] 事件在页面加载时自动触发。也就是说,我首先得到的是'!'。 3] 据我所知,next()已经被弃用,推荐使用emit()。 - DaveC426913
EventEmmiter,只在组件中使用。 - Ricardo D. Quiroga
1
EventEmitter应该只在实际组件中使用,Angular组件集成指南建议使用共享服务,如另一个答案中所述,因此对回答进行了下投票,因为需要更新或误导他人。 - Regfor

17

EventEmitter仅应在带有@Output指令的实际组件中使用。其他任何用法未来可能无法工作。

对于子父通信,最好使用Subject或BehaviorSubject。这是来自Angular指南的示例。

https://angular.io/guide/component-interaction

@Injectable()
export class MissionService {

  // Observable string sources
  private missionAnnouncedSource = new Subject<string>();
  private missionConfirmedSource = new Subject<string>();

  // Observable string streams
  missionAnnounced$ = this.missionAnnouncedSource.asObservable();
  missionConfirmed$ = this.missionConfirmedSource.asObservable();

  // Service message commands
  announceMission(mission: string) {
    this.missionAnnouncedSource.next(mission);
  }

  confirmMission(astronaut: string) {
    this.missionConfirmedSource.next(astronaut);
  }
}

提示:

如果您有一个事件仅表示发生或完成了某事,而没有任何实际数据与之关联,您可以使用 Subject<void>()。这使得 next() 的签名更清晰,您不需要为此提供虚拟值。

例如:

windowClosed = new Subject<void>();

windowClosed.next()将会触发该事件。


是的,当我只需要服务做一件事时,我也很难。我该怎么命名它呢?是为组件命名,还是为动作命名,或者为动作类型命名。我想你只需要看看哪种方式适合你。 - Simon_Weaver
另一个需要记住的关键点是,您可以组合用于内部数据传输的服务。它们可以相互注入,并且如果您有具有5个不相关子项的复杂控制,则可以注入5个服务。我希望它们有比“服务”更好的名称。 - Simon_Weaver

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接