typescript 107 lines · 3 tabs

Reusable Angular Confirmation Dialog Service Returning an Observable of the User's Choice

Shared by codesnips Aug 2026
3 tabs
import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';

export interface ConfirmDialogData {
  title: string;
  message: string;
  confirmText: string;
  cancelText: string;
}

@Component({
  selector: 'app-confirm-dialog',
  template: `
    <h2 mat-dialog-title>{{ data.title }}</h2>
    <mat-dialog-content>{{ data.message }}</mat-dialog-content>
    <mat-dialog-actions align="end">
      <button mat-button (click)="cancel()">{{ data.cancelText }}</button>
      <button mat-raised-button color="warn" (click)="confirm()">
        {{ data.confirmText }}
      </button>
    </mat-dialog-actions>
  `,
})
export class ConfirmDialogComponent {
  constructor(
    private readonly dialogRef: MatDialogRef<ConfirmDialogComponent, boolean>,
    @Inject(MAT_DIALOG_DATA) public readonly data: ConfirmDialogData
  ) {}

  confirm(): void {
    this.dialogRef.close(true);
  }

  cancel(): void {
    this.dialogRef.close(false);
  }
}
3 files · typescript Explain with highlit

This snippet shows a small, self-contained confirmation dialog built the idiomatic Angular way: a stateless dialog component driven by injected data, plus a thin service that opens it and hands back an Observable<boolean> representing the user's choice. The pattern solves a common friction point — calling code that wants to ask "are you sure?" without importing Angular Material APIs, wiring up dialog refs, or managing subscriptions everywhere.

In ConfirmDialogComponent, the component knows nothing about who opened it. It reads its title, message, and button labels from MAT_DIALOG_DATA via a typed ConfirmDialogData interface, and closes itself with a boolean payload through MatDialogRef.close. Keeping the component presentational means it is trivially reusable and testable; the confirm() and cancel() methods simply resolve the dialog to true or false.

In ConfirmationService, MatDialog.open returns a MatDialogRef, and afterClosed() already exposes an Observable. The service normalizes that stream: map(result => result === true) collapses the boolean | undefined case (the user pressing Escape or clicking the backdrop yields undefined) into a strict boolean, so consumers never have to guard against undefined. Sensible defaults are merged into the data object so callers can pass only a message. Returning a cold-ish, single-emission Observable fits Angular's reactive style and composes cleanly with operators like filter and switchMap.

In DangerZoneComponent, the payoff is visible: deleteAccount() calls confirmation.confirm(...), then filter(confirmed => confirmed) short-circuits when the user declines, and switchMap chains straight into the actual delete request. Because the confirmation is just another Observable in the pipeline, cancellation and the real work read as one linear flow rather than nested callbacks.

The main trade-off is that afterClosed() emits once and completes, so it behaves like a promise-shaped stream; for repeated prompts the service must be called again. A subtle pitfall is treating undefined as anything other than a decline — the explicit === true check guards against that. This approach is worth reaching for whenever confirmation logic is duplicated across components and should live behind one injectable seam.


Related snips

typescript
export type Settled<R> =
  | { status: 'fulfilled'; value: R }
  | { status: 'rejected'; reason: unknown };

export interface ConcurrencyOptions {
  limit: number;

Simple concurrency limiter for batch operations

node concurrency async
by codesnips 2 tabs
erb
<h1>Products</h1>

<%= form_with url: products_path, method: :get,
              data: { turbo_frame: "products_list", turbo_action: "advance" } do |f| %>
  <div class="filters">
    <%= f.text_field :q, value: params[:q], placeholder: "Search products" %>

Frame navigation that targets a specific frame via form_with

rails hotwire turbo
by codesnips 3 tabs
swift
import SwiftUI

struct CardModifier: ViewModifier {
    var backgroundColor: Color = .white
    var cornerRadius: CGFloat = 12
    var shadowRadius: CGFloat = 5

Custom SwiftUI view modifiers for reusability

swift swiftui ios
by Sofia Martinez 2 tabs
css
/* Interactive pseudo-classes */
a:link { color: blue; }
a:visited { color: purple; }
a:hover { text-decoration: underline; }
a:active { color: red; }
a:focus { outline: 2px solid orange; }

CSS pseudo-classes and pseudo-elements for advanced styling

css pseudo-classes pseudo-elements
by Alex Chang 2 tabs
typescript
import { useEffect, useState } from "react";

export function useDebounce<T>(value: T, delay = 300): T {
  const [debounced, setDebounced] = useState<T>(value);

  useEffect(() => {

Debounced search input (React)

react hooks debounce
by codesnips 3 tabs
hcl
# Using the module

module "api_service" {
  source = "./modules/ecs_service"

  service_name       = "api"

Terraform modules for reusable infrastructure

terraform modules iac
by Ryan Nakamura 2 tabs

Share this code

Here's the card — post it anywhere.

Reusable Angular Confirmation Dialog Service Returning an Observable of the User's Choice — share card
Link copied