Search code examples
angulartypescriptobservablebehaviorsubject

push string to observable<chatmessages[ ]>


I'm trying to create a chat-app and would like the message that the user sends, to be displayed in the chatroom (called feed in my project), just like all the other already existing messages are shown (i have a getMessages() for that)

how can I push the message that the user types, to my chatMessage[] ?

here is my message.Service.ts

@Injectable({
  providedIn: 'root'
})
export class MessageService {
  user: User;
  userName: Observable<string>;

  chatMessage: ChatMessage;
  chatMessages$: Observable<ChatMessage[]>;

    constructor(
    private http: HttpClient,
    private accountService: AccountService) {
      this.user = this.accountService.userValue;
     }
 

sendMessage(msg: string){
  const timestamp = this.getTimeStamp()

this.chatMessages$ = this.getMessages();
this.chatMessages$.push({ //having trouble here
  msg_content: msg,       //Property 'push' does not exist on type 'Observable<ChatMessage[]>'.
  timeSent: timestamp,
  userName: this.userName,
  email: this.user.email });

var formdata = new FormData();
formdata.append("sender_email", this.user.email);
formdata.append("sender_key", this.user.api_key);
formdata.append("msg_content", msg);
this.http.post("http://11.123.456.78:5000/messages/send_message", formdata )
  .subscribe(response => console.log(response), error => console.log('oops', error));
}

getMessages(){
  let params = new HttpParams().set("sender_email", "this.user.email").set("sender_key", "someapikey"); //Create new HttpParams 
  var url = "http://11.123.456.78:5000/messages/get_all_messages";
   return this.chatMessages$ = this.http.get<ChatMessage[]>(url, { params: params});
 }
}

my feed.component.ts

export class FeedComponent implements OnInit {
  chatMessages$: Observable<ChatMessage[]>;
  messages;
  messageObjects: string[] = [];

  constructor(private chat: MessageService) { 
  }
 
  ngOnInit() {
  this.messages = this.chat.getMessages().subscribe((allmessagesdata => { this.messages = allmessagesdata
  for (let messageObject of this.messages){
    this.messageObjects.push(messageObject.msg_content)
  };
 }));
 this.chatMessages$ = this.chat.getMessages();
 
  }
  
  ngOnchanges(){
    this.chat.getMessages().subscribe((allmessagesdata => { this.messages = allmessagesdata,  console.log(this.messages)}))
    
  }
}

my feed.html

<div *ngFor="let message of ( chatMessages$ | async )" >
<app-message [chatMessage]="message"> </app-message>    
</div>

my chat-message.model.ts

export class ChatMessage {
    $id?: string;
    email?: string;
    userName?: string;
    msg_content?: string;
    timeSent?: Date = new Date();
}

here's what I already tried:

I tried setting private subject = new Subject<string>(); and then saying this.subject.next(msg) in my sendMessage() function and then I created

getMessage():Observable<string> {
  return this.subject.asObservable();
}

and subscribed to it in my feed, but this only allows for one value of message.

My knowledge of observables and subjects is limited and I can't seem to fully grasp this concept yet, but I know I had to use them for this purpose, so any help would be really appreciated


Solution

  • You can't just push something to an observable as it's a stream. What you described can be done with many different approaches. For example you can create BehaviorSubject of ChatMessage[] which will store "recent" messages and then chatMessages$ should be a combination of backend messages and recent ones.

    My proposition (the first one which came to my mind, did some conception testing in the playground here: https://stackblitz.com/edit/rxjs-evjqhb)

    @Injectable({
      providedIn: 'root'
    })
    export class MessageService implements OnInit, OnDestroy{
      user: User;
      userName: Observable<string>;
    
      private _sendMessage = new Subject<ChatMessage>();
      private _destroy = new Subject<ChatMessage>();
      private _recentMessages = new BehaviorSubject<ChatMessage[]>([]);
      
      
      chatMessage: ChatMessage;
      existedChatMessages$: Observable<ChatMessage[]>;
      recentChatMessages$: Observable<ChatMessage[]>;
      chatMessages$: Observable<ChatMessage[]>;
    
        constructor(
        private http: HttpClient,
        private accountService: AccountService) {
          this.user = this.accountService.userValue;
         }
     
    
    sendMessage(msg: string){
      const timestamp = this.getTimeStamp()
    
    
    const message = {
      msg_content: msg,
      timeSent: timestamp,
      userName: this.userName,
      email: this.user.email };
    
      
      this._sendMessage.next(message);
    
    // you can move this to a tap() operator in the _sendMessage subject
    var formdata = new FormData();
    formdata.append("sender_email", this.user.email);
    formdata.append("sender_key", this.user.api_key);
    formdata.append("msg_content", msg);
    this.http.post("http://11.123.456.78:5000/messages/send_message", formdata )
      .subscribe(response => console.log(response), error => console.log('oops', error));
    }
    
    getMessages(){
      let params = new HttpParams().set("sender_email", "this.user.email").set("sender_key", "someapikey"); //Create new HttpParams 
      var url = "http://11.123.456.78:5000/messages/get_all_messages";
       return this.chatMessages$ = this.http.get<ChatMessage[]>(url, { params: params});
     }
     
     ngOnInit(){
        chatMessages$ = this.existedChatMessages$.pipe(
            switchMap(existed=> 
                this.recentChatMessages$].pipe(
                    map(recent)=>[...(existed||[]), ...recent]
                )
            )
        ); // combine streams of backend & recent messages
        
        this.existedChatMessages$ = this.getMessages();
        this.recentChatMessages$ = this._recentMessages.asObservable();
        this._recentMessages.pipe(
            switchMap(recentMessages => this._sendMessage.pipe(
                    map(message=> [...recentMessages, message]))
                )
            ),
            tap(x=> this._recentMessages.next(x)),
            takeUntil(this._destroy)
        ).subscribe(); // creating subscription for sendMessage subject to update recentMessages collection
        
        
     }
     
     ngOnDestroy(){
        this._destroy.next();
        this._destroy.complete();
     }
     
    }