Joba
Joba

Reputation: 898

Angular 4 Service method returns always undefined instead Observable

I wrote an angular service which connects with socket.io to my backend. I get the server data in the _getStripes() method correctly and transform them with from() into an Observable and set the class variable _LEDs with the recent created observable.

The public method getStripes() is called and subscribed in another component and returns everytime undefined.

led.service.ts:

import {Injectable} from '@angular/core';
import * as io from 'socket.io-client';
import {LED} from './led';
import {Observable} from 'rxjs/Observable';
import {from} from 'rxjs/observable/from';

@Injectable()
export class LedService {
  private _io;
  private _LEDs: Observable<LED>;

  constructor() {
    this._io = io('http://localhost:80');
    // Subscribe getStripes Event
    this._io.on('getStripes', this._getStripes);

    // Initial Request Stripes from Server
    this._io.emit('getStripes');
  }

  /**
   * Server Response
   * @param {LED[]} data
   * @private
   */
  private _getStripes(data: LED[]) {
    console.log('Got Stripes: ', data); // works
    this._LEDs = from(data);
  }

  /**
   * Get the current connected LEDStripes
   * @returns {Observable<LED>}
   */
  public getStripes(): Observable<LED> {
    // request latest Stripes
    this._io.emit('getStripes');
    return this._LEDs; // always undefined
  }

  public setStripeColor(name: string, color: string) {
    this._io.emit('setStripeColor', name, color);
  }

}

Upvotes: 2

Views: 858

Answers (1)

Richard Matsen
Richard Matsen

Reputation: 23463

You need to separate the subscription from the emit.

Subscription sets up the pipeline to receive incoming data, but emit sends data. It looks like it's request/response style, since the event and emit params are the same string, but in any case there is a time delay between the two, so the pipeline needs to be established prior.

Try using a Subject for this._LEDS.

In the service

import {Injectable} from '@angular/core';
...
import {Subject} from 'rxjs/Subject';

@Injectable()
export class LedService {
  private _io;
  private _LEDs = new Subject<LED>();
  public LEDs = this._LEDs.asObservable();  // Subscribe to this

  constructor() {
    ...
    // Subscribe getStripes Event
    this._io.on('getStripes', this._getStripes.bind(this) );
    ...
  }

  /**
   * Server Response
   * @param {LED[]} data
   * @private
   */
  private _getStripes(data: LED[]) {
    data.forEach(led => this._LEDs.next(led));
    // OR send the whole array as a single emit if it suits your application better
    //    this._LEDs.next(data);
    // in which case the declaration above would be
    //    private _LEDs = new Subject<LED[]>();
  }

  /**
   * Get the current connected LEDStripes
   * @returns void
   */
  public getStripes(): void {
    // request latest Stripes
    this._io.emit('getStripes');
  }

In the component

ngOnInit() {
  // subscribe to the LEDs
  this.sub = this.ledService.LEDS.subscribe(leds => {
    // do something with data
  }) 
  // or use the subscription directly in the template
  // with <div> {{ (ledService.LEDS | async) }} </div>
}

getFresh() {
  this.ledService.getStripes();
}

Upvotes: 1

Related Questions