可观察的更新用户界面仅一次

问题描述

我对Angular(10)并不陌生,并尝试掌握Observables的概念。我有一个服务和一个组件,这些服务用参与者填充了一个数组,并且该组件应该显示参与者

服务

export class MercureService {

  private _participants = new BehaviorSubject<ParticipantDTO[]>([]);
  private participantList: Array<ParticipantDTO> = [];

  constructor() {
  }

  get participants$(): Observable<Array<ParticipantDTO>> {
    return this._participants.asObservable();
  }

  admin_topic(topic: AdminTopic): void {

    const url = new URL(environment.mercure);
    url.searchParams.append('topic',`${topic.sessionUuid}_${topic.userUuid}`);

    const eventSource = new EventSource(url.toString());
    eventSource.onmessage = event => {
      const json = JSON.parse(event.data);
      console.log('NEW EVENT');
      // ...
      if (json.event === BackendEvents.NEW_PARTICIPANT) {
        this.participantList.push({Voter: json.data.Voter,VoterUuid: json.data.VoterUuid,Vote: '0'});
        this._participants.next(this.participantList);
      }
    };
  }

Component.ts

export class StartSessionComponent implements OnInit,OnDestroy {
  // ...
  unsubscribe$: Subject<void> = new Subject<void>();
  participantList: ParticipantDTO[];

  constructor(
    // ...
    public mercure: MercureService
  ) {}

  ngOnInit(): void {
    this.mercure.participants$
      .pipe(takeuntil(this.unsubscribe$))
      .subscribe((data) => {
        this.participantList = data;
      });

    this.mercure.admin_topic({sessionUuid: '123',userUuid: '456'});
  }

  ngOnDestroy(): void {
    this.unsubscribe$.next();
    this.unsubscribe$.complete();
  }

Component.html

...
  <div *ngFor="let participant of this.mercure.participants$ | async">
    <p>{{participant.Voter}} - Vote ({{participant.Vote}})</p>
  </div>
...

console说,

所以我没有发送消息,而是被EventSource接收。
NEW EVENT

,UI会更新(添加新的<p>WHATEVER NAME - Vote XXX</p>)。但是,当我从服务器发送第二条消息时,我得到了

NEW EVENT
再次

,但用户界面未更新。我怀疑我在Observable上做错了,请给我一些帮助吗?

解决方法

这是一种预期的行为,因为this.participantList指的是主题已经存储的值(因为引用未更改),您可能希望每次都散布数组以创建一个新数组您要更新其值:

this._participants.next(....this.participantList);
,

问题在于EventSource事件是在Angular外部发出的,因此eventSource.onmessage内部发生的任何事情都无法正确更新UI。这就是为什么您需要将onmessage内发生的一切包装在NgZone的帮助下在Angular中运行。

查看示例:

  constructor(
    private zone: NgZone
  ) { }

  admin_topic(topic: AdminTopic): void {
    const url = new URL(environment.mercure);
    url.searchParams.append('topic',`${topic.sessionUuid}_${topic.userUuid}`);

    const eventSource = new EventSource(url.toString());
    eventSource.onmessage = event => {
      this.zone.run(() => { // This is what you need
        const json = JSON.parse(event.data);
        console.log('NEW EVENT');
        // ...
        if (json.event === BackendEvents.NEW_PARTICIPANT) {
          this.participantList.push({ voter: json.data.voter,voterUuid: json.data.voterUuid,vote: '0' });
          this._participants.next(this.participantList);
        }
      })
    };
  }

另一种解决方案是使用事件源包装器,该包装器将为您做完全相同的事情,并使您易于使用。它还将包裹在Observable中,以便您可以拥有丰富的经验。例如,请参见此文章:Event Source wrapped with NgZone and Observable