Opening Ionic 4 Modals from a Common Service

October 11th 2019 Ionic 4 TypeScript

In Ionic 4, even for lazy-loaded modal pages the component must be specified using the type (unlike in Ionic 3 where it had to be passed in as a string containing the type name):

const modal = await this.modalCtrl.create({
  component: ModalPage
});

There's nothing wrong with that. Enforcing strong typing is usually a good idea.

However, it can potentially cause a circular dependency, especially if you move the code for creating and opening modal pages to a separate service to avoid repeating the same boilerplate code throughout the application and to make testing easier:

@Injectable({
  providedIn: "root"
})
export class NavigationService {
  constructor(private modalCtrl: ModalController) {}

  async openModal() {
    const modal = await this.modalCtrl.create({
      component: ModalPage
    });
    await modal.present();
  }
}

The page wanting to open a modal page can then simply call the NavigationService method (which can be mocked in unit tests):

export class HomePage {
  constructor(private navigation: NavigationService) {}

  async openModal() {
    this.navigation.openModal();
  }
}

The problem occurs when you try to inject the NavigationService in the modal page as well (e.g. to navigate to some other page based on the user's action in the modal):

export class ModalPage implements OnInit {
  constructor(private navigation: NavigationService) {}
}

This will create a circular reference:

[ng] WARNING in Circular dependency detected:
[ng] src\app\modal\modal.page.ts -> src\app\navigation.service.ts -> src\app\modal\modal.page.ts
[ng] WARNING in Circular dependency detected:
[ng] src\app\navigation.service.ts -> src\app\modal\modal.page.ts -> src\app\navigation.service.ts

To resolve the issue, the NavigationService must not depend on the modal page. This can be avoided if the modal page type is passed into the NavigationService method as a parameter:

async openModal(modalPage: typeof ModalPage) {
  const modal = await this.modalCtrl.create({
    component: modalPage
  });
  await modal.present();
}

The typeof ModalPage parameter type is used to make sure the correct component type will be passed to the method when invoking it:

async openModal() {
  this.navigation.openModal(ModalPage);
}

With this change, the page opening the modal depends on the modal page instead of the NavigationService. This is much less likely to cause a circular dependency. Usually, there's no need for two modal pages being able to open each other.

Copyright
Creative Commons License