From 65e8558706f71fa09594eac1f27e84567f438a87 Mon Sep 17 00:00:00 2001
From: Mohammad Alayoub <mohammad.alayoub@mni.thm.de>
Date: Tue, 22 Jun 2021 18:28:08 +0000
Subject: [PATCH] merge 'update-admin-dialog' into 'staging'

---
 .../room-creator-page.component.ts            |   4 +-
 .../moderator-comment-list.component.ts       |   4 +-
 .../room-moderator-page.component.ts          |   4 +-
 .../room-participant-page.component.ts        |   4 +-
 .../TopicCloudAdminData.ts                    |  25 +-
 .../topic-cloud-administration.component.ts   | 196 +++++-----
 .../comment-answer.component.ts               |   4 +-
 .../comment-list/comment-list.component.ts    | 145 +++-----
 .../shared/comment/comment.component.ts       |   9 +-
 .../shared/header/header.component.html       |   9 +
 .../question-wall/question-wall.component.ts  |   4 +-
 .../shared/room-page/room-page.component.ts   |   4 +-
 .../tag-cloud-pop-up.component.html           |   2 +-
 .../shared/tag-cloud/tag-cloud.component.ts   |  10 +-
 src/app/services/http/spacy.service.ts        |   6 +-
 .../services/util/room-data.service.spec.ts   |  17 +
 src/app/services/util/room-data.service.ts    | 334 ++++++++++++++++++
 .../services/util/tag-cloud-data.service.ts   |  99 ++----
 .../util/topic-cloud-admin.service.ts         |  88 ++---
 .../ws-comment-service.service.spec.ts        |  13 -
 .../websockets/ws-comment-service.service.ts  |  29 --
 .../websockets/ws-comment.service.spec.ts     |  13 +
 .../services/websockets/ws-comment.service.ts |  21 ++
 23 files changed, 624 insertions(+), 420 deletions(-)
 create mode 100644 src/app/services/util/room-data.service.spec.ts
 create mode 100644 src/app/services/util/room-data.service.ts
 delete mode 100644 src/app/services/websockets/ws-comment-service.service.spec.ts
 delete mode 100644 src/app/services/websockets/ws-comment-service.service.ts
 create mode 100644 src/app/services/websockets/ws-comment.service.spec.ts
 create mode 100644 src/app/services/websockets/ws-comment.service.ts

diff --git a/src/app/components/creator/room-creator-page/room-creator-page.component.ts b/src/app/components/creator/room-creator-page/room-creator-page.component.ts
index 8bfa542f3..094393be7 100644
--- a/src/app/components/creator/room-creator-page/room-creator-page.component.ts
+++ b/src/app/components/creator/room-creator-page/room-creator-page.component.ts
@@ -11,7 +11,7 @@ import { RoomEditComponent } from '../_dialogs/room-edit/room-edit.component';
 import { TranslateService } from '@ngx-translate/core';
 import { LanguageService } from '../../../services/util/language.service';
 import { TSMap } from 'typescript-map';
-import { WsCommentServiceService } from '../../../services/websockets/ws-comment-service.service';
+import { WsCommentService } from '../../../services/websockets/ws-comment.service';
 import { CommentService } from '../../../services/http/comment.service';
 import { ModeratorsComponent } from '../_dialogs/moderators/moderators.component';
 import { BonusTokenComponent } from '../_dialogs/bonus-token/bonus-token.component';
@@ -52,7 +52,7 @@ export class RoomCreatorPageComponent extends RoomPageComponent implements OnIni
               public dialog: MatDialog,
               private translateService: TranslateService,
               protected langService: LanguageService,
-              protected wsCommentService: WsCommentServiceService,
+              protected wsCommentService: WsCommentService,
               protected commentService: CommentService,
               private liveAnnouncer: LiveAnnouncer,
               private _r: Renderer2,
diff --git a/src/app/components/moderator/moderator-comment-list/moderator-comment-list.component.ts b/src/app/components/moderator/moderator-comment-list/moderator-comment-list.component.ts
index 67b350f74..fbd0782ff 100644
--- a/src/app/components/moderator/moderator-comment-list/moderator-comment-list.component.ts
+++ b/src/app/components/moderator/moderator-comment-list/moderator-comment-list.component.ts
@@ -5,7 +5,7 @@ import { TranslateService } from '@ngx-translate/core';
 import { LanguageService } from '../../../services/util/language.service';
 import { Message } from '@stomp/stompjs';
 import { MatDialog } from '@angular/material/dialog';
-import { WsCommentServiceService } from '../../../services/websockets/ws-comment-service.service';
+import { WsCommentService } from '../../../services/websockets/ws-comment.service';
 import { User } from '../../../models/user';
 import { Vote } from '../../../models/vote';
 import { UserRole } from '../../../models/user-roles.enum';
@@ -73,7 +73,7 @@ export class ModeratorCommentListComponent implements OnInit, OnDestroy {
     private translateService: TranslateService,
     public dialog: MatDialog,
     protected langService: LanguageService,
-    private wsCommentService: WsCommentServiceService,
+    private wsCommentService: WsCommentService,
     protected roomService: RoomService,
     public eventService: EventService,
     private router: Router,
diff --git a/src/app/components/moderator/room-moderator-page/room-moderator-page.component.ts b/src/app/components/moderator/room-moderator-page/room-moderator-page.component.ts
index 07ed7ef4a..466dc34e6 100644
--- a/src/app/components/moderator/room-moderator-page/room-moderator-page.component.ts
+++ b/src/app/components/moderator/room-moderator-page/room-moderator-page.component.ts
@@ -6,7 +6,7 @@ import { RoomService } from '../../../services/http/room.service';
 import { ActivatedRoute } from '@angular/router';
 import { TranslateService } from '@ngx-translate/core';
 import { LanguageService } from '../../../services/util/language.service';
-import { WsCommentServiceService } from '../../../services/websockets/ws-comment-service.service';
+import { WsCommentService } from '../../../services/websockets/ws-comment.service';
 import { CommentService } from '../../../services/http/comment.service';
 import { Message } from '@stomp/stompjs';
 import { NotificationService } from '../../../services/util/notification.service';
@@ -33,7 +33,7 @@ export class RoomModeratorPageComponent extends RoomPageComponent implements OnI
               protected route: ActivatedRoute,
               private translateService: TranslateService,
               protected langService: LanguageService,
-              protected wsCommentService: WsCommentServiceService,
+              protected wsCommentService: WsCommentService,
               protected commentService: CommentService,
               protected notification: NotificationService,
               public eventService: EventService,
diff --git a/src/app/components/participant/room-participant-page/room-participant-page.component.ts b/src/app/components/participant/room-participant-page/room-participant-page.component.ts
index 72b93139a..2d98c3688 100644
--- a/src/app/components/participant/room-participant-page/room-participant-page.component.ts
+++ b/src/app/components/participant/room-participant-page/room-participant-page.component.ts
@@ -8,7 +8,7 @@ import { RoomService } from '../../../services/http/room.service';
 import { ActivatedRoute } from '@angular/router';
 import { TranslateService } from '@ngx-translate/core';
 import { LanguageService } from '../../../services/util/language.service';
-import { WsCommentServiceService } from '../../../services/websockets/ws-comment-service.service';
+import { WsCommentService } from '../../../services/websockets/ws-comment.service';
 import { CommentService } from '../../../services/http/comment.service';
 import { AuthenticationService } from '../../../services/http/authentication.service';
 import { LiveAnnouncer } from '@angular/cdk/a11y';
@@ -35,7 +35,7 @@ export class RoomParticipantPageComponent extends RoomPageComponent implements O
               protected route: ActivatedRoute,
               private translateService: TranslateService,
               protected langService: LanguageService,
-              protected wsCommentService: WsCommentServiceService,
+              protected wsCommentService: WsCommentService,
               protected commentService: CommentService,
               protected authenticationService: AuthenticationService,
               private liveAnnouncer: LiveAnnouncer,
diff --git a/src/app/components/shared/_dialogs/topic-cloud-administration/TopicCloudAdminData.ts b/src/app/components/shared/_dialogs/topic-cloud-administration/TopicCloudAdminData.ts
index 5d0dbabc4..56cd28585 100644
--- a/src/app/components/shared/_dialogs/topic-cloud-administration/TopicCloudAdminData.ts
+++ b/src/app/components/shared/_dialogs/topic-cloud-administration/TopicCloudAdminData.ts
@@ -39,20 +39,25 @@ const deLabels: Label[] = [
     {tag: 'app', label: 'Apposition'},
     {tag: 'da',  label: 'Dativobjekt'},
     {tag: 'oa',  label: 'Akkusativobjekt'},
-    {tag: 'nk',  label: 'Noun Kernel Element'},
+    {tag: 'nk',  label: 'Nomen Kernelement'},
     {tag: 'mo',  label: 'Modifikator'},
-    {tag: 'cj',  label: 'Konjunktor'}
+    {tag: 'cj',  label: 'Konjunktor'},
+    {tag: 'ROOT',  label: 'Satzkernelement'},
+    {tag: 'par',  label: 'Klammerzusatz'}
 ];
 
 const enLabels: Label[] = [
-    {tag: 'no',   label: 'Noun'},
-    {tag: 'pro',  label: 'Pronoun'},
-    {tag: 've',   label: 'Verb'},
-    {tag: 'adj',  label: 'Adjective'},
-    {tag: 'adv',  label: 'AdverbDVERB'},
-    {tag: 'pre',  label: 'Preposition'},
-    {tag: 'con',  label: 'Conjunction'},
-    {tag: 'int',  label: 'Interjection'}
+    {tag: 'nsubj',   label: 'Nominal subject'},
+    {tag: 'nsubjpass',  label: 'Passive nominal subject'},
+    {tag: 'pobj',   label: 'Object of preposition'},
+    {tag: 'nummod',  label: 'Numeric modifier'},
+    {tag: 'compound',  label: 'Compound'},
+    {tag: 'dobj',  label: 'Direct object'},
+    {tag: 'amod',  label: 'Adjectival modifier'},
+    {tag: 'npadvmod',  label: 'Noun phrase as adverbial modifier'},
+    {tag: 'conj',  label: 'Conjunct'},
+    {tag: 'ROOT',  label: 'Sentence kernel element'},
+    {tag: 'intj',  label: 'Interjection'}
 ];
 
 export const spacyLabels = new Labels(deLabels, enLabels);
diff --git a/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.ts b/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.ts
index 6154f966e..5a613e270 100644
--- a/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.ts
+++ b/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.ts
@@ -1,4 +1,4 @@
-import { Component, Inject, OnDestroy, OnInit, ViewChild } from '@angular/core';
+import { Component, Inject, OnDestroy, OnInit } from '@angular/core';
 import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
 import { NotificationService } from '../../../../services/util/notification.service';
 import { TopicCloudConfirmDialogComponent } from '../topic-cloud-confirm-dialog/topic-cloud-confirm-dialog.component';
@@ -6,14 +6,12 @@ import { UserRole } from '../../../../models/user-roles.enum';
 import { TranslateService } from '@ngx-translate/core';
 import { LanguageService } from '../../../../services/util/language.service';
 import { TopicCloudAdminService } from '../../../../services/util/topic-cloud-admin.service';
-import { TopicCloudAdminData, Labels, spacyLabels } from './TopicCloudAdminData';
-import { KeywordOrFulltext } from './TopicCloudAdminData';
+import { TopicCloudAdminData, Labels, spacyLabels, KeywordOrFulltext } from './TopicCloudAdminData';
 import { User } from '../../../../models/user';
 import { Comment } from '../../../../models/comment';
 import { CommentService } from '../../../../services/http/comment.service';
-import { WsCommentServiceService } from '../../../../services/websockets/ws-comment-service.service';
 import { TSMap } from 'typescript-map';
-import { Message } from '@stomp/stompjs';
+import { RoomDataService } from '../../../../services/util/room-data.service';
 
 
 @Component({
@@ -30,6 +28,7 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy {
   profanitywordlist: string[] = [];
   blacklistSubscription = undefined;
   profanitylistSubscription = undefined;
+  commentServiceSubscription = undefined;
   keywordOrFulltextENUM = KeywordOrFulltext;
   newKeyword = undefined;
   edit = false;
@@ -68,16 +67,15 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy {
     private langService: LanguageService,
     private topicCloudAdminService: TopicCloudAdminService,
     private commentService: CommentService,
-    private wsCommentServiceService: WsCommentServiceService) {
-      this.langService.langEmitter.subscribe(lang => {
-        this.translateService.use(lang);
-      });
-    }
+    private roomDataService: RoomDataService) {
+    this.langService.langEmitter.subscribe(lang => {
+      this.translateService.use(lang);
+    });
+  }
 
   ngOnInit(): void {
     this.isLoading = true;
     this.deviceType = localStorage.getItem('deviceType');
-    this.wsCommentServiceService.getCommentStream(localStorage.getItem('roomId')).subscribe(message => this.receiveMessage(message));
     this.blacklistSubscription = this.topicCloudAdminService.getBlacklist().subscribe(list => this.blacklist = list);
     this.profanitywordlist = this.topicCloudAdminService.getProfanityListFromStorage();
     this.profanitylistSubscription = this.topicCloudAdminService.getCustomProfanityList().subscribe(list => {
@@ -92,75 +90,10 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy {
     this.initializeKeywords();
   }
 
-  receiveMessage(message: Message){
-    const msg = JSON.parse(message.body);
-    const payload = msg.payload;
-    switch (msg.type) {
-      case 'CommentCreated':
-        this.commentService.getComment(payload.id).subscribe(com => {
-          this.pushInKeywords(com);
-        });
-        break;
-      case 'CommentPatched':
-        this.keywords.forEach(keyword => {
-          for (const comment of keyword.comments) {
-            if (payload.id === comment.id) {
-              let refreshKeywords = false;
-              for (const [key, value] of Object.entries(payload.changes)) {
-                switch (key) {
-                  case 'score':
-                    comment.score = value as number;
-                    refreshKeywords = true;
-                    break;
-                  case 'upvotes':
-                    comment.upvotes = value as number;
-                    refreshKeywords = true;
-                    break;
-                  case 'downvotes':
-                    comment.downvotes = value as number;
-                    refreshKeywords = true;
-                    break;
-                  case 'keywordsFromSpacy':
-                    comment.keywordsFromSpacy = JSON.parse(value as string);
-                    refreshKeywords = true;
-                    break;
-                  case 'keywordsFromQuestioner':
-                    comment.keywordsFromQuestioner = JSON.parse(value as string);
-                    refreshKeywords = true;
-                    break;
-                  case 'ack':
-                    if (!value as boolean) {
-                      this.removeFromKeywords(payload);
-                      refreshKeywords = true;
-                    }
-                    break;
-                  case 'tag':
-                    comment.tag = value as string;
-                    refreshKeywords = true;
-                    break;
-                }
-              }
-              if (refreshKeywords) {
-                this.refreshKeywords();
-              }
-              break;
-            }
-          }
-        });
-        break;
-      case 'CommentDeleted':
-        this.removeFromKeywords(payload);
-        break;
-    }
-    if (this.searchMode){
-      this.searchKeyword();
-    }
-  }
-
-  removeFromKeywords(comment: Comment){
-    for (const keyword of this.keywords){
+  removeFromKeywords(comment: Comment) {
+    for (const keyword of this.keywords) {
       keyword.comments.forEach(_comment => {
-        if (_comment.id === comment.id){
+        if (_comment.id === comment.id) {
           keyword.comments.splice(keyword.comments.indexOf(comment, 0), 1);
         }
       });
@@ -168,18 +101,18 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy {
     this.refreshKeywords();
   }
 
-  refreshKeywords(){
+  refreshKeywords() {
     const tempKeywords = this.keywords;
     this.keywords = [];
     tempKeywords.forEach(keyword => {
       keyword.comments.forEach(comment => this.pushInKeywords(comment));
     });
-    if (this.searchMode){
+    if (this.searchMode) {
       this.searchKeyword();
     }
   }
 
-  pushInKeywords(comment: Comment){
+  pushInKeywords(comment: Comment) {
     let keywords = comment.keywordsFromQuestioner;
     if (this.keywordORfulltext === KeywordOrFulltext[KeywordOrFulltext.both]) {
       if (!keywords || !keywords.length) {
@@ -195,7 +128,7 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy {
       const existingKey = this.checkIfKeywordExists(_keyword);
       if (existingKey) {
         existingKey.vote += comment.score;
-        if (this.checkIfCommentExists(existingKey.comments, comment.id)){
+        if (this.checkIfCommentExists(existingKey.comments, comment.id)) {
           existingKey.comments.push(comment);
         }
       } else {
@@ -210,18 +143,19 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy {
     });
   }
 
-  ngOnDestroy(){
+  ngOnDestroy() {
     this.setAdminData();
-    if(this.blacklistSubscription !== undefined){
+    if (this.blacklistSubscription !== undefined) {
       this.blacklistSubscription.unsubscribe();
     }
-    if(this.profanitylistSubscription !== undefined){
+    if (this.profanitylistSubscription !== undefined) {
       this.profanitylistSubscription.unsubscribe();
     }
   }
 
-  initializeKeywords(){
-    this.commentService.getAckComments(localStorage.getItem('roomId')).subscribe(comments => {
+  initializeKeywords() {
+    const roomId = localStorage.getItem('roomId');
+    this.roomDataService.getRoomData(roomId).subscribe(comments => {
       this.keywords = [];
       comments.forEach(comment => {
         this.pushInKeywords(comment);
@@ -229,9 +163,37 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy {
       this.sortQuestions();
       this.isLoading = false;
     });
+    this.commentServiceSubscription = this.roomDataService.receiveUpdates([
+      {type: 'CommentCreated', finished: true},
+      {type: 'CommentDeleted'},
+      {type: 'CommentPatched', finished: true, updates: ['score']},
+      {type: 'CommentPatched', finished: true, updates: ['upvotes']},
+      {type: 'CommentPatched', finished: true, updates: ['downvotes']},
+      {type: 'CommentPatched', finished: true, updates: ['keywordsFromSpacy']},
+      {type: 'CommentPatched', finished: true, updates: ['keywordsFromQuestioner']},
+      {type: 'CommentPatched', finished: true, updates: ['ack']},
+      {type: 'CommentPatched', finished: true, updates: ['tag']},
+      {type: 'CommentPatched', subtype: 'ack'},
+      {finished: true}
+    ]).subscribe(update => {
+      if (update.type === 'CommentCreated') {
+        this.pushInKeywords(update.comment);
+      } else if (update.type === 'CommentDeleted') {
+        this.removeFromKeywords(update.comment);
+      } else if (update.type === 'CommentPatched' && update.subtype === 'ack') {
+        if (!update.comment.ack) {
+          this.removeFromKeywords(update.comment);
+        }
+      }
+      if (update.finished) {
+        if (this.searchMode) {
+          this.searchKeyword();
+        }
+      }
+    });
   }
 
-  checkIfCommentExists(comments: Comment[], id: string): boolean{
+  checkIfCommentExists(comments: Comment[], id: string): boolean {
     return comments.filter(comment => comment.id === id).length === 0;
   }
 
@@ -251,7 +213,7 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy {
   }
 
   setDefaultAdminData() {
-    this.topicCloudAdminData = this.topicCloudAdminService.getDefaultAdminData;
+    this.topicCloudAdminData = TopicCloudAdminService.getDefaultAdminData;
     if (this.topicCloudAdminData) {
       this.considerVotes = this.topicCloudAdminData.considerVotes;
       this.profanityFilter = this.topicCloudAdminData.profanityFilter;
@@ -286,7 +248,7 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy {
   }
 
   checkIfThereAreQuestions() {
-    if (this.keywords.length === 0){
+    if (this.keywords.length === 0) {
       this.translateService.get('topic-cloud-dialog.no-keywords-note').subscribe(msg => {
         this.notificationService.show(msg);
       });
@@ -299,11 +261,11 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy {
   editKeyword(index: number): void {
     this.edit = true;
     setTimeout(() => {
-      document.getElementById('edit-input'+ index).focus();
+      document.getElementById('edit-input' + index).focus();
     }, 0);
   }
 
-  deleteKeyword(key: Keyword, message?: string): void{
+  deleteKeyword(key: Keyword, message?: string): void {
     key.comments.forEach(comment => {
       const changes = new TSMap<string, any>();
       let keywords = comment.keywordsFromQuestioner;
@@ -315,24 +277,24 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy {
       this.updateComment(comment, changes, message);
     });
 
-    if (this.searchMode === true){
+    if (this.searchMode === true) {
       this.searchKeyword();
     }
   }
 
-  updateComment(updatedComment: Comment, changes: TSMap<string, any>, messageTranslate?: string){
+  updateComment(updatedComment: Comment, changes: TSMap<string, any>, messageTranslate?: string) {
     this.commentService.patchComment(updatedComment, changes).subscribe(_ => {
-      if (messageTranslate){
-        this.translateService.get('topic-cloud-dialog.' + messageTranslate).subscribe(msg => {
-          this.notificationService.show(msg);
-        });
-      }
-    },
+        if (messageTranslate) {
+          this.translateService.get('topic-cloud-dialog.' + messageTranslate).subscribe(msg => {
+            this.notificationService.show(msg);
+          });
+        }
+      },
       error => {
         this.translateService.get('topic-cloud-dialog.changes-gone-wrong').subscribe(msg => {
           this.notificationService.show(msg);
         });
-    });
+      });
   }
 
   cancelEdit(): void {
@@ -342,21 +304,21 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy {
 
   confirmEdit(key: Keyword): void {
     const key2 = this.checkIfKeywordExists(this.newKeyword);
-    if (key2){
+    if (key2) {
       this.openConfirmDialog('merge-message', 'merge', key, key2);
     } else {
       key.comments.forEach(comment => {
         const changes = new TSMap<string, any>();
         let keywords = comment.keywordsFromQuestioner;
-        for (let i = 0; i < keywords.length; i++){
-          if (keywords[i].toLowerCase() === key.keyword.toLowerCase()){
+        for (let i = 0; i < keywords.length; i++) {
+          if (keywords[i].toLowerCase() === key.keyword.toLowerCase()) {
             keywords[i] = this.newKeyword.trim();
           }
         }
         changes.set('keywordsFromQuestioner', JSON.stringify(keywords));
         keywords = comment.keywordsFromSpacy;
-        for (let i = 0; i < keywords.length; i++){
-          if (keywords[i].toLowerCase() === key.keyword.toLowerCase()){
+        for (let i = 0; i < keywords.length; i++) {
+          if (keywords[i].toLowerCase() === key.keyword.toLowerCase()) {
             keywords[i] = this.newKeyword.trim();
           }
         }
@@ -368,13 +330,13 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy {
     this.edit = false;
     this.newKeyword = undefined;
     this.sortQuestions();
-    if (this.searchMode){
+    if (this.searchMode) {
       this.searchKeyword();
     }
   }
 
   openConfirmDialog(msg: string, _confirmLabel: string, keyword: Keyword, mergeTarget?: Keyword) {
-    const translationPart = 'topic-cloud-confirm-dialog.'+msg;
+    const translationPart = 'topic-cloud-confirm-dialog.' + msg;
     const confirmDialogRef = this.confirmDialog.open(TopicCloudConfirmDialogComponent, {
       data: {topic: keyword.keyword, message: translationPart, confirmLabel: _confirmLabel}
     });
@@ -389,7 +351,7 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy {
   }
 
   searchKeyword(): void {
-    if (!this.searchedKeyword){
+    if (!this.searchedKeyword) {
       this.searchMode = false;
     } else {
       this.filteredKeywords = this.keywords.filter(keyword =>
@@ -400,9 +362,9 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy {
   }
 
   mergeKeywords(key1: Keyword, key2: Keyword) {
-    if (key1 !== undefined && key2 !== undefined){
+    if (key1 !== undefined && key2 !== undefined) {
       key1.comments.forEach(comment => {
-        if (this.checkIfCommentExists(key2.comments, comment.id)){
+        if (this.checkIfCommentExists(key2.comments, comment.id)) {
           const changes = new TSMap<string, any>();
           let keywords = comment.keywordsFromQuestioner;
           keywords.push(key2.keyword);
@@ -418,8 +380,8 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy {
   }
 
   checkIfKeywordExists(key: string): Keyword {
-    for(const keyword of this.keywords){
-      if(keyword.keyword.toLowerCase() === key.trim().toLowerCase()){
+    for (const keyword of this.keywords) {
+      if (keyword.keyword.toLowerCase() === key.trim().toLowerCase()) {
         return keyword;
       }
     }
@@ -451,11 +413,11 @@ export class TopicCloudAdministrationComponent implements OnInit, OnDestroy {
   }
 
   changeProfanityFilter() {
-    if (this.profanityFilter){
+    if (this.profanityFilter) {
       this.translateService.get('topic-cloud-dialog.words-will-be-overwritten').subscribe(msg => {
         this.notificationService.show(msg);
       });
-      if (this.searchMode){
+      if (this.searchMode) {
         this.searchKeyword();
       }
     }
@@ -493,7 +455,7 @@ interface Keyword {
   vote: number;
 }
 
-export interface Data{
+export interface Data {
   user: User;
 }
 
diff --git a/src/app/components/shared/comment-answer/comment-answer.component.ts b/src/app/components/shared/comment-answer/comment-answer.component.ts
index 536bfb9d2..7b8ce1a71 100644
--- a/src/app/components/shared/comment-answer/comment-answer.component.ts
+++ b/src/app/components/shared/comment-answer/comment-answer.component.ts
@@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core';
 import { ActivatedRoute } from '@angular/router';
 import { TranslateService } from '@ngx-translate/core';
 import { LanguageService } from '../../../services/util/language.service';
-import { WsCommentServiceService } from '../../../services/websockets/ws-comment-service.service';
+import { WsCommentService } from '../../../services/websockets/ws-comment.service';
 import { CommentService } from '../../../services/http/comment.service';
 import { Comment } from '../../../models/comment';
 import { User } from '../../../models/user';
@@ -30,7 +30,7 @@ export class CommentAnswerComponent implements OnInit {
               private notificationService: NotificationService,
               private translateService: TranslateService,
               protected langService: LanguageService,
-              protected wsCommentService: WsCommentServiceService,
+              protected wsCommentService: WsCommentService,
               protected commentService: CommentService,
               private authenticationService: AuthenticationService,
               public dialog: MatDialog) { }
diff --git a/src/app/components/shared/comment-list/comment-list.component.ts b/src/app/components/shared/comment-list/comment-list.component.ts
index 72473799d..a4b96fa6e 100644
--- a/src/app/components/shared/comment-list/comment-list.component.ts
+++ b/src/app/components/shared/comment-list/comment-list.component.ts
@@ -3,9 +3,7 @@ import { Comment } from '../../../models/comment';
 import { CommentService } from '../../../services/http/comment.service';
 import { TranslateService } from '@ngx-translate/core';
 import { LanguageService } from '../../../services/util/language.service';
-import { Message } from '@stomp/stompjs';
 import { MatDialog } from '@angular/material/dialog';
-import { WsCommentServiceService } from '../../../services/websockets/ws-comment-service.service';
 import { User } from '../../../models/user';
 import { Vote } from '../../../models/vote';
 import { UserRole } from '../../../models/user-roles.enum';
@@ -29,6 +27,7 @@ import { BonusTokenService } from '../../../services/http/bonus-token.service';
 import { ModeratorService } from '../../../services/http/moderator.service';
 import { CommentFilter, Period } from '../../../utils/filter-options';
 import { CreateCommentWrapper } from '../../../utils/CreateCommentWrapper';
+import { RoomDataService } from '../../../services/util/room-data.service';
 
 export interface CommentListData {
   comments: Comment[];
@@ -104,7 +103,6 @@ export class CommentListComponent implements OnInit, OnDestroy {
     private translateService: TranslateService,
     public dialog: MatDialog,
     protected langService: LanguageService,
-    private wsCommentService: WsCommentServiceService,
     protected roomService: RoomService,
     protected voteService: VoteService,
     private authenticationService: AuthenticationService,
@@ -117,6 +115,7 @@ export class CommentListComponent implements OnInit, OnDestroy {
     private translationService: TranslateService,
     private bonusTokenService: BonusTokenService,
     private moderatorService: ModeratorService,
+    private roomDataService: RoomDataService,
   ) {
     langService.langEmitter.subscribe(lang => translateService.use(lang));
   }
@@ -238,13 +237,12 @@ export class CommentListComponent implements OnInit, OnDestroy {
             this.moderatorIds = list.map(m => m.accountId);
             this.moderatorIds.push(this.room.ownerId);
 
+            this.roomDataService.getRoomData(this.room.id).subscribe(comments => {
+              this.comments = comments;
+              this.getComments();
+              this.eventService.broadcast('commentListCreated', null);
+            });
             this.subscribeCommentStream();
-            this.commentService.getAckComments(this.room.id)
-              .subscribe(comments => {
-                this.comments = comments;
-                this.getComments();
-                this.eventService.broadcast('commentListCreated', null);
-              });
           });
           /**
            if (this.userRole === UserRole.PARTICIPANT) {
@@ -341,97 +339,6 @@ export class CommentListComponent implements OnInit, OnDestroy {
     }
   }
 
-  parseIncomingMessage(message: Message) {
-    const msg = JSON.parse(message.body);
-    const payload = msg.payload;
-    switch (msg.type) {
-      case 'CommentCreated':
-        const c = new Comment();
-        c.roomId = this.roomId;
-        c.body = payload.body;
-        c.id = payload.id;
-        c.timestamp = payload.timestamp;
-        c.tag = payload.tag;
-        c.creatorId = payload.creatorId;
-        c.userNumber = this.commentService.hashCode(c.creatorId);
-        this.commentService.getComment(c.id).subscribe(e => {
-          c.number = e.number;
-          c.keywordsFromQuestioner = e.keywordsFromQuestioner;
-        });
-
-        this.announceNewComment(c.body);
-        this.comments = this.comments.concat(c);
-        this.setComments(this.comments);
-        break;
-      case 'CommentPatched':
-        // ToDo: Use a map for comments w/ key = commentId
-        for (let i = 0; i < this.comments.length; i++) {
-          if (payload.id === this.comments[i].id) {
-            for (const [key, value] of Object.entries(payload.changes)) {
-              switch (key) {
-                case this.read:
-                  this.comments[i].read = <boolean>value;
-                  break;
-                case this.correct:
-                  this.comments[i].correct = <CorrectWrong>value;
-                  break;
-                case this.favorite:
-                  this.comments[i].favorite = <boolean>value;
-                  if (this.user.id === this.comments[i].creatorId && <boolean>value) {
-                    this.translateService.get('comment-list.comment-got-favorited').subscribe(ret => {
-                      this.notificationService.show(ret);
-                    });
-                  }
-                  break;
-                case this.bookmark:
-                  this.comments[i].bookmark = <boolean>value;
-                  break;
-                case 'score':
-                  this.comments[i].score = <number>value;
-                  this.getComments();
-                  break;
-                case this.ack:
-                  const isNowAck = <boolean>value;
-                  if (!isNowAck) {
-                    this.comments = this.comments.filter((el) => {
-                      return el.id !== payload.id;
-                    });
-                    this.setTimePeriod();
-                  }
-                  break;
-                case this.tag:
-                  this.comments[i].tag = <string>value;
-                  break;
-                case this.answer:
-                  this.comments[i].answer = <string>value;
-                  break;
-              }
-            }
-          }
-        }
-        break;
-      case 'CommentHighlighted':
-        // ToDo: Use a map for comments w/ key = commentId
-        for (let i = 0; i < this.comments.length; i++) {
-          if (payload.id === this.comments[i].id) {
-            this.comments[i].highlighted = <boolean>payload.lights;
-          }
-        }
-        break;
-      case 'CommentDeleted':
-        for (let i = 0; i < this.comments.length; i++) {
-          this.comments = this.comments.filter((el) => {
-            return el.id !== payload.id;
-          });
-        }
-        break;
-    }
-    this.setTimePeriod();
-    if (this.hideCommentsList) {
-      this.searchComments();
-    }
-  }
-
   closeDialog() {
     this.dialog.closeAll();
   }
@@ -468,7 +375,7 @@ export class CommentListComponent implements OnInit, OnDestroy {
           return c.userNumber === compare;
         case this.keyword:
           this.selectedKeyword = compare;
-          return c.keywordsFromQuestioner != null && c.keywordsFromQuestioner.length > 0 ? c.keywordsFromQuestioner.includes(compare) : false;
+          return c.keywordsFromQuestioner ? c.keywordsFromQuestioner.includes(compare) : false;
         case this.answer:
           return c.answer;
         case this.unanswered:
@@ -527,6 +434,12 @@ export class CommentListComponent implements OnInit, OnDestroy {
 
   pauseCommentStream() {
     this.freeze = true;
+    this.roomDataService.getRoomData(this.roomId, true)
+      .subscribe(comments => {
+        this.comments = comments;
+        this.setComments(comments);
+        this.getComments();
+      });
     this.commentStream.unsubscribe();
     this.translateService.get('comment-list.comment-stream-stopped').subscribe(msg => {
       this.notificationService.show(msg);
@@ -535,7 +448,7 @@ export class CommentListComponent implements OnInit, OnDestroy {
 
   playCommentStream() {
     this.freeze = false;
-    this.commentService.getAckComments(this.roomId)
+    this.roomDataService.getRoomData(this.roomId)
       .subscribe(comments => {
         this.comments = comments;
         this.setComments(comments);
@@ -548,8 +461,32 @@ export class CommentListComponent implements OnInit, OnDestroy {
   }
 
   subscribeCommentStream() {
-    this.commentStream = this.wsCommentService.getCommentStream(this.room.id).subscribe((message: Message) => {
-      this.parseIncomingMessage(message);
+    this.commentStream = this.roomDataService.receiveUpdates([
+      {type: 'CommentCreated', finished: true},
+      {type: 'CommentPatched', subtype: this.favorite},
+      {type: 'CommentPatched', subtype: 'score'},
+      {finished: true}
+    ]).subscribe(update => {
+      if (update.type === 'CommentCreated') {
+        this.announceNewComment(update.comment.body);
+        this.setComments(this.comments);
+      } else if (update.type === 'CommentPatched') {
+        if (update.subtype === 'score') {
+          this.getComments();
+        } else if (update.subtype === this.favorite) {
+          if (this.user.id === update.comment.creatorId && update.comment.favorite) {
+            this.translateService.get('comment-list.comment-got-favorited').subscribe(ret => {
+              this.notificationService.show(ret);
+            });
+          }
+        }
+      }
+      if (update.finished) {
+        this.setTimePeriod();
+        if (this.hideCommentsList) {
+          this.searchComments();
+        }
+      }
     });
   }
 
diff --git a/src/app/components/shared/comment/comment.component.ts b/src/app/components/shared/comment/comment.component.ts
index 768371bf7..f3e688c7c 100644
--- a/src/app/components/shared/comment/comment.component.ts
+++ b/src/app/components/shared/comment/comment.component.ts
@@ -1,4 +1,4 @@
-import { Component, Input, Output, OnInit, EventEmitter, ViewChild, AfterViewInit, Renderer2 } from '@angular/core';
+import { Component, Input, Output, OnInit, EventEmitter, ViewChild, AfterViewInit } from '@angular/core';
 import { Comment } from '../../../models/comment';
 import { Vote } from '../../../models/vote';
 import { AuthenticationService } from '../../../services/http/authentication.service';
@@ -8,7 +8,6 @@ import { CommentService } from '../../../services/http/comment.service';
 import { NotificationService } from '../../../services/util/notification.service';
 import { TranslateService } from '@ngx-translate/core';
 import { LanguageService } from '../../../services/util/language.service';
-import { WsCommentServiceService } from '../../../services/websockets/ws-comment-service.service';
 import { PresentCommentComponent } from '../_dialogs/present-comment/present-comment.component';
 import { MatDialog } from '@angular/material/dialog';
 import { animate, state, style, transition, trigger } from '@angular/animations';
@@ -18,7 +17,6 @@ import { UserRole } from '../../../models/user-roles.enum';
 import { Rescale } from '../../../models/rescale';
 import { RowComponent } from '../../../../../projects/ars/src/lib/components/layout/frame/row/row.component';
 import { User } from '../../../models/user';
-import { Observable } from 'rxjs';
 
 @Component({
   selector: 'app-comment',
@@ -70,8 +68,7 @@ export class CommentComponent implements OnInit, AfterViewInit {
     private notification: NotificationService,
     private translateService: TranslateService,
     public dialog: MatDialog,
-    protected langService: LanguageService,
-    private wsCommentService: WsCommentServiceService) {
+    protected langService: LanguageService) {
     langService.langEmitter.subscribe(lang => {
       translateService.use(lang);
       this.language = lang;
@@ -227,7 +224,7 @@ export class CommentComponent implements OnInit, AfterViewInit {
   setBookmark(comment: Comment): void {
     //@ts-ignore
     this.commentService.toggleBookmark(comment).subscribe(c => {this.comment = c; this.comment.keywordsFromQuestioner = JSON.parse(c.keywordsFromQuestioner)});
-    //@ts-ignore  
+    //@ts-ignore
   }
 
   goToFullScreen(element: Element): void {
diff --git a/src/app/components/shared/header/header.component.html b/src/app/components/shared/header/header.component.html
index 36be0756f..a896aa9a9 100644
--- a/src/app/components/shared/header/header.component.html
+++ b/src/app/components/shared/header/header.component.html
@@ -163,6 +163,15 @@
                 <span>{{'header.tag-cloud' | translate}}</span>
               </button>
 
+              <button mat-menu-item
+                      tabindex="0"
+                      *ngIf="user && user.role > 0"
+                      (click)="startWorkerDialog()">
+                <mat-icon>update
+                </mat-icon>
+                <span>{{'header.update-spacy-keywords' | translate}}</span>
+              </button>
+
             </ng-container>
             <ng-container *ngIf="router.url.includes('/participant/room/')">
             </ng-container>
diff --git a/src/app/components/shared/questionwall/question-wall/question-wall.component.ts b/src/app/components/shared/questionwall/question-wall/question-wall.component.ts
index d00502ced..b0a18cbaa 100644
--- a/src/app/components/shared/questionwall/question-wall/question-wall.component.ts
+++ b/src/app/components/shared/questionwall/question-wall/question-wall.component.ts
@@ -3,7 +3,7 @@ import { CommentService } from '../../../../services/http/comment.service';
 import { Comment } from '../../../../models/comment';
 import { RoomService } from '../../../../services/http/room.service';
 import { Room } from '../../../../models/room';
-import { WsCommentServiceService } from '../../../../services/websockets/ws-comment-service.service';
+import { WsCommentService } from '../../../../services/websockets/ws-comment.service';
 import { QuestionWallComment } from '../QuestionWallComment';
 import { ColComponent } from '../../../../../../projects/ars/src/lib/components/layout/frame/col/col.component';
 import { Router } from '@angular/router';
@@ -67,7 +67,7 @@ export class QuestionWallComponent implements OnInit, AfterViewInit, OnDestroy {
     private router: Router,
     private commentService: CommentService,
     private roomService: RoomService,
-    private wsCommentService: WsCommentServiceService,
+    private wsCommentService: WsCommentService,
     private langService: LanguageService,
     private translateService: TranslateService
   ) {
diff --git a/src/app/components/shared/room-page/room-page.component.ts b/src/app/components/shared/room-page/room-page.component.ts
index d61ddc13f..0efacd3c4 100644
--- a/src/app/components/shared/room-page/room-page.component.ts
+++ b/src/app/components/shared/room-page/room-page.component.ts
@@ -4,7 +4,7 @@ import { User } from '../../../models/user';
 import { RoomService } from '../../../services/http/room.service';
 import { ActivatedRoute } from '@angular/router';
 import { Location } from '@angular/common';
-import { WsCommentServiceService } from '../../../services/websockets/ws-comment-service.service';
+import { WsCommentService } from '../../../services/websockets/ws-comment.service';
 import { CommentService } from '../../../services/http/comment.service';
 import { EventService } from '../../../services/util/event.service';
 import { Message, IMessage } from '@stomp/stompjs';
@@ -28,7 +28,7 @@ export class RoomPageComponent implements OnInit, OnDestroy {
   constructor(protected roomService: RoomService,
               protected route: ActivatedRoute,
               protected location: Location,
-              protected wsCommentService: WsCommentServiceService,
+              protected wsCommentService: WsCommentService,
               protected commentService: CommentService,
               protected eventService: EventService
   ) {
diff --git a/src/app/components/shared/tag-cloud/tag-cloud-pop-up/tag-cloud-pop-up.component.html b/src/app/components/shared/tag-cloud/tag-cloud-pop-up/tag-cloud-pop-up.component.html
index 2fb300699..c79c3e282 100644
--- a/src/app/components/shared/tag-cloud/tag-cloud-pop-up/tag-cloud-pop-up.component.html
+++ b/src/app/components/shared/tag-cloud/tag-cloud-pop-up/tag-cloud-pop-up.component.html
@@ -42,7 +42,7 @@
         </p>
       </span>
     </div>
-    <div class="replacementContainer" *ngIf="checkLanguage">
+    <div class="replacementContainer" *ngIf="checkLanguage && user && user.role >= 1">
       <mat-form-field>
         <mat-label>{{'tag-cloud-popup.tag-correction-placeholder' | translate}}</mat-label>
         <input type="text"
diff --git a/src/app/components/shared/tag-cloud/tag-cloud.component.ts b/src/app/components/shared/tag-cloud/tag-cloud.component.ts
index 615d6f17d..87dac552b 100644
--- a/src/app/components/shared/tag-cloud/tag-cloud.component.ts
+++ b/src/app/components/shared/tag-cloud/tag-cloud.component.ts
@@ -1,4 +1,4 @@
-import { AfterViewInit, Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
+import { AfterContentInit, Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
 
 import {
   CloudData,
@@ -22,7 +22,7 @@ import { RoomService } from '../../../services/http/room.service';
 import { ThemeService } from '../../../../theme/theme.service';
 import { cloneParameters, CloudParameters, CloudTextStyle, CloudWeightSettings } from './tag-cloud.interface';
 import { TopicCloudAdministrationComponent } from '../_dialogs/topic-cloud-administration/topic-cloud-administration.component';
-import { WsCommentServiceService } from '../../../services/websockets/ws-comment-service.service';
+import { WsCommentService } from '../../../services/websockets/ws-comment.service';
 import { CreateCommentWrapper } from '../../../utils/CreateCommentWrapper';
 import { TopicCloudAdminService } from '../../../services/util/topic-cloud-admin.service';
 import { TagCloudPopUpComponent } from './tag-cloud-pop-up/tag-cloud-pop-up.component';
@@ -148,7 +148,7 @@ const getDefaultCloudParameters = (): CloudParameters => {
   templateUrl: './tag-cloud.component.html',
   styleUrls: ['./tag-cloud.component.scss']
 })
-export class TagCloudComponent implements OnInit, AfterViewInit, OnDestroy {
+export class TagCloudComponent implements OnInit, OnDestroy, AfterContentInit {
 
   @ViewChild(TCloudComponent, {static: false}) child: TCloudComponent;
   @ViewChild(TagCloudPopUpComponent) popup: TagCloudPopUpComponent;
@@ -194,7 +194,7 @@ export class TagCloudComponent implements OnInit, AfterViewInit, OnDestroy {
               private route: ActivatedRoute,
               protected roomService: RoomService,
               private themeService: ThemeService,
-              private wsCommentService: WsCommentServiceService,
+              private wsCommentService: WsCommentService,
               private topicCloudAdmin: TopicCloudAdminService,
               private router: Router,
               public dataManager: TagCloudDataService) {
@@ -277,7 +277,7 @@ export class TagCloudComponent implements OnInit, AfterViewInit, OnDestroy {
     });
   }
 
-  ngAfterViewInit() {
+  ngAfterContentInit() {
     document.getElementById('footer_rescale').style.display = 'none';
     this._calcFont = window.getComputedStyle(document.getElementById('tagCloudComponent')).fontFamily;
     this.dataManager.bindToRoom(this.roomId);
diff --git a/src/app/services/http/spacy.service.ts b/src/app/services/http/spacy.service.ts
index eb4f184a5..11e2b1d91 100644
--- a/src/app/services/http/spacy.service.ts
+++ b/src/app/services/http/spacy.service.ts
@@ -3,6 +3,7 @@ import { HttpClient, HttpHeaders } from '@angular/common/http';
 import { Observable } from 'rxjs';
 import { BaseHttpService } from './base-http.service';
 import { catchError, map, tap } from 'rxjs/operators';
+import { TopicCloudAdminService } from '../util/topic-cloud-admin.service';
 
 export type Model = 'de' | 'en' | 'fr' | 'es' | 'it' | 'nl' | 'pt' | 'auto';
 
@@ -177,12 +178,13 @@ export class SpacyService extends BaseHttpService {
 
   getKeywords(text: string, model: Model): Observable<string[]> {
     const url = '/spacy';
+    const wanted = TopicCloudAdminService.getDefaultAdminData.wantedLabels[model];
     return this.http.post<KeywordList>(url, {text, model}, httpOptions)
       .pipe(
         tap(_ => ''),
         catchError(this.handleError<any>('getKeywords')),
-        map((result: KeywordList) =>
-          [...new Set(result.map(e => e.type === 'entity' ? e.text.trim() : e.lemma.trim()))])
+        map((elem: KeywordList) => wanted != null ? elem.filter(e => wanted.includes(e.dep)) : elem),
+        map((result: KeywordList) => [...new Set(result.map(e => e.lemma.trim()))])
       );
   }
 }
diff --git a/src/app/services/util/room-data.service.spec.ts b/src/app/services/util/room-data.service.spec.ts
new file mode 100644
index 000000000..2cf2be03c
--- /dev/null
+++ b/src/app/services/util/room-data.service.spec.ts
@@ -0,0 +1,17 @@
+/*import { TestBed } from '@angular/core/testing';
+
+import { RoomDataService } from './room-data.service';
+
+describe('RoomDataService', () => {
+  let service: RoomDataService;
+
+  beforeEach(() => {
+    TestBed.configureTestingModule({});
+    service = TestBed.inject(RoomDataService);
+  });
+
+  it('should be created', () => {
+    expect(service).toBeTruthy();
+  });
+});
+*/
diff --git a/src/app/services/util/room-data.service.ts b/src/app/services/util/room-data.service.ts
new file mode 100644
index 000000000..a88ea65f9
--- /dev/null
+++ b/src/app/services/util/room-data.service.ts
@@ -0,0 +1,334 @@
+import { Injectable } from '@angular/core';
+import { Observable, of, Subject, Subscription } from 'rxjs';
+import { WsCommentService } from '../websockets/ws-comment.service';
+import { Message } from '@stomp/stompjs';
+import { Comment } from '../../models/comment';
+import { CommentService } from '../http/comment.service';
+import { CorrectWrong } from '../../models/correct-wrong.enum';
+
+export interface UpdateInformation {
+  type: 'CommentCreated' | 'CommentPatched' | 'CommentHighlighted' | 'CommentDeleted';
+  subtype?: string;
+  comment: Comment;
+  finished?: boolean;
+  updates?: string[];
+}
+
+class RoomDataUpdateSubscription {
+  updateSubject = new Subject<UpdateInformation>();
+  private readonly _filters: Partial<UpdateInformation>[];
+
+  constructor(filters: Partial<UpdateInformation>[]) {
+    this._filters = filters;
+  }
+
+  onUpdate(event: UpdateInformation): void {
+    for (const filter of this._filters) {
+      if (this.ensureEqual(filter, event)) {
+        this.updateSubject.next(event);
+        break;
+      }
+    }
+  }
+
+  /**
+   * Checks if value1 is a subset of value2
+   */
+  private ensureEqual(value1: any, value2: any): boolean {
+    if (Array.isArray(value1)) {
+      if (!Array.isArray(value2)) {
+        return false;
+      }
+      for (const key of value1) {
+        let same = false;
+        for (const otherKey of value2) {
+          if (this.ensureEqual(key, otherKey)) {
+            same = true;
+            break;
+          }
+        }
+        if (!same) {
+          return false;
+        }
+      }
+      return true;
+    } else if (typeof value1 === 'object') {
+      if (typeof value2 !== 'object') {
+        return false;
+      }
+      const keys = Object.keys(value1);
+      for (const key of keys) {
+        if (!this.ensureEqual(value1[key], value2[key])) {
+          return false;
+        }
+      }
+      return true;
+    }
+    return value1 === value2;
+  }
+}
+
+enum UpdateType {
+  force,
+  commentStream
+}
+
+interface FastRoomAccessObject {
+  [commentId: string]: Comment;
+}
+
+@Injectable({
+  providedIn: 'root'
+})
+export class RoomDataService {
+
+  private _currentSubscriptions: RoomDataUpdateSubscription[] = [];
+  private _currentComments: Comment[] = null;
+  private _commentUpdates: Subject<Comment[]> = new Subject<Comment[]>();
+  private _fastCommentAccess: FastRoomAccessObject = null;
+  private _wsCommentServiceSubscription: Subscription = null;
+  private _currentRoomId: string = null;
+
+  constructor(private wsCommentService: WsCommentService,
+              private commentService: CommentService) {
+  }
+
+  get currentRoomData() {
+    return this._currentComments;
+  }
+
+  receiveUpdates(updateFilter: Partial<UpdateInformation>[]): Observable<UpdateInformation> {
+    if (!this._currentRoomId) {
+      console.error('Update Subscription got not registered, room is not bound!');
+      return null;
+    }
+    const subscription = new RoomDataUpdateSubscription(updateFilter);
+    this._currentSubscriptions.push(subscription);
+    return subscription.updateSubject.asObservable();
+  }
+
+  getRoomData(roomId: string, freezed: boolean = false): Observable<Comment[]> {
+    if (roomId && roomId === this._currentRoomId) {
+      return of(freezed ? [...this._currentComments] : this._currentComments);
+    }
+    const tempSubject = new Subject<Comment[]>();
+    const subscription = this._commentUpdates.subscribe(comments => {
+      tempSubject.next(freezed ? [...comments] : comments);
+      subscription.unsubscribe();
+    });
+    this.ensureRoomBinding(roomId);
+    return tempSubject.asObservable();
+  }
+
+  private ensureRoomBinding(roomId: string) {
+    if (!roomId || roomId === this._currentRoomId) {
+      return;
+    }
+    this._currentSubscriptions.length = 0;
+    this._currentRoomId = roomId;
+    this._currentComments = null;
+    this._fastCommentAccess = {};
+    if (this._wsCommentServiceSubscription) {
+      this._wsCommentServiceSubscription.unsubscribe();
+    }
+    this._wsCommentServiceSubscription = this.wsCommentService.getCommentStream(roomId)
+      .subscribe(msg => this.onMessageReceive(msg));
+    this.commentService.getAckComments(roomId).subscribe(comments => {
+      this._currentComments = comments;
+      for (const comment of comments) {
+        this._fastCommentAccess[comment.id] = comment;
+      }
+      this.triggerUpdate(UpdateType.force, null);
+    });
+  }
+
+  private triggerUpdate(type: UpdateType, additionalInformation: UpdateInformation) {
+    if (type === UpdateType.force) {
+      this._commentUpdates.next(this._currentComments);
+    } else if (type === UpdateType.commentStream) {
+      for (const subscription of this._currentSubscriptions) {
+        subscription.onUpdate(additionalInformation);
+      }
+    }
+  }
+
+  private onMessageReceive(message: Message) {
+    const msg = JSON.parse(message.body);
+    const payload = msg.payload;
+    switch (msg.type) {
+      case 'CommentCreated':
+        this.onCommentCreate(payload);
+        break;
+      case 'CommentPatched':
+        this.onCommentPatched(payload);
+        break;
+      case 'CommentHighlighted':
+        this.onCommentHighlighted(payload);
+        break;
+      case 'CommentDeleted':
+        this.onCommentDeleted(payload);
+        break;
+    }
+  }
+
+  private onCommentCreate(payload: any) {
+    const c = new Comment();
+    c.roomId = this._currentRoomId;
+    c.body = payload.body;
+    c.id = payload.id;
+    c.timestamp = payload.timestamp;
+    c.tag = payload.tag;
+    c.creatorId = payload.creatorId;
+    c.userNumber = this.commentService.hashCode(c.creatorId);
+    this.triggerUpdate(UpdateType.commentStream, {
+      type: 'CommentCreated',
+      finished: false,
+      comment: c
+    });
+    this.commentService.getComment(c.id).subscribe(comment => {
+      this._fastCommentAccess[comment.id] = comment;
+      this._currentComments.push(comment);
+      this.triggerUpdate(UpdateType.commentStream, {
+        type: 'CommentCreated',
+        finished: true,
+        comment
+      });
+    });
+  }
+
+  private onCommentPatched(payload: any) {
+    const comment = this._fastCommentAccess[payload.id];
+    if (!comment) {
+      console.error('comment ' + payload.id + ' was not found!');
+      return;
+    }
+    const updates = [];
+    for (const [key, value] of Object.entries(payload.changes)) {
+      updates.push(key);
+      switch (key) {
+        case 'read':
+          comment.read = value as boolean;
+          this.triggerUpdate(UpdateType.commentStream, {
+            type: 'CommentPatched',
+            subtype: 'read',
+            comment
+          });
+          break;
+        case 'correct':
+          comment.correct = value as CorrectWrong;
+          this.triggerUpdate(UpdateType.commentStream, {
+            type: 'CommentPatched',
+            subtype: 'correct',
+            comment
+          });
+          break;
+        case 'favorite':
+          comment.favorite = value as boolean;
+          this.triggerUpdate(UpdateType.commentStream, {
+            type: 'CommentPatched',
+            subtype: 'favorite',
+            comment
+          });
+          break;
+        case 'bookmark':
+          comment.bookmark = value as boolean;
+          this.triggerUpdate(UpdateType.commentStream, {
+            type: 'CommentPatched',
+            subtype: 'bookmark',
+            comment
+          });
+          break;
+        case 'score':
+          comment.score = value as number;
+          this.triggerUpdate(UpdateType.commentStream, {
+            type: 'CommentPatched',
+            subtype: 'score',
+            comment
+          });
+          break;
+        case 'upvotes':
+          comment.upvotes = value as number;
+          break;
+        case 'downvotes':
+          comment.downvotes = value as number;
+          break;
+        case 'keywordsFromSpacy':
+          comment.keywordsFromSpacy = JSON.parse(value as string);
+          break;
+        case 'keywordsFromQuestioner':
+          comment.keywordsFromQuestioner = JSON.parse(value as string);
+          break;
+        case 'ack':
+          const isNowAck = value as boolean;
+          comment.ack = isNowAck;
+          if (!isNowAck) {
+            this.removeComment(payload.id);
+          }
+          this.triggerUpdate(UpdateType.commentStream, {
+            type: 'CommentPatched',
+            subtype: 'ack',
+            comment
+          });
+          break;
+        case 'tag':
+          comment.tag = value as string;
+          this.triggerUpdate(UpdateType.commentStream, {
+            type: 'CommentPatched',
+            subtype: 'tag',
+            comment
+          });
+          break;
+        case 'answer':
+          comment.answer = value as string;
+          this.triggerUpdate(UpdateType.commentStream, {
+            type: 'CommentPatched',
+            subtype: 'answer',
+            comment
+          });
+          break;
+      }
+    }
+    this.triggerUpdate(UpdateType.commentStream, {
+      type: 'CommentPatched',
+      finished: true,
+      updates,
+      comment
+    });
+  }
+
+  private onCommentHighlighted(payload: any) {
+    const comment = this._fastCommentAccess[payload.id];
+    if (!comment) {
+      console.error('comment ' + payload.id + ' was not found!');
+      return;
+    }
+    comment.highlighted = payload.lights as boolean;
+    this.triggerUpdate(UpdateType.commentStream, {
+      type: 'CommentHighlighted',
+      finished: true,
+      comment
+    });
+  }
+
+  private onCommentDeleted(payload: any) {
+    const comment = this._fastCommentAccess[payload.id];
+    if (!comment) {
+      console.error('comment ' + payload.id + ' was not found!');
+      return;
+    }
+    this.removeComment(payload.id);
+    this.triggerUpdate(UpdateType.commentStream, {
+      type: 'CommentDeleted',
+      finished: true,
+      comment
+    });
+  }
+
+  private removeComment(id: string) {
+    const index = this._currentComments.findIndex(el => el.id === id);
+    if (index >= 0) {
+      this._currentComments.splice(index, 1);
+    }
+    this._fastCommentAccess[id] = undefined;
+  }
+}
diff --git a/src/app/services/util/tag-cloud-data.service.ts b/src/app/services/util/tag-cloud-data.service.ts
index 9647b619c..176e49609 100644
--- a/src/app/services/util/tag-cloud-data.service.ts
+++ b/src/app/services/util/tag-cloud-data.service.ts
@@ -1,14 +1,12 @@
 import { Injectable } from '@angular/core';
 import { TopicCloudAdminData } from '../../components/shared/_dialogs/topic-cloud-administration/TopicCloudAdminData';
 import { Observable, Subject, Subscription } from 'rxjs';
-import { WsCommentServiceService } from '../websockets/ws-comment-service.service';
-import { CommentService } from '../http/comment.service';
 import { TopicCloudAdminService } from './topic-cloud-admin.service';
 import { CommentFilter } from '../../utils/filter-options';
 import { TranslateService } from '@ngx-translate/core';
 import { CloudParameters } from '../../components/shared/tag-cloud/tag-cloud.interface';
 import { Comment } from '../../models/comment';
-import { Message } from '@stomp/stompjs';
+import { RoomDataService } from './room-data.service';
 
 export interface TagCloudDataTagEntry {
   weight: number;
@@ -70,7 +68,7 @@ export class TagCloudDataService {
   private _dataBus: Subject<TagCloudData>;
   private _metaDataBus: Subject<TagCloudMetaData>;
   private _cachedData: TagCloudData;
-  private _wsCommentSubscription = null;
+  private _commentSubscription = null;
   private _roomId = null;
   private _supplyType = TagCloudDataSupplyType.keywordsAndFullText;
   private _calcWeightType = TagCloudCalcWeightType.byLength;
@@ -83,9 +81,8 @@ export class TagCloudDataService {
   private _subscriptionAdminData: Subscription;
   private _currentFilter: CommentFilter;
 
-  constructor(private _wsCommentService: WsCommentServiceService,
-              private _commentService: CommentService,
-              private _tagCloudAdmin: TopicCloudAdminService) {
+  constructor(private _tagCloudAdmin: TopicCloudAdminService,
+              private _roomDataService: RoomDataService) {
     this._isDemoActive = false;
     this._isAlphabeticallySorted = false;
     this._dataBus = new Subject<TagCloudData>();
@@ -108,23 +105,34 @@ export class TagCloudDataService {
   bindToRoom(roomId: string): void {
     this._currentFilter = CommentFilter.currentFilter;
     this._roomId = roomId;
-    this.onReceiveAdminData(this._tagCloudAdmin.getDefaultAdminData);
+    this.onReceiveAdminData(TopicCloudAdminService.getDefaultAdminData);
     this._subscriptionAdminData = this._tagCloudAdmin.getAdminData.subscribe(adminData => {
       this.onReceiveAdminData(adminData, true);
     });
 
     this.fetchData();
     if (!this._currentFilter.paused) {
-      this._wsCommentSubscription = this._wsCommentService
-        .getCommentStream(this._roomId).subscribe(e => this.onMessage(e));
+      this._commentSubscription = this._roomDataService.receiveUpdates([
+        {type: 'CommentCreated', finished: true},
+        {type: 'CommentDeleted'},
+        {type: 'CommentPatched', finished: true, updates: ['score']},
+        {type: 'CommentPatched', finished: true, updates: ['downvotes']},
+        {type: 'CommentPatched', finished: true, updates: ['upvotes']},
+        {type: 'CommentPatched', finished: true, updates: ['keywordsFromSpacy']},
+        {type: 'CommentPatched', finished: true, updates: ['keywordsFromQuestioner']},
+        {type: 'CommentPatched', finished: true, updates: ['ack']},
+        {type: 'CommentPatched', finished: true, updates: ['tag']},
+      ]).subscribe(_ => {
+        this.rebuildTagData();
+      });
     }
   }
 
   unbindRoom(): void {
     this._subscriptionAdminData.unsubscribe();
-    if (this._wsCommentSubscription !== null) {
-      this._wsCommentSubscription.unsubscribe();
-      this._wsCommentSubscription = null;
+    if (this._commentSubscription !== null) {
+      this._commentSubscription.unsubscribe();
+      this._commentSubscription = null;
     }
   }
 
@@ -262,7 +270,7 @@ export class TagCloudDataService {
   }
 
   private fetchData(): void {
-    this._commentService.getAckComments(this._roomId).subscribe((comments: Comment[]) => {
+    this._roomDataService.getRoomData(this._roomId).subscribe((comments: Comment[]) => {
       this._lastFetchedComments = comments;
       if (this._isDemoActive) {
         this._lastMetaData.commentCount = comments.length;
@@ -371,67 +379,4 @@ export class TagCloudDataService {
       this.reformatData();
     }
   }
-
-  private onMessage(message: Message): void {
-    const msg = JSON.parse(message.body);
-    const payload = msg.payload;
-    switch (msg.type) {
-      case 'CommentCreated':
-        this._commentService.getComment(payload.id).subscribe(c => {
-          this._lastFetchedComments.push(c);
-          this.rebuildTagData();
-        });
-        break;
-      case 'CommentPatched':
-        for (const comment of this._lastFetchedComments) {
-          if (payload.id === comment.id) {
-            let needRebuild = false;
-            for (const [key, value] of Object.entries(payload.changes)) {
-              switch (key) {
-                case 'score':
-                  comment.score = value as number;
-                  needRebuild = true;
-                  break;
-                case 'upvotes':
-                  comment.upvotes = value as number;
-                  needRebuild = true;
-                  break;
-                case 'downvotes':
-                  comment.downvotes = value as number;
-                  needRebuild = true;
-                  break;
-                case 'keywordsFromSpacy':
-                  comment.keywordsFromSpacy = JSON.parse(value as string);
-                  needRebuild = true;
-                  break;
-                case 'keywordsFromQuestioner':
-                  comment.keywordsFromQuestioner = JSON.parse(value as string);
-                  needRebuild = true;
-                  break;
-                case 'ack':
-                  const isNowAck = value as boolean;
-                  if (!isNowAck) {
-                    this._lastFetchedComments = this._lastFetchedComments.filter((el) => el.id !== payload.id);
-                  }
-                  needRebuild = true;
-                  break;
-                case 'tag':
-                  comment.tag = value as string;
-                  needRebuild = true;
-                  break;
-              }
-            }
-            if (needRebuild) {
-              this.rebuildTagData();
-            }
-            break;
-          }
-        }
-        break;
-      case 'CommentDeleted':
-        this._lastFetchedComments = this._lastFetchedComments.filter((el) => el.id !== payload.id);
-        this.rebuildTagData();
-        break;
-    }
-  }
 }
diff --git a/src/app/services/util/topic-cloud-admin.service.ts b/src/app/services/util/topic-cloud-admin.service.ts
index 297b61f4a..bc650ebd2 100644
--- a/src/app/services/util/topic-cloud-admin.service.ts
+++ b/src/app/services/util/topic-cloud-admin.service.ts
@@ -1,8 +1,11 @@
 import { Injectable } from '@angular/core';
 import * as BadWords from 'naughty-words';
-// eslint-disable-next-line max-len
-import { TopicCloudAdminData, KeywordOrFulltext, Labels, spacyLabels } from '../../components/shared/_dialogs/topic-cloud-administration/TopicCloudAdminData';
-import { RoomService } from './../../services/http/room.service';
+import {
+  TopicCloudAdminData,
+  KeywordOrFulltext,
+  spacyLabels
+} from '../../components/shared/_dialogs/topic-cloud-administration/TopicCloudAdminData';
+import { RoomService } from '../http/room.service';
 import { Room } from '../../models/room';
 import { TranslateService } from '@ngx-translate/core';
 import { NotificationService } from './notification.service';
@@ -12,15 +15,16 @@ import { Observable, Subject } from 'rxjs';
   providedIn: 'root',
 })
 export class TopicCloudAdminService {
+  private static readonly adminKey = 'Topic-Cloud-Admin-Data';
   private adminData: Subject<TopicCloudAdminData>;
   private blacklist: Subject<string[]>;
   private profanityWords = [];
   private customProfanityWords: Subject<string[]>;
   private readonly profanityKey = 'custom-Profanity-List';
-  private readonly adminKey = 'Topic-Cloud-Admin-Data';
+
   constructor(private roomService: RoomService,
-    private translateService: TranslateService,
-    private notificationService: NotificationService) {
+              private translateService: TranslateService,
+              private notificationService: NotificationService) {
     this.blacklist = new Subject<string[]>();
     this.adminData = new Subject<TopicCloudAdminData>();
     this.customProfanityWords = new Subject<string[]>();
@@ -33,11 +37,7 @@ export class TopicCloudAdminService {
       .concat(BadWords['tr']);
   }
 
-  get getAdminData(): Observable<TopicCloudAdminData>{
-    return this.adminData.asObservable();
-  }
-
-  get getDefaultAdminData(): TopicCloudAdminData {
+  static get getDefaultAdminData(): TopicCloudAdminData {
     let data = JSON.parse(localStorage.getItem(this.adminKey));
     if (!data) {
       data = {
@@ -55,14 +55,34 @@ export class TopicCloudAdminService {
     return data;
   }
 
+  static getDefaultSpacyTagsDE(): string[] {
+    const tags: string[] = [];
+    spacyLabels.de.forEach(label => {
+      tags.push(label.tag);
+    });
+    return tags;
+  }
+
+  static getDefaultSpacyTagsEN(): string[] {
+    const tags: string[] = [];
+    spacyLabels.en.forEach(label => {
+      tags.push(label.tag);
+    });
+    return tags;
+  }
+
+  get getAdminData(): Observable<TopicCloudAdminData> {
+    return this.adminData.asObservable();
+  }
+
   setAdminData(_adminData: TopicCloudAdminData) {
-    localStorage.setItem(this.adminKey, JSON.stringify(_adminData));
+    localStorage.setItem(TopicCloudAdminService.adminKey, JSON.stringify(_adminData));
     this.getBlacklist().subscribe(list => {
       _adminData.blacklist = [];
-      if (_adminData.blacklistIsActive){
+      if (_adminData.blacklistIsActive) {
         _adminData.blacklist = list;
       }
-      if (_adminData.profanityFilter){
+      if (_adminData.profanityFilter) {
         _adminData.blacklist = _adminData.blacklist.concat(this.getProfanityListFromStorage().concat(this.profanityWords));
       }
       this.adminData.next(_adminData);
@@ -77,7 +97,7 @@ export class TopicCloudAdminService {
     return this.blacklist.asObservable();
   }
 
-  getProfanityListFromStorage(){
+  getProfanityListFromStorage() {
     const list = localStorage.getItem(this.profanityKey);
     return list ? JSON.parse(list) : [];
   }
@@ -117,7 +137,7 @@ export class TopicCloudAdminService {
     if (word !== undefined) {
       this.getRoom().subscribe(room => {
         const newlist = room.blacklist ? JSON.parse(room.blacklist) : [];
-        if (!newlist.includes(word.toLowerCase().trim())){
+        if (!newlist.includes(word.toLowerCase().trim())) {
           newlist.push(word.toLowerCase().trim());
         }
         this.updateBlacklist(newlist, room, 'add-successful');
@@ -128,7 +148,7 @@ export class TopicCloudAdminService {
   removeWordFromBlacklist(word: string) {
     if (word !== undefined) {
       this.getRoom().subscribe(room => {
-        if (room.blacklist && room.blacklist.length > 0){
+        if (room.blacklist && room.blacklist.length > 0) {
           const newlist = JSON.parse(room.blacklist);
           newlist.splice(newlist.indexOf(word, 0), 1);
           this.updateBlacklist(newlist, room, 'remove-successful');
@@ -144,35 +164,19 @@ export class TopicCloudAdminService {
 
   updateRoom(updatedRoom: Room, message?: string) {
     this.roomService.updateRoom(updatedRoom).subscribe(_ => {
-      if (!message) {
-        message = 'changes-successful';
-      }
-      this.translateService.get('topic-cloud.' + message).subscribe(msg => {
-        this.notificationService.show(msg);
-        this.blacklist.next(JSON.parse(updatedRoom.blacklist));
-      });
-    },
+        if (!message) {
+          message = 'changes-successful';
+        }
+        this.translateService.get('topic-cloud.' + message).subscribe(msg => {
+          this.notificationService.show(msg);
+          this.blacklist.next(JSON.parse(updatedRoom.blacklist));
+        });
+      },
       error => {
         this.translateService.get('topic-cloud.changes-gone-wrong').subscribe(msg => {
           this.notificationService.show(msg);
         });
-    });
-  }
-
-  getDefaultSpacyTagsDE(): string[] {
-    const tags: string[] = [];
-    spacyLabels.de.forEach(label => {
-      tags.push(label.tag);
-    });
-    return tags;
-  }
-
-  getDefaultSpacyTagsEN(): string[] {
-    const tags: string[] = [];
-    spacyLabels.en.forEach(label => {
-      tags.push(label.tag);
-    });
-    return tags;
+      });
   }
 
   filterProfanityWords(str: string): string {
diff --git a/src/app/services/websockets/ws-comment-service.service.spec.ts b/src/app/services/websockets/ws-comment-service.service.spec.ts
deleted file mode 100644
index 02c67cb49..000000000
--- a/src/app/services/websockets/ws-comment-service.service.spec.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-/* import { TestBed } from '@angular/core/testing';
-
-import { WsCommentServiceService } from './ws-comment-service.service';
-
-describe('WsCommentServiceService', () => {
-  beforeEach(() => TestBed.configureTestingModule({}));
-
-  it('should be created', () => {
-    const service: WsCommentServiceService = TestBed.get(WsCommentServiceService);
-    expect(service).toBeTruthy();
-  });
-});
-*/
diff --git a/src/app/services/websockets/ws-comment-service.service.ts b/src/app/services/websockets/ws-comment-service.service.ts
deleted file mode 100644
index e08ea411b..000000000
--- a/src/app/services/websockets/ws-comment-service.service.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { Injectable } from '@angular/core';
-import { Comment } from '../../models/comment';
-import { WsConnectorService } from '../../services/websockets/ws-connector.service';
-import { CreateComment } from '../../models/messages/create-comment';
-import { PatchComment } from '../../models/messages/patch-comment';
-import { HighlightComment } from '../../models/messages/highlight-comment';
-import { TSMap } from 'typescript-map';
-import { UpVote } from '../../models/messages/up-vote';
-import { DownVote } from '../../models/messages/down-vote';
-import { ResetVote } from '../../models/messages/reset-vote';
-import { Observable } from 'rxjs';
-import { IMessage } from '@stomp/stompjs';
-
-
-@Injectable({
-  providedIn: 'root'
-})
-export class WsCommentServiceService {
-
-  constructor(private wsConnector: WsConnectorService) { }
-
-  getCommentStream(roomId: string): Observable<IMessage> {
-    return this.wsConnector.getWatcher(`/topic/${roomId}.comment.stream`);
-  }
-
-  getModeratorCommentStream(roomId: string): Observable<IMessage> {
-    return this.wsConnector.getWatcher(`/topic/${roomId}.comment.moderator.stream`);
-  }
-}
diff --git a/src/app/services/websockets/ws-comment.service.spec.ts b/src/app/services/websockets/ws-comment.service.spec.ts
new file mode 100644
index 000000000..5f3e9314b
--- /dev/null
+++ b/src/app/services/websockets/ws-comment.service.spec.ts
@@ -0,0 +1,13 @@
+/*import { TestBed } from '@angular/core/testing';
+
+import { WsCommentService } from './ws-comment.service';
+
+describe('WsCommentService', () => {
+  beforeEach(() => TestBed.configureTestingModule({}));
+
+  it('should be created', () => {
+    const service: WsCommentService = TestBed.get(WsCommentService);
+    expect(service).toBeTruthy();
+  });
+});
+*/
diff --git a/src/app/services/websockets/ws-comment.service.ts b/src/app/services/websockets/ws-comment.service.ts
new file mode 100644
index 000000000..60ccb90e7
--- /dev/null
+++ b/src/app/services/websockets/ws-comment.service.ts
@@ -0,0 +1,21 @@
+import { Injectable } from '@angular/core';
+import { WsConnectorService } from './ws-connector.service';
+import { Observable } from 'rxjs';
+import { IMessage } from '@stomp/stompjs';
+
+
+@Injectable({
+  providedIn: 'root'
+})
+export class WsCommentService {
+
+  constructor(private wsConnector: WsConnectorService) { }
+
+  getCommentStream(roomId: string): Observable<IMessage> {
+    return this.wsConnector.getWatcher(`/topic/${roomId}.comment.stream`);
+  }
+
+  getModeratorCommentStream(roomId: string): Observable<IMessage> {
+    return this.wsConnector.getWatcher(`/topic/${roomId}.comment.moderator.stream`);
+  }
+}
-- 
GitLab