Open mlyknown opened 8 years ago
import {EventEmitter, Injectable} from '@angular/core';
@Injectable()
export class EventBus {
private callback: EventEmitter<any>;
private subscriberObj: any;
public constructor() {
this.callback = new EventEmitter();
this.subscriberObj = {};
this.callback.subscribe((eventObj) => {
if (this.subscriberObj.hasOwnProperty(eventObj.eventName)) {
for (let fn of this.subscriberObj[eventObj.eventName]) {
fn.apply(null, [eventObj.data]);
}
}
});
}
public on(eventName: string, cb: (x: any) => void) {
if (!this.subscriberObj.hasOwnProperty(eventName)) {
this.subscriberObj[eventName] = [];
}
this.subscriberObj[eventName].push(cb);
}
public emit(eventName: string, data?: any) {
this.callback.emit({ eventName: eventName, data: data });
}
}
这个代码还没有做取消注册,仅供参考。
楼上给出的是一个类似事件中心的方案,这样的话,所有的这种传递都是经过全局去了,应用较大的时候事件名需要控制,避免冲突。
也可以这样:根据要传递内容的所属模块:全局或者局部放一些Subject,比如说:
user.ts
export const userSubject: BehaviorSubject<User> = new BehaviorSubject<User>(null)
然后需要的地方,也就是收、发双方来import这个user.ts,发的地方:
userSubject.next(currentUser)
收的地方:
userSubject.subscribe(user => this.user = user)
也可以把user.ts搞成可注入的。
这样的好处是,如果你有一些数据是全应用级别共享的,那就在app.ts里放这些subject,如果你只是模块级别共享传递,就在module-a.ts里面放对应的subject,互相没有影响,并且也不会冲突
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs/Subject';
import { Subscription } from 'rxjs/Subscription';
interface Event {
source: Subject<any>,
observable: any
}
@Injectable()
export class EventBus {
private eventMap: Map<string, Event> = new Map<string, Event>();
constructor() {
}
emit(eventName: string, data?: any): boolean {
if (this.eventMap.has(eventName)) {
this.eventMap.get(eventName).source.next(data);
return true;
};
return false;
}
on(eventName: string, handler: Function): Subscription {
let eventObservable;
if (!this.eventMap.has(eventName)) {
let eventSource = new Subject<any>();
eventObservable = eventSource.asObservable();
this.eventMap.set(eventName, { source: eventSource, observable: eventObservable });
} else {
eventObservable = this.eventMap.get(eventName).observable;
}
return eventObservable.subscribe(handler);
}
}
最新的代码已经变成这样了。
我觉得所有通信都用一套,比较好控制。当然,命名冲突是一个问题,靠约定或者增加强制检查可以处理。
@hstarorg 请问服务使用的时候是不是必须写到constructor里,类似
constructor(private momentsService: MomentsService,
private toastService: ToastService,
private router: Router,
private eventBus: EventBus) {
}
这样每个组件都要这么写明有点麻烦,有没有好办法?能不能写成静态方法?
@bigggge 可以写成静态方法,但是不太符合Angular的Style~
@hstarorg 这个EventBus是怎么用的啊?能否给个demo,谢谢,比如我要从一个组件传一个值到另一个组件,传递和接受是怎么写的?
注入EventBus服务。
然后接收方:
this.eventBus.on('xxxx', (e, data) => {
});
发送方:
this.eventBus.emit('xxx', data);
F5刷新怎么破
localStorage, sessionStorage
@hstarorg 你好,请问这个eventBus在两个路由之间可以通信吗?我试了下发现不行,是直接把你这个eventBus服务拿来用的,发送方和接受方也是按你上面介绍写的,发送和接受方那个eventName要统一吗?能不能再给个小demo看下!谢谢~
@HALOH 这个是全局都可以通信的。实现方式就是发布/订阅。
先使用on方法,注册一个事件(指定事件名),然后使用emit,触发事件(使用之前指定的事件名即可)。
@HALOH 需要注意一点,EventBus必须要在根模块中注入,写在根模块的providers中,否则就不是单例了。
@hstarorg 哦哦,我开始没在根模块注入!我再试试!谢谢~
@hstarorg 你好,我把EventBus按照你说的在根模块中注入了,也写在根模块的providers中,然后两个路由组件也分别注入后,把路由一组件作为发送方执行:this.eventBus.emit('msg', "information");路由二组件作为接受方执行:this.eventBus.on('msg', (e, data) => { console.log(data); }) 但是每次从路由一到路由二并没有执行这个console,而是在从路由二到路由一的时候执行了,但是输出为undifined;请问这是什么原因,是我使用方法不对吗?
@HALOH 因为是事件监听,要保存on要先于emit执行。而且还需要注意,因为是事件订阅,还需要手动销毁。在 ngOnDestroy
中 unsubscribe
。
我大概知道你的需求了,是在两个路由之间传递数据,因为两个路由不是同时存在的,这种靠事件传递也不可取。如果是路由跳转传递数据,可以用路由参数。
@hstarorg 嗯嗯,需求确实是在两个路由之间传递参数,因为传的参数比较多,所以不想通过路由参数传递。现在暂时就在路由里传参数吧,等再找到好的方法再改!非常感谢~
@HALOH 目前我存在了service里,不知道是不是个好办法
@HALOH 事件中心,多用于全局事件的通知,比如登录成功,退出等等。我是尽量避免在路由中传递大量数据。另外还可以参考 vuex
实现一个全局的数据存储中心(这个类似于存储到service中)
@bigggge 嗯嗯,我回头试试!谢谢~
@hstarorg 嗯嗯~好的~谢谢!
@bigggge 顺便请教一下,一个路由组件把数据存在service里,然后跳到其他路由组件时是可以获取的,但是刷新后数据会丢失,这种情况如果不想用localStorage,、sessionStorage和cookie,有其他的方法保存吗?就是还是保存在service但是刷新不还原!
@HALOH 我也不太清楚,我目前也有这个问题
@bigggge 嗯嗯,后面如果找到解决方案分享下~☺
@HALOH @bigggge 刷新和初次进入是一个比较常见的场景,这个时候如果不管是存储到sessionStorage或者是Service,都需要先初始化一次。(一般是在路由拦截器做处理,首次激活路由,初始化数据)
看看这个? rxjs + eventbus https://github.com/a-jie/RxEmitter
服务或者根组件变量吧