3

I have a service (MessageService) that emits some messages based on some other app events.
Then, I have a component MessagesComponent and I'd like to render all the messages that were emitted by my Service. My approach is to use an Observable<Message[]> to store and reactively update the UI every time a new Message arrives. Here's my working solution

@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();
  }
}

Right now, I start with an empty BehaviorSubject array to store all the messages. Then, in the ngOnInit hook, I subscribe to the MessagesService's message$ and after a new emission I am combining what I have in the BehaviorSubject with the new message thus emitting a new array with all the items. My solution seems to work, but I'd like to avoid the subscription in NgOnit (if possible). I was thinking in something like this

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

I'm trying to use the array itself as part of their own initialization using SwitchMap + StartWith however it doesn't seems to work, so I'm looking for some other ideas :)

Oh, the Message type is just a simple interface with some properties.

export interface Message {
  id: string;
  value: string;
}
4
  • can you explain in short about your use case ? Commented Mar 5, 2022 at 11:03
  • First paragraph :) Commented Mar 5, 2022 at 11:05
  • Ok, I think you do not need messages$ of compoennt.ts , You can use service message$ directly in component template for your ngFor loop. Commented Mar 5, 2022 at 11:08
  • No, not really. Service's message$ emits a single message (it's not an array). That's why I'm using the BehaviourSubject in the component, to store all the service emissions as an array allowing me to iterate the array in the template. Commented Mar 5, 2022 at 11:15

1 Answer 1

1

You can use the scan operator to accumulate state. It will have access to the previous emission and you can build upon that.

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

  constructor(private messagesService: MessagesService) { }

}

When scan receives an emission from mesageService.message$, it will run the function

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

and emit the result. message is the value received from the service. The first time, all has a value of our initial seed value ([]). After that, it is the previously emitted value;

Notice the component code is greatly simplified; you don't need: Subject, BehaviorSubject, subscription, ngOnInit, ngOnDestroy, takeUntil, switchMap, nor take :-)

Here's a little StackBlitz demo.

Sign up to request clarification or add additional context in comments.

1 Comment

This is great, exactly what I was looking for!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.