← All MicroBlogs

Wrapping Angular Dialog Callbacks in an Observable

February 20, 2026

angularrxjsobservabletypescriptpatterns

When you trigger a dialog — an email composer, a confirmation modal, a file picker — you eventually need to know what the user did. Did they confirm? Cancel? The imperative approach passes callbacks directly. The reactive approach wraps the whole thing in an Observable<boolean> and lets you .subscribe() to the result like any other stream.

Here's the pattern in two parts.

Part 1 — The payload carries its own callbacks

Instead of wiring up event emitters or service methods, embed the resolution functions directly in the payload object you dispatch:

interface EmailPayload {
  data: { subject: string; to: string[]; body: string; /* ... */ };
  onMailSent?: () => void;
  onMailTextDialogClose?: () => void;
}

The dialog doesn't know who opened it. It just calls payload.onMailSent() or payload.onMailTextDialogClose() when the user acts. This keeps the dialog component completely decoupled.

Part 2 — Bridge callbacks to an Observable at the call site

The trick is new Observable(observer => { ... }). Inside its setup function you control exactly when it emits and when it completes — which means you can hand those controls off to callbacks:

listenToEmailDialogResult(payload: EmailPayload): Observable<boolean> {
  return new Observable<boolean>(observer => {
    this.dispatchEmailEvent({
      ...payload,
      onMailSent: () => {
        observer.next(true);
        observer.complete();
      },
      onMailTextDialogClose: () => {
        observer.next(false);
        observer.complete();
      }
    });
  });
}

Now the caller just does:

this.listenToEmailDialogResult(payload).subscribe(wasSent => {
  this.toastMessage = wasSent ? '✅ Email sent!' : '❌ Email closed';
});

One subscribe, one value, automatic completion. No manual cleanup needed.

The Subject as an event bus

A Subject<EmailPayload> acts as the in-component message bus. The constructor subscribes once to it and updates showDialog and dialogPayload whenever a new payload arrives. This decouples dispatch (emailBus.next(payload)) from presentation logic cleanly.

private emailBus = new Subject<EmailPayload>();

constructor() {
  this.emailBus.subscribe(payload => {
    this.dialogPayload = payload;
    this.showDialog = true;
  });
}

When is this useful?

This pattern is especially useful when:

  • A parent or sibling component needs to open a shared dialog and await its result
  • You want to chain dialog results with other RxJS operators like switchMap, filter, or catchError
  • The dialog is a dumb component that shouldn't know about services or routing

The new Observable constructor is often underused. Whenever you have a third-party callback API — timers, DOM events, dialog results — wrapping it this way gives you the full power of the RxJS operator chain without any coupling to the internals.

Try it live

The full working example — component, template, and dialog flow — is runnable on StackBlitz:

Open on StackBlitz →

Tech Innovation Hub
Modern Software Architecture

Exploring cutting-edge technologies and architectural patterns that drive innovation in software development.

Projects

© 2025 Tech Innovation Hub. Built with Gatsby and modern web technologies.