Create a "Dynamic Modal" in a Service?

I would like to fix some issues and automate some stuff when I create a modal in Ionic V4. The solution I’ve found is to create a service to create my modals for me.

@Injectable({
    providedIn: 'root'
})
export class ModalSingleton {

   public lock = false;
   public modal: HTMLIonModalElement;

   constructor(
       public toastHelper: ToastHelper,
       public modalController: ModalController,
   ) {
   }

    /**
     * This helper adds a Double click Guard to the modal and implement the progress bar when opening
     * @param $config The modal
     */
   async presentModal( $config: ModalOptions ) {
       if ( this.lock ) {
           console.log('Double click detected');
           return;
       } // Double click guard
       this.lock = true;

       this.toastHelper.presentProgressBar();
       this.modal = await this.modalController.create( $config );

       await new Promise(r => setTimeout(r, 2));
       await this.modal.present();
       this.modal.onDidDismiss().then( onDismiss => {
            this.dismissModal();
       });
       this.toastHelper.dismissProgressBar();
   }


   async dismissModal() {
       this.modal = null;
       this.lock  = false;
   }

Then in my component, I call it.

this.modalSingleton.presentModal( {
component: PaymentMethodModal,
});

It works but I MUST add “PaymentMethodModal” and EVERY other modal.module in app.module.ts, therefore, losing the lazy loading feature.

How can my service resolve and use modals dynamically?

Thank you for your time

I use this exact same approach. I import the module on the page I’m opening the modal from. If you’d like I can share some code

Yes, please. Would you mind sharing? I need to get my head around this. When you say:

I import the module on the page I’m opening the modal from

Do you mean in the current component I’m using the service?

I use:

MyComponant => MyService => MyService creates the modal using the param from MyComponant

Like this (MyComponant):

this.modalSingleton.presentModal( { componant: PaymentMethod } )

But I need to import PaymentMethodModule in app.modules.ts.
If I try in MyComponantModules it won’t work


  async presetntModal() {

    let component: any;

    if (someCondition1) {
      component = ModalComponent1;
    }

    else if (someCondition2) {
      component = ModalComponent2;
    }

    [...]

    this.modalToShow = await this.modalController.create({
      component: component,
      cssClass: 'myModalClass',
      componentProps: {
        id: this.id,
        tokenValue: this.tokenValue,
      }
    });

    this.modalToShow.onDidDismiss().then(() => {
      // do something
    });

    return await this.modalToShow.present();
  }

or you can import in the component where you are using it

This is my implementation:

popover-modal.service.ts:

  constructor(private modalController: ModalController, private popoverController: PopoverController) { }

  async getModal(component: any, componentProps?: any, mode?: 'md' | 'ios', cssClass?: string | string[], back_dis?: boolean): Promise<HTMLIonModalElement> {
    let backdropDismiss = back_dis === false ? false : true;
    return await this.modalController.create({
      component,
      animated: true,
      componentProps,
      mode: mode || 'ios',
      keyboardClose: true,
      cssClass,
      backdropDismiss,
    });
  }

  async getPopover(component: any, event?: any, componentProps?: any, mode?: 'md' | 'ios', cssClass?: string | string[]): Promise<HTMLIonPopoverElement> {
    return await this.popoverController.create({
      cssClass: cssClass ? cssClass : [],
      component,
      animated: true,
      componentProps,
      mode: mode || 'ios',
      backdropDismiss: true,
      event: event ? event : null
    });
  }

As an example I have a client list that opens a modal to show details of a client. So I export the component on it’s module and import the module in my client-list.module.ts:

client-details.module.ts:

// imports...
import { ClientDetailsComponent } from './client-details.component';

@NgModule({
  imports: [IonicModule, CommonModule,],
  declarations: [ClientDetailsComponent],
  exports: [ClientDetailsComponent],
})
export class ClientDetailsModule { }

client-list.module.ts:

import { ClientDetailsModule } from '../client-details/client-details.module';

@NgModule({
  imports: [
      // ...
      ClientDetailsModule,
  ]
}

When I want to open the modal I just do:

client-list.page.ts:

import { ClientDetailsComponent } from '../client-details/client-details.component';

async detailsClient(client: Client) {
   const modal = await this.popoverModalService.getModal(ClientDetailsComponent, { client, data: this.data }, "ios", ['modal-class-example']);
   modal.present();
}

client-details.component.ts:

  @Input() client: Client;
  @Input() data: DataOptions;

  // client-details methods

To explain what happens:
In every page I want to open a modal I need to import that component’s module in the page I’m currently in. If that component doesn’t have a .module.ts file than you can create ir using ionic g module path/relative/to/component or declare/export it on the page you’ll open it from. The latter only allows you to open the modal(or popover) in the page you have declare it unless you also import that page’s module in other page’s module but that can be a bit confusing.

Hope that helps you. If you need further explaining just hit me back.