RxJS Observable: Subscription lost? RxJS Observable: Subscription lost? angular angular

RxJS Observable: Subscription lost?


You should never use a getter like that. Do not return an Observable from a getter.

Angular will unsubscribe/subscribe again and again, everytime a change detection cycle happens (which happens a lot).

For now on I'll write "CD" for "change detection"

Simple demo of that:

Take a really simple component:

// only here to mock a part of the storeconst _obsSubject$ = new BehaviorSubject('name 1');@Component({  selector: 'my-app',  templateUrl: './app.component.html',  styleUrls: ['./app.component.css']})export class AppComponent {  get obs$() {    return _obsSubject$      .asObservable()      .pipe(tap(x => console.log('getting a new value')));  }  randomFunction() {    // we don't care about that, it's just    // to trigger CD from the HTML template  }}

You'll see getting a new value in your console, and every time you click on the button "Click to trigger change detection", which has a (click) event registered, it'll trigger a new CD cycle.

And, as many times as you click on that button, you'll see that you are getting twice getting a new value.(twice is because we're not in production mode and Angular performs 2 CD cycles to make sure the variable has not changed between the first and the second change detection, which might lead to problems but that's another story).

The point of an observable is that it can remains open for a long time and you should take advantage of that.In order to refactor the previous code to keep the subscription opened and avoid unsubscribing/subscribing again, we can just get rid of the getter and declare a public variable (accessible by the template):

// only here to mock a part of the storeconst _obsSubject$ = new BehaviorSubject('name 1');@Component({  selector: 'my-app',  templateUrl: './app.component.html',  styleUrls: ['./app.component.css']})export class AppComponent {  obs$ = _obsSubject$    .asObservable()    .pipe(tap(x => console.log('getting a new value')));  randomFunction() {    // we don't care about that, it's just    // to trigger CD from the HTML template  }}

And now, no matter how many times you click on the button, you'll see one and only one getting a new value (until the observable emits a new value of course), but the change detection will not trigger a new subscription.

Here's a live demo on Stackblitz so you can play around and see the console.log happening =)https://stackblitz.com/edit/angular-e42ilu

EDIT:A getter is a function, and thus, Angular has to call it on every CD to check if there's a new value coming from it that should be updated in the view. This costs a lot, but it's the principle and the "magic" of the framework. That's also why you should avoid running intensive CPU tasks in function that might be triggered on every CD. If it's a pure function (same input same output AND no side effects), use a pipe because they're considered "pure" by default and cache the results. For the same arguments they will run the function in the pipe only once, cache the result and then just return the result instantly without running the function again.


The Observable returned from ngrx.select() will only fire when the data in the store has changed.

If you want the Observable to fire when initialName changes, then I would recommend converting initialName into an RXJS Subject and using combineLatest:

initialNameSubject = new BehaviorSubject<string>('some-name');constructor(  @Inject(TablesStoreInjectionToken) readonly store: Store<TablesState>) {  setTimeout(() => {    this.store      .select(this.tableName, 'columns')      .combineLatest(this.initialNameSubject)      .map(([items, initialName]) => items.find(_ => _.name === initialName))      .filter(_ => !!_)      .map(_ => {        console.log('B', _.name)        return _.name      })      .subscribe(_ => console.log('B!', _))  })}