21

This is mainly an RxJs best practice/approach question, since my POC code works but I'm brand new to RxJs.

The question boils down to .subscribe() vs .publish().connect(), since they both appear to do the same thing.

In my angular2 app, I have a button that calls a function to log the user out, which calls a function in my service that performs some server side actions and returns me a URL to redirect the user to. In order to initiate the request I call .subscribe() to cause the observable to start producing values. I was reading an article on "Cold vs Hot Observables" and it another approach would be to call .publish().connect() instead of .subscribe(). Is there any benefit to either approach.

<a (click)="logout()">Logout</a>

The logout function looks like this:

logout.component.ts

logout() { this.authService.logout(); }

And the service (actual logout) looks like this:

auth.service.ts

logout() : Observable<boolean>  {
        this.http.get(this.location.prepareExternalUrl('api/v1/authentication/logout'))
            .map(this.extractData)
            .catch(this.handleError)
            .do((x: string) => { window.location.href = x; })
            .subscribe();    // Option A - 

        return Observable.of(true);

    }

auth.service.alternative.ts

logout() : Observable<boolean>  {
        this.http.get(this.location.prepareExternalUrl('api/v1/authentication/logout'))
            .map(this.extractData)
            .catch(this.handleError)
            .do((x: string) => { window.location.href = x; })
            .publish()  // Option B - Make connectable observable
            .connect(); // Option B - Cause the connectable observable to subscribe and produce my value       

        return Observable.of(true);
    }
martin
  • 93,354
  • 25
  • 191
  • 226
ClaytonK
  • 417
  • 1
  • 5
  • 12

2 Answers2

30

The difference between subscribe() and .publish().connect() is in when they subscribe to its source Observable. Consider the following Observable:

let source = Observable.from([1, 2, 3])

This Observable emits all values to an Observer right when it subscribes. So if I have two Observers then they receive all values in order:

source.subscribe(val => console.log('obs1', val));
source.subscribe(val => console.log('obs2', val));

This will print to console:

obs1 1
obs1 2
obs1 3
obs2 1
obs2 2
obs2 3

On the other hand calling .publish() returns a ConnectableObservable. This Observable doesn't subscribe to it's source (source in our example) in its constructor and only keeps its reference. Then you can subscribe multiple Observers to it and nothing happens. Finally, you call connect() and the ConnectableObservable subscribes to the source which starts emitting values. This time there're already two Observers subscribes so it emits values to both of them one by one:

let connectable = source.publish();
connectable.subscribe(val => console.log('obs1', val));
connectable.subscribe(val => console.log('obs2', val));
connectable.connect();

Which prints to console:

obs1 1
obs2 1
obs1 2
obs2 2
obs1 3
obs2 3

See live demo: http://plnkr.co/edit/ySWocRr99m1WXwsOGfjS?p=preview

martin
  • 93,354
  • 25
  • 191
  • 226
  • So in my case, where I just want the observable to basically execute immediately, calling `.subscribe()` with no arguments is probably the most succinct way of performing this and `.publish().connect()` accomplishes this in a roundabout way. – ClaytonK Oct 21 '16 at 17:31
  • 1
    @ClaytonK Generally yes, most of the time `subscribe()` is sufficient. – martin Oct 21 '16 at 17:33
  • Why does the output order change ? both samples shows that `obs2` was the second to subscribve ? Is it like `.share()` ? – Royi Namir Apr 20 '17 at 20:33
  • @Royi That's because of how the `Observable.from` works internally. When you subscribe it starts emitting all its values immediately. This means that without using any scheduler it'll emit all its values (array items) to the observer that subscribed right now. Then again to the second observer. With `ConnectableObservable` and `connect()` we first subscribe both observers and after that subscribe to the source `Observable.from`. It means that it'll iterate the array just once and emits each value to both observers at the same time. – martin Apr 20 '17 at 20:42
  • so `publish` effectively returns a `Subject`, correct? and how is it different from `multicast` operator? – Max Koretskyi Jul 20 '17 at 07:47
  • The `publish` returns an instance of `ConnectableObservable` while `multicast` returns just `Observable`. – martin Jul 20 '17 at 09:28
5

This sidesteps your question a bit but you may find it helpful:

I would not return a different observable stream from the one that calls the http service because doing so makes it impossible for the calling function to:

  • cancel the stream
  • modify the stream
  • determine whether the operation was successful

Instead I'd do:

auth.servive.ts

logout() : Observable<string>  {
       return this.http.get(...).map(this.extractData)          
            .catch(this.handleError);
}

Now the calling code can do whatever it wants with the resulting url

logout.component.ts

logout(){
    this.authService.logout().subscribe(
        url => window.location.href = url,
        err => {
            /*todo: handle if error was thrown by authService.handleError*/
        }
    );
}
Harry Ninh
  • 16,288
  • 5
  • 60
  • 54
BeetleJuice
  • 39,516
  • 19
  • 105
  • 165