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;
}