What Is The Reason to Store Subscription into a Subscriptions Set

What is the reason to store subscription into a subscriptions set?

We usually want to store the subscription somewhere, to keep the subscription alive. We often want to keep several subscriptions alive until the enclosing object is destroyed, so it's convenient to store all the subscriptions in a single container.

However, the container does not have to be a Set! It can be (and usually should be) an Array.

Cancellable provides two store(in:) methods:

extension Cancellable {
public func store<C>(in collection: inout C) where C : RangeReplaceableCollection, C.Element == AnyCancellable

public func store(in set: inout Set<AnyCancellable>)
}

(Array conforms to RangeReplaceableCollection, but Set does not, so it needs its own method.)

You have found the one that stores into a Set. But do you need the behavior of a Set? The only reason to store your subscriptions in a Set is if you need to efficiently remove a single subscription from the set, and the set may be large. Otherwise, just use an Array, like this:

class MyObject {

private var tickets = [AnyCancellable]()

...
future
.sink(receiveCompletion: { print($0) }, receiveValue: { print($0) })
.store(in: &tickets)

I think the reason you often see code that uses a Set<AnyCancellable> is because most Swift programmers are much more familiar with Set and Array than with RangeReplaceableCollection. When Xcode offers to autocomplete sink to take either a Set or a RangeReplaceableCollection, you're going to pick the Set version if you don't know that an Array is a RangeReplaceableCollection.

Is it good to call subscribe inside subscribe?

The correct way is to compose the various observables in some manner then subscribe to the overall flow — how you compose them will depend on your exact requirements.

If you can do them all in parallel:

forkJoin(
this.service.service1(), this.service.service2(), this.service.service3()
).subscribe((res) => {
this.funcA(res[0], res[1], res[2]);
});

If each depends on the result of the previous, you can use mergeMap (formerly known as flatMap) or switchMap:

this.service.service1().pipe(
mergeMap((res1) => this.service.service2(res1)),
mergeMap((res2) => this.service.service3(res2))
).subscribe((res3) => {
// Do something with res3.
});

... and so on. There are many operators to compose observables to cover lots of different scenarios.

Angular/RxJS When should I unsubscribe from `Subscription`

TL;DR

For this question there are two kinds of Observables - finite value and infinite value.

http Observables produce finite (1) values and something like a DOM event listener Observable produces infinite values.

If you manually call subscribe (not using async pipe), then unsubscribe from infinite Observables.

Don't worry about finite ones, RxJs will take care of them.


Sources:

  1. I tracked down an answer from Rob Wormald in Angular's Gitter here.

    He states (I reorganized for clarity and emphasis is mine):

    if its a single-value-sequence (like an http request)
    the manual cleanup is unnecessary (assuming you subscribe in the controller manually)

    i should say "if its a sequence that completes" (of which single value sequences, a la http, are one)

    if its an infinite sequence, you should unsubscribe which the async pipe does for you

    Also he mentions in this YouTube video on Observables that "they clean up after themselves..." in the context of Observables that complete (like Promises, which always complete because they are always producing one value and ending - we never worried about unsubscribing from Promises to make sure they clean up XHR event listeners, right?)

  2. Also in the Rangle guide to Angular 2 it reads

    In most cases we will not need to explicitly call the unsubscribe method unless we want to cancel early or our Observable has a longer lifespan than our subscription. The default behavior of Observable operators is to dispose of the subscription as soon as .complete() or .error() messages are published. Keep in mind that RxJS was designed to be used in a "fire and forget" fashion most of the time.

    When does the phrase "our Observable has a longer lifespan than our subscription" apply?

    It applies when a subscription is created inside a component which is destroyed before (or not 'long' before) the Observable completes.

    I read this as meaning if we subscribe to an http request or an Observable that emits 10 values and our component is destroyed before that http request returns or the 10 values have been emitted, we are still OK!

    When the request does return or the 10th value is finally emitted the Observable will complete and all resources will be cleaned up.

  3. If we look at this example from the same Rangle guide we can see that the subscription to route.params does require an unsubscribe() because we don't know when those params will stop changing (emitting new values).

    The component could be destroyed by navigating away in which case the route params will likely still be changing (they could technically change until the app ends) and the resources allocated in subscription would still be allocated because there hasn't been a completion.

  4. In this video from NgEurope Rob Wormald also says you do not need to unsubscribe from Router Observables. He also mentions the http service and ActivatedRoute.params in this video from November 2016.

  5. The Angular tutorial, the Routing chapter now states the following:

    The Router manages the observables it provides and localizes the subscriptions. The subscriptions are cleaned up when the component is destroyed, protecting against memory leaks, so we don't need to unsubscribe from the route params Observable.

    Here's a discussion on the GitHub Issues for the Angular docs regarding Router Observables where Ward Bell mentions that clarification for all of this is in the works.


I spoke with Ward Bell about this question at NGConf (I even showed him this answer which he said was correct) but he told me the docs team for Angular had a solution to this question that is unpublished (though they are working on getting it approved). He also told me I could update my SO answer with the forthcoming official recommendation.

The solution we should all use going forward is to add a private ngUnsubscribe = new Subject<void>(); field to all components that have .subscribe() calls to Observables within their class code.

We then call this.ngUnsubscribe.next(); this.ngUnsubscribe.complete(); in our ngOnDestroy() methods.

The secret sauce (as noted already by @metamaker) is to call takeUntil(this.ngUnsubscribe) before each of our .subscribe() calls which will guarantee all subscriptions will be cleaned up when the component is destroyed.

Example:

import { Component, OnDestroy, OnInit } from '@angular/core';
// RxJs 6.x+ import paths
import { filter, startWith, takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';
import { BookService } from '../books.service';

@Component({
selector: 'app-books',
templateUrl: './books.component.html'
})
export class BooksComponent implements OnDestroy, OnInit {
private ngUnsubscribe = new Subject<void>();

constructor(private booksService: BookService) { }

ngOnInit() {
this.booksService.getBooks()
.pipe(
startWith([]),
filter(books => books.length > 0),
takeUntil(this.ngUnsubscribe)
)
.subscribe(books => console.log(books));

this.booksService.getArchivedBooks()
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe(archivedBooks => console.log(archivedBooks));
}

ngOnDestroy() {
this.ngUnsubscribe.next();
this.ngUnsubscribe.complete();
}
}

Note: It's important to add the takeUntil operator as the last one to prevent leaks with intermediate Observables in the operator chain.


More recently, in an episode of Adventures in Angular Ben Lesh and Ward Bell discuss the issues around how/when to unsubscribe in a component. The discussion starts at about 1:05:30.

Ward mentions "right now there's an awful takeUntil dance that takes a lot of machinery" and Shai Reznik mentions "Angular handles some of the subscriptions like http and routing".

In response Ben mentions that there are discussions right now to allow Observables to hook into the Angular component lifecycle events and Ward suggests an Observable of lifecycle events that a component could subscribe to as a way of knowing when to complete Observables maintained as component internal state.

That said, we mostly need solutions now so here are some other resources.

  1. A recommendation for the takeUntil() pattern from RxJs core team member Nicholas Jamieson and a TSLint rule to help enforce it: https://ncjamieson.com/avoiding-takeuntil-leaks/

  2. Lightweight npm package that exposes an Observable operator that takes a component instance (this) as a parameter and automatically unsubscribes during ngOnDestroy: https://github.com/NetanelBasal/ngx-take-until-destroy

  3. Another variation of the above with slightly better ergonomics if you are not doing AOT builds (but we should all be doing AOT now): https://github.com/smnbbrv/ngx-rx-collector

  4. Custom directive *ngSubscribe that works like async pipe but creates an embedded view in your template so you can refer to the 'unwrapped' value throughout your template: https://netbasal.com/diy-subscription-handling-directive-in-angular-c8f6e762697f

I mention in a comment to Nicholas' blog that over-use of takeUntil() could be a sign that your component is trying to do too much and that separating your existing components into Feature and Presentational components should be considered. You can then | async the Observable from the Feature component into an Input of the Presentational component, which means no subscriptions are necessary anywhere. Read more about this approach here.

why should we use subscribe() over map() in Angular?

If you want to return an Observable some other code can subscribe to, but you still want to manipulate the data events in the current method, use map.

The actual user of the observable needs to subscribe(), because without subscribe() the observable won't be executed at all. (forEach() or toArray() and probably others work as well to execute the observable instead of subscribe())

subscribe() returns a Subscription that can not be subscribed to, but it can be used to cancel the subscription.

map() returns an Observable which can be subscribed to.

Subscription to promise

subscribe changes the type from Observable to Subscription, thus causing the type error.

What you probably want is to convert your Observable to a Promise, while preserving the function call. You can do this, by piping the Observable through tap and then converting the result with toPromise. Like this:

getUserData(uid) {
return this.fireStore.collection('users').doc(uid).valueChanges().pipe(
tap(data => {
this.writeCookie(data)
this.currentUser = data;
}),
first()
).toPromise()
}

Make sure to create a completing pipe, like you can do with the first operator, otherwise the Promise will never resolve.

You can leave out new Promise(...) in your consumer.

tap() vs subscribe() to set a class property

Edit: Ignore this answer!

Here is a good answer: https://stackoverflow.com/a/50882183/5932590

See JMD's comment below for more context!


Good question. In the source code for the tap operator, this comment pretty much sums it up:

This operator is useful for debugging your Observables for the correct values
or performing other side effects.

Note: this is different to a subscribe on the Observable. If the Observable returned by do is not subscribed, the side effects specified by the Observer will never happen. do therefore simply spies on existing execution, it does not trigger an execution to happen like subscribe does.

Any side effect you can run in a tap can probably also be put in the subscribe block. The subscribe indicates your intent to actively use the source value since it's saying "when this observable emits, I want to save it's value in the applicants variable". The tap operator is mostly there for debugging, but it can be used to run side effects.

In general, favor the subscribe block for running side effects, use tap for debugging, but be aware that tap can do more if you need it to.



Related Topics



Leave a reply



Submit