Hone logo
Hone
Problems

Angular Dialog Service

Develop a reusable Angular service that manages the display and behavior of modal dialogs. This service should abstract away the complexities of creating, showing, and hiding dialogs, allowing components to easily interact with them without direct DOM manipulation or intricate component instantiation logic. This is a common pattern in web applications for user confirmations, form inputs, and displaying detailed information.

Problem Description

Your task is to create an DialogService in Angular using TypeScript. This service will be responsible for dynamically creating, displaying, and destroying dialog components. The service should provide methods to open a dialog with specific content and data, and allow the dialog to return a result to the caller.

Key Requirements:

  1. DialogService Class: Create a service named DialogService.
  2. open<T, R>(componentType: ComponentType<T>, config?: DialogConfig<any>): DialogRef<T, R> Method:
    • This method should accept a component type (e.g., MyDialogComponent) and an optional configuration object.
    • The componentType parameter signifies the Angular component to be rendered as the dialog.
    • The DialogConfig should allow passing data to the dialog component (data) and potentially other configuration options (e.g., width, height, disableClose).
    • This method should return a DialogRef object.
  3. DialogRef<T, R> Class:
    • This class represents a reference to an opened dialog.
    • It should have a close(result?: R) method. Calling close should dismiss the dialog and optionally pass a result back to the caller.
    • It should expose an observable (e.g., afterClosed: Observable<R | undefined>) that emits when the dialog is closed, providing the result.
  4. Dynamic Component Loading: The service must dynamically load and attach the provided componentType to the DOM. This typically involves using ViewContainerRef and ComponentFactoryResolver (or the newer createComponent API).
  5. Overlay/Backdrop: A backdrop element should be rendered behind the dialog to dim the background and indicate focus. This backdrop should be dismissible by default (clicking on it closes the dialog), unless disableClose is set to true in the DialogConfig.
  6. Styling: Basic styling for the dialog container and backdrop should be considered, allowing for customization via DialogConfig (e.g., width, height).
  7. Singleton Service: The DialogService should be a singleton service, ensuring only one instance manages all dialogs.

Expected Behavior:

  • When dialogService.open(MyDialogComponent, { data: 'some data' }) is called, MyDialogComponent should be rendered in the DOM, likely within an overlay.
  • The data passed in the config should be accessible within MyDialogComponent (e.g., via an @Input() property or a service injected into the dialog component).
  • Clicking a close button within MyDialogComponent or clicking the backdrop (if enabled) should call the dialogRef.close() method.
  • When dialogRef.close('dialog result') is called, the dialog component should be removed from the DOM, and any component that subscribed to dialogRef.afterClosed should receive 'dialog result'.
  • If the dialog is closed without a result (e.g., clicking the backdrop when disableClose is true and no explicit close button is used), the afterClosed observable should emit undefined.

Edge Cases:

  • No Dialogs Open: Ensure the service handles calls gracefully when no dialogs are open.
  • Multiple Dialogs: Consider how multiple dialogs should be stacked and managed (though for this challenge, a single active dialog is sufficient for core functionality).
  • Closing on Navigation: Ideally, dialogs should be closed when the user navigates away from the current route. (This can be a stretch goal).
  • Component Inputs/Outputs: How will data be passed to the dialog and how will results be passed from the dialog?

Examples

Example 1: Basic Dialog Opening

// In a component's method
constructor(private dialogService: DialogService) {}

openSimpleDialog() {
  const dialogRef = this.dialogService.open(SimpleMessageDialogComponent, {
    data: { message: 'Hello from the component!' }
  });

  dialogRef.afterClosed.subscribe(result => {
    console.log('Dialog closed with:', result);
  });
}

// Assume SimpleMessageDialogComponent has an @Input() message and a close button

Example 2: Dialog with Data and Result

// In a component's method
constructor(private dialogService: DialogService) {}

openConfirmDialog() {
  const dialogRef = this.dialogService.open(ConfirmDialogComponent, {
    data: { title: 'Confirm Action', message: 'Are you sure you want to proceed?' }
  });

  dialogRef.afterClosed.subscribe(confirmed => {
    if (confirmed) {
      console.log('User confirmed the action.');
      // Perform action
    } else {
      console.log('User cancelled the action.');
    }
  });
}

// Assume ConfirmDialogComponent has @Input() title and message,
// and buttons for 'Confirm' (calls dialogRef.close(true)) and 'Cancel' (calls dialogRef.close(false))

Example 3: Dialog with Configuration

// In a component's method
constructor(private dialogService: DialogService) {}

openLargeDialog() {
  const dialogRef = this.dialogService.open(LargeDataDialogComponent, {
    data: { items: [...] },
    width: '80%',
    disableClose: true // User cannot close by clicking backdrop
  });

  dialogRef.afterClosed.subscribe(result => {
    console.log('Large dialog closed:', result);
  });
}

// Assume LargeDataDialogComponent displays a list of items and has its own close button

Constraints

  • The solution must be implemented in TypeScript.
  • You should leverage Angular's built-in features for dynamic component creation (e.g., ViewContainerRef, ComponentFactoryResolver or createComponent).
  • The DialogService must be provided at the root level (providedIn: 'root').
  • Avoid using third-party libraries for dialog management (e.g., Angular Material Dialog). The goal is to build the core logic yourself.
  • Performance: While not strictly benchmarked, the solution should be reasonably efficient for dynamic component loading and DOM manipulation.

Notes

  • Think about how to manage the lifecycle of the dialog component. When should it be created? When should it be destroyed?
  • Consider the use of HostListener or other mechanisms to capture clicks on the backdrop.
  • The DialogConfig interface will need to be defined.
  • The DialogRef will need to manage the actual component instance and its lifecycle, as well as an Observable for closing.
  • You will likely need to create a simple "container" component or use a direct Renderer2 manipulation to append the dialog and backdrop to the application's root view.
  • For this challenge, you don't need to build the actual dialog components (SimpleMessageDialogComponent, ConfirmDialogComponent, etc.). Focus solely on the DialogService and the DialogRef mechanism.
Loading editor...
typescript