web-dev-qa-db-fra.com

RxJs observable avec WebSocket

Mon angular utilise une prise Web pour communiquer avec le backend.

Dans mon cas de test, j'ai 2 composants clients. Le temporisateur observable imprime deux ID client différents comme prévu.

Chaque ngOnInit () affiche également l'ID de son client.

MAINTENANT pour une raison quelconque, l'abonnement de websocketService.observeClient () est appelé 2 fois pour chaque message, mais this.client.id imprime toujours la valeur du deuxième client.

Voici ma composante client

@Component({
...
})
export class ClientComponent implements OnInit {

  @Input() client: Client;

  constructor(public websocketService: WebsocketService) {
    Observable.timer(1000, 1000).subscribe(() => console.log(this.client.id));
  }

  ngOnInit() {

    console.log(this.client.id);
    this.websocketService.observeClient().subscribe(data => {
      console.log('message', this.client.id);
    });

  }

}

Et mon service websocket

@Injectable()
export class WebsocketService {

  private observable: Observable<MessageEvent>;
  private observer: Subject<Message>;

  constructor() {

    const socket = new WebSocket('ws://localhost:9091');

    this.observable = Observable.create(
      (observer: Observer<MessageEvent>) => {
        socket.onmessage = observer.next.bind(observer);
        socket.onerror = observer.error.bind(observer);
        socket.onclose = observer.complete.bind(observer);
        return socket.close.bind(socket);
      }
    );

    this.observer = Subject.create({
      next: (data: Message) => {
        if (socket.readyState === WebSocket.OPEN) {
          socket.send(JSON.stringify(data));
        }
      }
    });

  }

  observeClient(): Observable<MessageEvent> {
    return this.observable;
  }

}

Modifier

D'accord, pour autant que j'ai lu, cela a à voir avec le fait que les observables sont des objets unicast et je dois utiliser un sujet pour cela, mais je ne sais pas comment créer le sujet.

11
Pascal

Depuis rxjs 5, vous pouvez utiliser la fonction websocket intégrée qui crée le sujet pour vous. Il se reconnecte également lorsque vous vous réabonnez au flux après une erreur. Veuillez vous référer à cette réponse:

https://stackoverflow.com/a/44067972/5522

TLDR:

 let subject = Observable.webSocket('ws://localhost:8081');
 subject
   .retry()
   .subscribe(
      (msg) => console.log('message received: ' + msg),
      (err) => console.log(err),
      () => console.log('complete')
    );
 subject.next(JSON.stringify({ op: 'hello' }));
20
Herman

Eh bien, je souffre avec les websockets avec angular et python depuis longtemps. J'avais des websockets pour chaque composant et ils ne se fermaient pas, donc tous les chaque fois que je passe d'un onglet à un autre (en changeant entre les composants), le websocket a été recréé à l'arrière et je recevais deux ou plusieurs fois le même.

Il existe de nombreux tutoriels expliquant comment faire des websockets, mais pas beaucoup expliquant comment fermer ou comment en avoir plusieurs.

Voici mon morceau de pain: le meilleur tuto que j'aie trouvé jusqu'ici: https://medium.com/@lwojciechowski/websockets-with-angular2-and-rxjs-8b6c5be02fac Mais pas encore terminé. Il ne ferme pas correctement le WS

Voici ce que j'ai fait: (Ma classe WebSocketTestService)

@Injectable()
export class WebSocketTestService {
    public messages: Subject<any>  = new Subject<any>();

    constructor(private wsService: WebSocketService) {
        console.log('constructyor ws synop service')
    }

    public connect() {
        this.messages = <Subject<any>>this.wsService
            .connect('ws://localhost:8000/mytestwsid')
            .map((response: any): any => {
                return JSON.parse(response.data);
            })
    }

    public close() {
        this.wsService.close()
    }
} // end class 

Et ici (dans un autre fichier pour moi) la classe websocket

import {Injectable} from '@angular/core';
import {Observable} from 'rxjs/Observable';
import {Subject} from 'rxjs/Subject';
import {Observer} from "rxjs/Observer";

@Injectable()
export class WebSocketService {
    private subject: Subject<MessageEvent>;
    private subjectData: Subject<number>;
  private ws: any;
    // For chat box
    public connect(url: string): Subject<MessageEvent> {
        if (!this.subject) {
            this.subject = this.create(url);
        }
        return this.subject;
    }

    private create(url: string): Subject<MessageEvent> {
        this.ws = new WebSocket(url);

        let observable = Observable.create(
            (obs: Observer<MessageEvent>) => {
                this.ws.onmessage = obs.next.bind(obs);
                this.ws.onerror   = obs.error.bind(obs);
                this.ws.onclose   = obs.complete.bind(obs);

                return this.ws.close.bind(this.ws);
            });

        let observer = {
            next: (data: Object) => {
                if (this.ws.readyState === WebSocket.OPEN) {
                    this.ws.send(JSON.stringify(data));
                }
            }
        };

        return Subject.create(observer, observable);
  }

  public close() {
    console.log('on closing WS');
    this.ws.close()
    this.subject = null
  }

} // end class WebSocketService

Et enfin, mon appel au WSTestService dans le composant

this.webSocketTestService.connect();
this.webSocketTestService.messages.subscribe(message => {
      console.log(message);

})

et dans la partie la plus importante, le ngOnDestroy ()

ngOnDestroy() {
    this.webSocketTestService.messages.unsubscribe();
    this.webSocketTestService.close()

Bien sûr, ajoutez les deux services à la section des fournisseurs dans le module app.module

C'est le seul moyen que j'ai trouvé pour fermer correctement la prise Web lorsque je passe d'une vue à l'autre et que je détruis des composants. Je ne sais pas vraiment si c'est votre cas, mais il m'est difficile de trouver un exemple qui fonctionne correctement avec plusieurs vues et prises Web. J'ai eu des problèmes similaires à ceux que vous avez demandés, alors j'espère que cela fonctionne pour vous. Faites-moi savoir si mon approche fonctionne pour vous.

Ce que je ne comprends vraiment pas, c'est pourquoi Angular appelle tous les ngOnInit de chaque composant même s'ils ne sont pas affichés lorsque je démarre l'application. Je ne veux créer le WS que lorsque j'entre dans la vue , pas lorsque je lance l'application. Si vous trouvez une solution, faites-le moi savoir.

bonne chance!

3
Javier

Vous devez utiliser l'opérateur share pour le partager entre les abonnés.

this.observable = Observable.create(
    (observer: Observer<MessageEvent>) => {
       socket.onmessage = observer.next.bind(observer);
       socket.onerror = observer.error.bind(observer);
       socket.onclose = observer.complete.bind(observer);
       return socket.close.bind(socket);
    }
).share();

Assurez-vous également que ce service est un singleton.

Doc: https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/share.md

1
eko