angular - Generer Observable Array ved å bruke den originale Arrayen selv


Jeg har en tjeneste (MessageService) som sender ut noen meldinger basert på andre hendelser i appen.
Så har jeg en komponent MessagesComponent og jeg ønsker å vise alle meldingene som ble sendt ut av min tjeneste. Min tilnærming er å bruke en Observable<Message[]> for å lagre og reaktivt oppdatere UI hver gang en ny melding kommer. Her er min fungerende løsning

@Component({
  selector: 'app-messages',
  template: `
    <h2>Received messages</h2>
    <ol>
      <li class="message" *ngFor="let message of messages$ | async">
        {{ message }}
      </li>
    </ol>
  `,
  styles: [``]
})
export class MessagesComponent implements OnInit, OnDestroy {
  destroy$ = new Subject<void>();
  messages$ = new BehaviorSubject<Message[]>([]);
  
  constructor(private messagesService: MessagesService) {}

  ngOnInit(): void {
    this.messagesService.message$
      .pipe(
        takeUntil(this.destroy$),
        switchMap((message: Message) =>
          this.messages$.pipe(
            take(1),
            map((all: Message[]) => {
              return [...all, message];
            })
          )
        )
      )
      .subscribe((messages: Message[]) => {
        this.messages$.next(messages);
      });
  }

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

Akkurat nå starter jeg med en tom BehaviorSubject-array for å lagre alle meldingene. Deretter, i ngOnInit-hooken, abonnerer jeg på MessagesService's message$, og etter en ny utsendelse kombinerer jeg det jeg har i BehaviorSubject med den nye meldingen, slik at jeg sender ut en ny array med alle elementene. Løsningen min ser ut til å fungere, men jeg vil gjerne unngå abonnementet i NgOnInit (hvis mulig). Jeg tenkte på noe som dette

messages$: Observable<Message[]> = this.messagesService.message$.pipe(
        switchMap((message: Message) =>
          this.messages$.pipe(
            startWith([]),
            take(1),
            map((all: Message[]) => [...all, message])
          )
        )
      );

Jeg prøver å bruke selve arrayet som en del av sin egen initialisering ved å bruke SwitchMap + StartWith, men det ser ikke ut til å fungere, så jeg leter etter noen andre ideer :)

Å, meldingstypen er bare et enkelt grensesnitt med noen egenskaper.

export interface Message {
  id: string;
  value: string;
}

Svar #1

Du kan bruke scan-operatøren for å akkumulere tilstand. Den vil ha tilgang til forrige utslipp, og du kan bygge videre på det.

export class AppComponent {
  
  messages$ = this.messagesService.message$.pipe(
    scan((all, message) => all.concat(message), [])
  );

  constructor(private messagesService: MessagesService) { }

}

Når scan mottar en utslipp fra mesageService.message$, vil det kjøre funksjonen

(all, message) => all.concat(message)

og emittere resultatet. message er verdien mottatt fra tjenesten. Første gang har all en verdi av vår opprinnelige startverdi ([]). Etter det er det den tidligere emitterte verdien;

Merk at komponentkoden er sterkt forenklet; du trenger ikke: Subject, BehaviorSubject, abonnement, ngOnInit, ngOnDestroy, takeUntil, switchMap, eller take :-)

Her er en liten StackBlitz-demo.