1
votes

I've an Angular 6 component where I've a grid that uses async pipe to subscribe to an observable with items. I'd like to have a button that when clicked, refreshes data in that observable. I've added the following code:

public refreshData(): void {
   this.details$ = this._service.getDetails();
}

Then, on the template:

*ngIf="(details$ | async); let d"

So right now, each time a user clicks the button, refreshData method is executed. That code works, data is refreshed, but I am creating a new observable for every click. Is there a better way to handle that ?

3
Have you looked into using a Subject, so you can add new items into the same stream?jonrsharpe
I don't see any benefit in updating/adding to a stream, vs. replacing the reference - so unless you have a specific requirement, I'd say keep it as-is.Thor Jacobsen
Create a (Behaivor)Subject in the class. Your function should just push new data to it. reactivex.io/documentation/subject.htmlMax
The case I am worried about is that with the current solution, I am not ubsubscribing from the stream. So when I create a new observable in refreshData method, does the async pipe ubsubscribe from the "old one" ?macpak
Nothing you can do from the component side, to push new data to the subscribers you have to implement the logic in the service. To your second comment, subscribing from view with async automatically unsubscribe on destroyYaakovHatam

3 Answers

7
votes

As commented, a more reactive-ish way of doing this would be the following:

import {Subject,Observable} from 'rxjs';
import {startWith,switchMap} from 'rxjs/operators';

class FooComponent implements OnDestroy {
  private readonly refreshClick$ = new Subject<void>();
  readonly details$ : Observable<FooDetails>;

  constructor(...){
    this.details$ = this.refreshClick$.pipe(
     // fake emission of a click so that initial data can be loaded
     startWith(undefined),
     switchMap(() => this._service.getDetails())
    );
  }

  ngOnDestroy(){
    // "complete" should be invoked on subjects once their lifetime is complete
    refreshClick$.complete();
  }

  refresh(){
    this.refreshClick$.next();
  }
}
0
votes

Try using subject and each time you want to update data, just update the subject, it will be reflected in ui.

-1
votes

I'm not sure what your goal is. To only update the data in the view when the button is clicked, even when the service provided fresh data in the meantime, or add some entirely new details object on button click to the stream, but the view should continously update the details regardles of them coming from the service or the button click. Assuming the second case for now:

To control the flow of a stream, that is to be able to add to a stream, you can use a Subject.

Local Subject

Assuming you don't want to touch the _service logic, and only need the refreshed value inside your component, but still get the input from its getDetails() observable: You can subscribe to the service in your ngOnInit, handing the result to your local Subject and also handing the refreshed value in your refreshData() method. Assuming the type of the details to be Details we get:

private details$: Subject<Details>;

constructor(private _service: Service) {}

ngOnInit(): void {
  this._service.getDetails().subscribe(updateDetails => this.details$.next(updateDetails));
}

ngOnDestroy(): void {
   this.details$.complete();
}

public refreshData(): void {
  this.details$.next(new Details());
}

Service Subject

If you want to keep the update not only locally but send it to the service, you have to adjust the services logic to handle an update to the details. That means turning the service observable into a Subject. Usually you don't want to expose the services Subjects directly but only the observable side of the subject and handle update requests in a method. That means in your service:

private _detailsSource$: Subject<Details> = new Subject<Details>();
private _details$: Observable<Details> = this._detailsSource$.asObservable();

public getDetails(): Observable<Details> {
   return this._details$;
}

public updateDetails(update: Details): void {
   // you can add some check logic if the update is valid here
   this._detailsSource$.next(update);
}

And in your component you just need to adjust your refresh method:

refreshData(): void {
  this._service.updateDetails(new Details());
}

You can also skip the getDetails method in your service and just expose the details observable directly by making it public:

private _detailsSource$: Subject<Details> = new Subject<Details>();
public details$: Observable<Details> = this._detailsSource$.asObservable();