Persistent subscriptions with rxjs in Angular 5

筅森魡賤 提交于 2019-12-23 22:09:39

问题


I'm still a bit new to rxjs in Angular 5 and it a bit hard to formulate my question. Still I hope for some tips.

I often end up with the same setup:

  • Multiple Components to display the same data
  • A single Service to access the data

Now I have 2 options when receiving data via Observables:

a) Subscribe to an observable to get data once, and subscribe again to get updates

b) Subscribe to an observable and always get updates when data changes

a) ist straight forward, but with b) I often have troubles and wondering if this is the right way to use Observables.

One issue is, that unsubscribe gets important in some cases, and missing the unsubscribe leads to serious garbage being executed with each update of the observable.

On the other hand, with option a) I might miss some updates in one component when another component is updating the underlying data.

Is there any best practice to avoid all these pitfalls?


回答1:


It sounds like the concept you are trying to figure out is how to regulate subscription management for RxJS when using Angular. There are three main options that come to mind for this:

  1. Automatically create and delete subscriptions using the async pipe. If you want to make UI changes based strictly on data emitted from an observable, then the async pipe handily creates a subscription to the given observable when the component is created and removes those subscription when the component is destroyed. This is arguably the cleanest way to use subscriptions.

As an example:

@Component({
    selector: 'my-component',
    template: `
        <div *ngFor="let value of value$ | async">
            {{value}}
        </div>
    `
})
export class MyComponent {
    public value$: Observable<String> = this.myValueService
        .getValues()
        .map(value => `Value: $value`);
    constructor(myValueService: MyValueService) {}
}
  1. Manage subscriptions in components by creating class-level Subscription objects in the ngOnInit method and then unsubscribing in the ngOnDestroy method. This is the convention that I tend toward when I need access to the subscriptions within the component code. Having ngOnInit and ngOnDestroy methods in every component that uses subscriptions adds boilerplate but is generally necessary if you need subscriptions in your component code.

For example:

@Component({
    selector: 'my-component',
    template: `
        <div #myDiv></div>
    `
})
export class MyComponent implements OnInit, OnDestroy {
    private mySub: Subscription;
    constructor(myValueService: MyValueService) {}

    public ngOnInit() {
        this.mySub = this.myValueService.getValue().subscribe((value) => {
            console.log(value);
            // Do something with value
        });
    }
    public ngOnDestroy() {
        this.mySub.unsubscribe();
    }
}
  1. Limit subscription life by using a limiting operation, such as first(). This is what is done by default when you initiate a subscription to HttpClient observables. This has the benefit of requiring little code, but it can also lead to cases where the subscription is not cleaned up (e.g., if the observable never emits).

If everything that I want to do with an observable can be done in the view, then I virtually always use option 1. This covers most cases in my experience. You can always use intermediate observables to produce an observable that you can subscribe to in the view if you need to. Intermediate observables don't introduce memory leak concerns.




回答2:


Another option is to use the observable to retrieve the data, then let Angular's change detection handle the rest. With Angular's change detection, it will update the UI as the data changes ... no need to subscribe again to get updates.

For example, I have this type of UI:

I retrieve the data using Http and an observable. But then I leverage Angular's change detection to handle any updates.

Here is a piece of my service:

@Injectable()
export class MovieService {
    private moviesUrl = 'api/movies';
    private movies: IMovie[];

    currentMovie: IMovie | null;

    constructor(private http: HttpClient) { }

    getMovies(): Observable<IMovie[]> {
        if (this.movies) {
            return of(this.movies);
        }
        return this.http.get<IMovie[]>(this.moviesUrl)
                        .pipe(
                            tap(data => console.log(JSON.stringify(data))),
                            tap(data => this.movies = data),
                            catchError(this.handleError)
                        );
    }

    // more stuff here
}

And here is the full code (except the imports) for the detail component shown on the right above:

export class MovieDetailComponent implements OnInit {
    pageTitle: string = 'Movie Detail';
    errorMessage: string;

    get movie(): IMovie | null {
        return this.movieService.currentMovie;
    }

    constructor(private movieService: MovieService) {
    }

    ngOnInit(): void {
    }
}

You can see the complete example (with editing) here: https://github.com/DeborahK/MovieHunter-communication/tree/master/MH-Take5




回答3:


When passing data between components, I find the RxJS BehaviorSubject very useful.

You can also use a regular RxJS Subject for sharing data via a service, but here’s why I prefer a BehaviorSubject.

  1. It will always return the current value on subscription - there is no need to call onnext().
  2. It has a getValue() function to extract the last value as raw data.
  3. It ensures that the component always receives the most recent data.
  4. you can get an observable from behavior subject using the asobservable() method on behavior subject.
  5. Refer this for more

Example

In a service, we will create a private BehaviorSubject that will hold the current value of the message. We define a currentMessage variable to handle this data stream as an observable that will be used by other components. Lastly, we create the function that calls next on the BehaviorSubject to change its value.

The parent, child, and sibling components all receive the same treatment. We inject the DataService in the components, then subscribe to the currentMessage observable and set its value equal to the message variable.

Now if we create a function in any one of these components that changes the value of the message. The updated value is automatically broadcasted to all other components.

shared.service.ts

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

@Injectable()
export class SharedService {

  private messageSource = new BehaviorSubject<string>("default message");
  currentMessage = this.messageSource.asObservable();

  constructor() { }

  changeMessage(message: string) {
    this.messageSource.next(message)
  }

}

parent.component.ts

import { Component, OnInit } from '@angular/core';
import { SharedService } from "../shared.service";

@Component({
  selector: 'app-sibling',
  template: `
    {{message}}
    <button (click)="newMessage()">New Message</button>
  `,
  styleUrls: ['./sibling.component.css']
})
export class SiblingComponent implements OnInit {

  message: string;

  constructor(private service: sharedService) { }

  ngOnInit() {
    this.service.currentMessage.subscribe(message => this.message = message)
  }

  newMessage() {
    this.service.changeMessage("Hello from Sibling")
  }

}

sibling.component.ts

import { Component, OnInit } from '@angular/core';
import { SharedService } from "../shared.service";

@Component({
  selector: 'app-sibling',
  template: `
    {{message}}
    <button (click)="newMessage()">New Message</button>
  `,
  styleUrls: ['./sibling.component.css']
})
export class SiblingComponent implements OnInit {

  message: string;

  constructor(private service: SharedService) { }

  ngOnInit() {
    this.service.currentMessage.subscribe(message => this.message = message)
  }

  newMessage() {
    this.service.changeMessage("Hello from Sibling");
  }

}


来源:https://stackoverflow.com/questions/49307098/persistent-subscriptions-with-rxjs-in-angular-5

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!