Creating a versatile modal component with Angular for endless usage opportunities

After following this informative tutorial, I successfully created a flexible and customizable modal component. However, a new challenge has arisen. Consider two components, A and B, both utilizing the modal component to display modals. In Component A, I need functionA() to be executed upon clicking the submit button, whereas in Component B, it should be functionB(). How can I modify the modal .ts file to grant access and execute these distinct functions?

I aim to avoid creating a global service file with all functions or moving them into the modal.ts file. Instead, I prefer to keep each component clean and independent while optimizing their interaction by passing the function name and necessary input arguments only. Does anyone have insights on the best approach to achieve this? Thank you in advance.

.ts of the modal:

  open(): Promise<boolean> {
     ...
  }

  async close(): Promise<void> {
      const result = await this.modalConfig.onClose();
      this.modalRef.close(result);  
  }

  async dismiss(): Promise<void> {
    ...
  }
}

config.ts of the modal:

export class ModalConfig {
  modalTitle: string;
  data: string = '';
  onClose?(): Promise<boolean> | boolean;
}

.html of the modal:

<ng-template #modal>
  <div class="modal-header">
    <button type="button" (click)="dismiss()">cancel</button>
    <button type="button" (click)="close()">Ok</button>
  </div>
</ng-template>

Answer №1

If you're looking to grasp the concept of a reusable modal component, I've put together a simplified working version for your understanding. Take a look at the code and feel free to reach out if you encounter any issues. By coding the modal component once, we can effortlessly reuse it across the entire application!

modal component

html

<div
  class="modal"
  tabindex="-1"
  role="dialog"
  *ngIf="open"
  style="display:block;"
>
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title">{{ config.title }}</h5>
        <button
          type="button"
          class="close"
          data-dismiss="modal"
          aria-label="Close"
          (click)="discardWrapper($event)"
        >
          <span aria-hidden="true"gt;&times;</span>
        </button>
      </div>
      <div class="modal-body">
        <p>{{ config.description }}</p>
      </div>
      <div class="modal-footer">
        <button
          type="button"
          class="btn btn-primary"
          (click)="saveWrapper($event)"
        >
          Save changes
        </button>
        <button
          type="button"
          class="btn btn-secondary"
          data-dismiss="modal"
          (click)="discardWrapper($event)"
        <Close
        /button>
      /div>
    /div>
  /div>
/div>

ts

import { CommonModule } from '@angular/common';
import { Component, OnInit } from '@angular/core';
import { AComponentService } from './a/a.component.service';

export class ModalConfig {
  title?: string = '';
  description?: string = '';
  save?: Function = () => {};
  discard?: Function = () => {};

  constructor(
    title: string = '',
    description: string = '',
    save = null,
    discard = null
  ) {
    if (title) this.title = title;
    if (description) this.description = description;
    if (save) this.save = save;
    if (discard) this.discard = discard;
  }
}

@Component({
  selector: 'app-modal',
  templateUrl: './modal.component.html',
  styleUrls: ['./modal.component.css'],
  imports: [CommonModule],
  standalone: true,
})
export class ModalComponent implements OnInit {
  constructor(private aComponentService: aComponentService) {}

  get open() {
    return this.aComponentService.isOpen;
  }

  get config() {
    return this.AComponentService.config;
  }

  ...

}

service

import { Injectable } from '@angular/core';
import { ModalConfig } from './modal.component';

@Injectable({
  providedIn: 'root',
})
export class AComponentService {
  isOpen = false;
  configAComponent = new ModalConfig();

  constructor() {}

  open(config: ModalConfig) {
    this.configAComponent = { ...this.configAComponent, ...config };
    this.isOpen = true;
  }
}

A component using this logic

ts

import { Component, OnInit } from '@angular/core';
import { AComponentService } from '../services/a/a.component.service';

@Component({
  selector: 'app-a',
  templateUrl: './a.component.html',
  styleUrls: ['./a.component.css'],
  standalone: true,
})
export class AComponent implements OnInit {
  constructor(private aComponentService: aComponentService) {}

  ngOnInit() {}

  openModal() {
    this.aComponentService.open({
      title: 'test',
      description: 'test description',
    });
  }
}

html

<p>a works!</p>

<button (click)="openModal()" class="btn btn-primary">open modal</button>

Check out stackblitz

Answer №2

Transfer the dismiss() and close() functions to a separate service and execute them from there. By following this approach, you should be able to resolve your problem successfully.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Tips on customizing a row in a table

Having a small issue styling a row in my table. Essentially, the table consists of 4 rows. a) If the data under column Title 5 is below 0, then the entire row should display in red color. b) If the data under column Title 5 is equal to 17, then the compl ...

Unable to initialize a public variable due to issues with Ionic Storage retrieval

I am currently facing an issue where I am trying to assign a token stored in the Ionic storage module to a public variable. However, when I attempt to set the token and then access it from another function, I encounter an undefined error. Here is the code ...

Sending various files from an Angular application to a Spring Controller

I am struggling to upload multiple files to the server using Angular. Below is my Angular code snippet: app.directive('fileModel', ['$parse', function ($parse) { return { restrict: 'A', link: function (sco ...

Navigating the NextJS App Directory: Tips for Sending Middleware Data to a page.tsx File

These are the repositories linked to this question. Client - https://github.com/Phillip-England/plank-steady Server - https://github.com/Phillip-England/squid-tank Firstly, thank you for taking the time. Your help is much appreciated. Here's what I ...

Problem encountered when attempting to retrieve information from a web service in Angular 4

I am currently attempting to retrieve data from a web service API. However, all I can see is the data on the console. The web service requires an ID, so I input the ID first and then proceed to obtain the data related to that ID within the web service us ...

Difficulty in utilizing gettext for parsing the .po translation file

When attempting to utilize ngx-translate with the .po loader, a warning is triggered during compile time: WARNING in ./node_modules/encoding/lib/iconv-loader.js 9:12-34 Critical dependency: the request of a dependency is an expression The warning specifi ...

Why is Axios not being successfully registered as a global variable in this particular Vue application?

Recently, I have been delving into building a Single Page Application using Vue 3, TypeScript, and tapping into The Movie Database (TMDB) API. One of the hurdles I faced was managing Axios instances across multiple components. Initially, I imported Axios ...

Assigning a value to an angular object

In my current project with Angular 16, I am executing a query on the database and assigning the returned number to a document number. Data Model export class Document { doc_data: string =""; doc_for: string=""; doc_number: number = 0; doc_ ...

What is the best way to extract data from the currentValue property of SimpleChange objects?

Trying to utilize the onChanges lifecycle hook in Angular has led me to the SimpleChange object, with properties like currentValue and previousValue. When passing an array of numbers to this hook, I noticed that currentValue displays as type Array. Check ...

Guide to dynamically loading asset images within an Angular component

I've been attempting to dynamically load images into a carousel on my page, but I'm facing an issue with the code. Here's what I have so far: <div id="carousel" class="carousel slide" data-ride="carousel"> <div class="carouse ...

What is the best way to assign a value to an ngrx reducer/action in order to update the state?

As a newcomer to Angular ngrx, I am in the process of integrating it into my 'standard beginner-project' by replacing all @Input()s and @Output()s. While incrementing and decrementing the state is straightforward, I am faced with the challenge of ...

Upgrading host and ComponentResolver from AngularDart version 4 to version 5

I'm currently in the process of transitioning a large Angular4 application (recently upgraded from Angular2) to Angular5. Within various sections of the application, we employ a directive to mark a div or other html element for generation inside that ...

Utilizing Angular 2 Observable for showcasing a seamless flow of real-time information

Currently, my Angular 2 Web application is utilizing a Couchbase Server as its database. The data communication occurs through WebAPIs that interact with the CouchBase server. As of now, I am uncertain if this method is optimal, but I am constantly polling ...

Refreshing local storage memory on render with a custom Next.js hook

I recently developed a custom Next.js hook named useLocalStorage to store data in local storage. Everything is working fine, except for one issue - the local storage memory gets refreshed with every render. Is there a way to prevent this from happening? ...

What is the best way to show and hide a tooltip message when the mouse enters and leaves an element?

I am facing an issue where the mouseleave event is not working as expected. Even though the mouseenter event works fine and displays a message, the message stays visible even after the pointer is removed. How can I fix this problem? .on("mouseenter" ...

Issues with the functionality of angular-oauth2-oidc tryLogin() are causing unexpected results

In my angular 8 project, I am using release version 8.0.4 with the authorization code flow implemented: Below is the code snippet that I have in place this.oauthService.configure(authConfig); this.oauthService.tokenValidationHandler = new JwksVali ...

Tips for utilizing express in your typescript projects

I'm curious about the transition of definition files from tsd to typings, and now to @types. How can I incorporate @types in a node/express project? What is currently preferred and what's the reason for moving from tsd to typing and finally to @t ...

Tips on implementing nested ngFor loops

I am managing a Cloud Firestore database with a unique structure: service [serviceId] [userId] documentId service_img : "test.png" users [uid] services [documentId] name: "user added service name" The above showcases ...

Exploring nested contexts in react testing library with renderHook

This is a sample code snippet in TypeScript that uses React and Testing Library: import React, { FC, useEffect, useMemo, useState } from 'react'; import { renderHook, waitFor } from '@testing-library/react'; interface PropsCtx { inpu ...

What is the best way to fetch the chosen item in an Angular select dropdown

I am working on a dropdown that populates with items of type ObjectA. item.component.html: <label>items list: <select formControlName="itemsCtl" (change)="onChange()"> <option *ngFor="let item of itemList" [value]="item">{{i ...