kittencup / angular2-ama-cn

angular2 随便问
692 stars 101 forks source link

angular2 不同路由之前怎么进行数据传递或者说数据同步? #184

Open mlyknown opened 8 years ago

Imporial commented 8 years ago

服务或者根组件变量吧

hstarorg commented 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 });
  }
}

这个代码还没有做取消注册,仅供参考。

xufei commented 8 years ago

楼上给出的是一个类似事件中心的方案,这样的话,所有的这种传递都是经过全局去了,应用较大的时候事件名需要控制,避免冲突。

也可以这样:根据要传递内容的所属模块:全局或者局部放一些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,互相没有影响,并且也不会冲突

hstarorg commented 8 years ago
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 commented 8 years ago

我觉得所有通信都用一套,比较好控制。当然,命名冲突是一个问题,靠约定或者增加强制检查可以处理。

bigggge commented 7 years ago

@hstarorg 请问服务使用的时候是不是必须写到constructor里,类似

constructor(private momentsService: MomentsService,
                private toastService: ToastService,
                private router: Router,
                private eventBus: EventBus) {
    }

这样每个组件都要这么写明有点麻烦,有没有好办法?能不能写成静态方法?

hstarorg commented 7 years ago

@bigggge 可以写成静态方法,但是不太符合Angular的Style~

bigggge commented 7 years ago

@hstarorg 这个EventBus是怎么用的啊?能否给个demo,谢谢,比如我要从一个组件传一个值到另一个组件,传递和接受是怎么写的?

hstarorg commented 7 years ago

注入EventBus服务。

然后接收方:

this.eventBus.on('xxxx', (e, data) => {
});

发送方:

this.eventBus.emit('xxx', data);
macaliu commented 7 years ago

F5刷新怎么破

hstarorg commented 7 years ago

localStorage, sessionStorage

HALOH commented 7 years ago

@hstarorg 你好,请问这个eventBus在两个路由之间可以通信吗?我试了下发现不行,是直接把你这个eventBus服务拿来用的,发送方和接受方也是按你上面介绍写的,发送和接受方那个eventName要统一吗?能不能再给个小demo看下!谢谢~

hstarorg commented 7 years ago

@HALOH 这个是全局都可以通信的。实现方式就是发布/订阅。

先使用on方法,注册一个事件(指定事件名),然后使用emit,触发事件(使用之前指定的事件名即可)。

hstarorg commented 7 years ago

@HALOH 需要注意一点,EventBus必须要在根模块中注入,写在根模块的providers中,否则就不是单例了。

HALOH commented 7 years ago

@hstarorg 哦哦,我开始没在根模块注入!我再试试!谢谢~

HALOH commented 7 years ago

@hstarorg 你好,我把EventBus按照你说的在根模块中注入了,也写在根模块的providers中,然后两个路由组件也分别注入后,把路由一组件作为发送方执行:this.eventBus.emit('msg', "information");路由二组件作为接受方执行:this.eventBus.on('msg', (e, data) => { console.log(data); }) 但是每次从路由一到路由二并没有执行这个console,而是在从路由二到路由一的时候执行了,但是输出为undifined;请问这是什么原因,是我使用方法不对吗?

hstarorg commented 7 years ago

@HALOH 因为是事件监听,要保存on要先于emit执行。而且还需要注意,因为是事件订阅,还需要手动销毁。在 ngOnDestroyunsubscribe。 我大概知道你的需求了,是在两个路由之间传递数据,因为两个路由不是同时存在的,这种靠事件传递也不可取。如果是路由跳转传递数据,可以用路由参数。

HALOH commented 7 years ago

@hstarorg 嗯嗯,需求确实是在两个路由之间传递参数,因为传的参数比较多,所以不想通过路由参数传递。现在暂时就在路由里传参数吧,等再找到好的方法再改!非常感谢~

bigggge commented 7 years ago

@HALOH 目前我存在了service里,不知道是不是个好办法

hstarorg commented 7 years ago

@HALOH 事件中心,多用于全局事件的通知,比如登录成功,退出等等。我是尽量避免在路由中传递大量数据。另外还可以参考 vuex 实现一个全局的数据存储中心(这个类似于存储到service中)

HALOH commented 7 years ago

@bigggge 嗯嗯,我回头试试!谢谢~

HALOH commented 7 years ago

@hstarorg 嗯嗯~好的~谢谢!

HALOH commented 7 years ago

@bigggge 顺便请教一下,一个路由组件把数据存在service里,然后跳到其他路由组件时是可以获取的,但是刷新后数据会丢失,这种情况如果不想用localStorage,、sessionStorage和cookie,有其他的方法保存吗?就是还是保存在service但是刷新不还原!

bigggge commented 7 years ago

@HALOH 我也不太清楚,我目前也有这个问题

HALOH commented 7 years ago

@bigggge 嗯嗯,后面如果找到解决方案分享下~☺

hstarorg commented 7 years ago

@HALOH @bigggge 刷新和初次进入是一个比较常见的场景,这个时候如果不管是存储到sessionStorage或者是Service,都需要先初始化一次。(一般是在路由拦截器做处理,首次激活路由,初始化数据)

drawcall commented 7 years ago

看看这个? rxjs + eventbus https://github.com/a-jie/RxEmitter

testsla commented 6 years ago

https://stackblitz.com/edit/angular-zcj-store