Angular 2 share websocket service across components
Asked Answered
D

2

7

I am building a web application using angular 2 in which I want to have multiple components listening to the same service. This service returns an observable that returns incoming data from a websocket. I wrote the code based on this example.

The current problem is: The data is send from the home component through the service to the server (using websockets) and data is returned. However, only the observer in the home.component is getting called (with id: room.created and data), not the one in the navbar.

Could someone tell me why not both are called? I also tried to add the messages$.subscribe to the app.component but to no avail.

Now, let's get to the code.

A message service that returns an observable. This service is used by the components to send and receive messages from.

@Injectable()
export class MessageService {
    private _messages: Rx.Subject<Message>;
    messages$: Rx.Observable<Message>;

    constructor(wsService: SocketService, private configuration: Configuration) {
      console.log('messag eservice');
      this._messages = <Rx.Subject<Message>>wsService
        .connect()
        .map((response: MessageEvent): Message => {
            let data = JSON.parse(response.data);
            return {
                id: data.id,
                data: data.data,
            }
        });

      this.messages$ = this._messages.asObservable();
    }

    public send(message: Message): void {
      this._messages.next(message);
    }
} 

A socket service which creates a websocket connection and binds itself to input and output of this socket.

import { Injectable } from '@angular/core';
import * as Rx from "rxjs/Rx";
import { Configuration } from '../app.constants';

@Injectable()
export class SocketService {
    private subject: Rx.Subject<MessageEvent>;

    constructor(private configuration: Configuration){};

    public connect(wsNamespace = ''): Rx.Subject<MessageEvent> {
        var url = this.configuration.wsUrl + wsNamespace;
        if(!this.subject) {
            this.subject = this.create(url);
        }
        return this.subject;
    }

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

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

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

        // on obs next (send something in the stream) send it using ws.
        let observer = {
            next: (data: Object) => {
                if (ws.readyState === WebSocket.OPEN) {
                    ws.send(JSON.stringify(data));
                }
            },
        };

        return Rx.Subject.create(observer, observable);
    }
}

An app component with the following providers:

  providers: [MessageService, SocketService, Configuration, AuthService]

I am instantiating the providers in my main app.component to make sure that the messages and socket services are not instantiated twice.

My home.component looks like this (this is a page being loaded using routing):

import { Component, OnInit } from '@angular/core';
import { Subject } from 'rxjs';
import { Router }    from '@angular/router';
import { MessageService } from '../../services/message.service';

@Component({
  ...
  providers: []
})
export class HomeComponent implements OnInit {
  constructor(private router: Router, private messageService: MessageService) {}

  ngOnInit(): void {
    this.messageService.send({
      id: 'room.create',
      data: {'name': 'Blaat'}
    });

    this.messageService.messages$.subscribe(msg => {
      console.log(msg);
        if(msg.id == 'room.created') {
            // navigate naar games!
        }
    });
  }

}

My navbar component looks like this (directive):

import { Component, OnInit } from '@angular/core';
import { MessageService } from '../../services/message.service';

@Component({
  moduleId: module.id,
  selector: 'navbar',
  templateUrl: 'navbar.component.html',
  styleUrls: ['navbar.component.css']
})
export class Navbar implements OnInit {

  constructor(private messageService: MessageService) { }

  ngOnInit() {

    this.messageService.messages$.subscribe(msg => {
      console.log(msg);
        if(msg.id == 'room.created') {
            // navigate naar games!
        }
    });
  }

}
Dipteral answered 27/8, 2016 at 17:19 Comment(0)
P
10

It seems that your observable create function is called multiple times, most probably two components => two subscriptions => two observable create function invocations. So the latest observable create fn overrides previous observable callbacks to websocket onmessage, onerror and onclose. You should multicast the underlying observable to prevent that (share operator should do the trick).

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

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

More useful resources of how to do this properly https://github.com/ReactiveX/rxjs/blob/master/src/observable/dom/WebSocketSubject.ts https://github.com/blesh/RxSocketSubject

Peeper answered 27/8, 2016 at 22:7 Comment(2)
Thank you very much! Adding the share method to my observable fixed the problem. That small method did cost me about three hours :). I am new to observables, do you think that this setup is appropriate for creating a socket connection in a service, especially when I want to separate my socket connection from the rest of my service logic (messages service and socket service).Dipteral
I'm glad I could help. I doubt I could help you more on this question, but I updated my answer and added few helpful resources of how this could be implemented or which packages could be used.Peeper
G
0

If any one wants to send an encrypted request on angular using service and fetch the encrypted response then decrypt it to use in the template here is the below code

mycomponent.ts file :::

import { Component, TemplateRef, OnInit, ViewChild } from '@angular/core';
import { myService } from 'src/app/my.service';
import { Base64 } from 'js-base64';
import { SelectItem } from 'primeng/api';
import { ActivatedRoute, Router } from '@angular/router';


getDataFromService() {
    this._myService.getData("api_name/url")
      .subscribe(data => {
        let myResp = Base64.decode(this.myData = data);
        this.myResponse = JSON.parse(myResp );
}

myservice.ts file :::

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Iwatchlist } from './watchlist';
import { Base64 } from 'js-base64';

private _url: string = "http://192.168.10.10:1000";

getData(svcName): Observable<any> {
  let baseUrl= this._url + "/"+svcName;

    const httpOptions = {
     headers: { 'Content-Type': 'application/json' },
      "request": {
        "FormFactor": "M",
        "svcGroup": "portfolio",
        "svcVersion": "1.0.0",
        "svcName": svcName,
        "requestType": "U",
        "data": {
          "gcid": "123",
          "gscid": "abc123"
        }
      }
    };
  
    let encryptedData = Base64.encode(JSON.stringify(httpOptions));
    let postRequest = this.http.post(baseUrl, encryptedData, {responseType: 'text'});
    return postRequest;
  }
Griego answered 24/11, 2020 at 10:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.