LaVOZs

The World’s Largest Online Community for Developers

'; rxjs - Angular open a dialog and return observable for action result - LavOzs.Com

I would like to show a dialog by code (not MatDialog but a custom one) and then return an Observable that will trigger a result to the caller.

Something like this:

    @ViewChild(PopupComponent)
      myDialog: PopupComponent;


public show(){
   this.myDialog.show().subscribe(v => {
       if(v.OkClicked){
          console.log('ok clicked!');
       }
   });
}


export interface DialogResult{
    OkClicked: boolean;
}

@Component({
  selector: 'f-popup',
  outputs: [ "onClose" ],
  templateUrl: './popup.component.html',
  styleUrls: ['./popup.component.css']
})

private _result : ReplaySubject<DialogResult>;
private _obsResult: Observable<DialogResult>;

public show(): Observable<DialogResult>(){
    // show the dialog
    if(!this._result){
        this._result = new ReplaySubject<DialogResult>(1);
    }
    return this._obsResult.asObservable();
}

public onOkClick(){
   let result: DialogResult = {OkClicked: true};
   this._obsResult.next(result);
}

public onClosed(){
    this._obsResult.complete();
    this._obsResult = null;
}

ngOnDestroy() {
    if(this._obsResult) {
      this._obsResult.complete();
      this._obsResult = null;
   }
}

The dialog is not destroyed when it is closed, it is a component inside anohther component that is shown and hidden. My question is, is it a good way to recreate a Subject on every show of the dialog? Are there memory leaks for this way?

I would suggest to do it the other way around.

You can pass a data object with an observable to your dialog when you open it. When in the PopupComponent the action is confirmed (OK is clicked) you subscribe to the passed action$ observable. You can optionally pass in a cancel$ observable to do something when user clicks cancel or close.

interface PopupData<T> { 
  action$: Observable<T> // Action to which you subscribe on OK
  cancel$?: Observable<void> // Optional observable for handling cancel
}

@Component({
  selector: 'f-popup',
  templateUrl: './popup.component.html',
  styleUrls: ['./popup.component.css']
})

public data: PopupData<any>;

public show(data: PopupData<T>): Observable<T>(){
  this.data = data;
}

public onOkClick(){
  this.data.action$.subscribe();
}

public onClosed(){
  if (this.data.cancel$) {
    this.data.cancel$.subscribe();
  }
}

UPDATE:

I will answer your question, since you are apparently not interested in suggestions for alternative solutions, but please take note of the comment at the bottom of the answer!

You are not totally good when it comes to memory problems. When the component gets destroyed you still have a living ReplaySubject on your _result property. I would suggest you also complete and unsubscribe from your ReplaySubject in the ngOnDestroy lifecycle hook. Another issue is that you subscribe to the observable in in your show() call but you do not unsubscribe. Since you use a Subject the observable is multicast meaning you create a memory leak there. You can consider using a pipe like take(1) or first() to make it unicast, that is probably the right thing to do, since you probably want to be notified only once per opened dialog.

You can also consider using a normal Subject, since there is no need for replay functionality here (at least with the code you shared I see no reason why).

Then there is this line:

return this._obsResult.asObservable();

Should probably be:

return this._result.asObservable();

I would suggest following changes:

Change to Subject

private _result: Subject<DialogResult>;

Add complete and unsubscribe for Subject to ngOnDestroy

ngOnDestroy() {
  if(this._obsResult) {
    this._obsResult.complete();
    this._obsResult = null;
  }
  if (this._result) {
    this._result.complete();
    this._result.unsubscribe();
  }
}

And return unicast value instead of multicast using take(1) pipe:

return this._result.asObservable().pipe(take(1));

A last comment:

In my opinion you are over-engineering a simple dialog box. I would suggest to rethink and consider simplifying your popup logic.

Related
Creating and returning Observable from Angular 2 Service
Delegation: EventEmitter or Observable in Angular
How to create an Observable from static data similar to http one in Angular?
What is the correct way to share the result of an Angular Http network call in RxJs 5?
Return an empty Observable
Subscribing for Observable in service angular is not working
Angular 4+ ngOnDestroy() in service - destroy observable
Angular - “has no exported member 'Observable'”
Create filtered Subject to Observable in Angular
angular7 ag-grid this.http is undefined error