Ionic 4 Lazy Loading and Modals

In Ionic 3, I was lazy loading the content page for modals using something like this:

  login() {
     let loginModal = this.modalCtrl.create('LoginPage');
     loginModal.onDidDismiss(data => {
       console.info('HomePage LoginModal onDidDismiss: ');      
     });
     loginModal.present();
  }

My login modal can be called from many places in my app, if the user is not logged in, so it’s something I want to be efficient, and not loaded at all if the user is already logged in.

But after creating an Ionic 4 version of the app (which if anyone says it only takes a few hours, don’t believe them unless your app is less than 4 pages:), I’m struggling a little to figure out the best way to create the login modal, because I really don’t want the content “LoginPage” loaded unless it’s needed, and it seems it has to be imported into every page that might use the login modal…

From the Ionic 4 docs:

async presentModal() {
    const modal = await this.modalController.create({
      component: ModalPage,
      componentProps: { value: 123 }
    });
    return await modal.present();
  }

So… if I create a component with no template, and all the component really does is create the login Modal, and imports the “LoginPage” is it actually more efficient, less bandwidth etc. ?

 app/components/login-modal/login-modal.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { IonicModule } from '@ionic/angular';


import { LoginModalComponent } from './login-modal.component';


@NgModule({
   declarations: [
      LoginModalComponent,
      ],
   
  imports: [
    CommonModule,
    IonicModule.forRoot(),
    
  ],
  exports: [
     LoginModalComponent,
  ],

  entryComponents: [],
})
export class LoginModalComponentModule {}




 app/components/login-modal/login-modal.module.ts





If the above is my LoginModal component Module, and I import this module into every page that needs it, won’t the “LoginPage” component be loaded also? I know… only once… but with Ionic 3 it wasn’t loaded unless it was used, and this is big app… we really don’t want to load anything that the user doesn’t need.

So, is there a better way to use lazy loading and modals?

5 Likes

I’m with you, I don’t get it neither

Definitely I don’t get it. I invested some times but was unable to use a lazy loaded modal

I had a look at the example https://github.com/ionic-team/ionic/blob/053c375521af842e5a0dd8a16d7706fd3fada095/core/src/components/modal/readme.md but not sure that’s the way

Anyone knows how to lazy load a modal in v4?

Ok so I investigated a bit playing with source map (see https://forum.ionicframework.com/t/solved-ionic-cli-v4-where-are-the-source-maps) and I think that declaring modals the following way may be lazy loading and bundle size safe

WARN I may be wrong but I think it’s ok, so plz correct me if it isn’t, I would appreciate it

  1. You create a modal

     @Component({
       templateUrl: './my-modal.modal.html',
       selector: 'my-modal'
     })
    export class MyModal
    
  2. and for the modal your create also a separate module (important: don’t miss the entryComponents)

     @NgModule({
      declarations: [
          MyModal
      ],
      imports: [
          IonicModule,
          CommonModule
      ],
      entryComponents: [
          MyModal
      ]
    })
    export class MyModalModule {
    }
    
  3. at this point you are good, you have a modal you could use everywhere. to do so, just include it in your pages/components respective modules

    imports: [
       MyModalModule
    ]
    

voilà :slight_smile:

then, about my tests, I did as a described above and included the modal only in one and only one page and module, let say A

I had a look to the source map (doing ionic build --source-map) and I found a reference to the modal only in A.js

Then, I added another reference to this modal in another page and module, let’s call it B. Ran the cmd and had a look at the js files and I find no references anymore in A.js but I found one reference to the modal in a file called A~B.js

That’s why I think it is safe to include modal this way to be lazy loading and bundle size safe because I only saw one reference to the modal in only one separate bundle file

p.s.: I’m agree seeing the reference only once in the bundle doesn’t say that it is 100% sure lazy loading safe, but it gave me a good hint that it is probably the case

2 Likes

@reedrichards Thanks for your research.

I’ve tried on your steps, it seems not work. Maybe I don’t catch your idea, mainly the 3rd step:

For example, A page want to show a modal for B page, by your idea, should I import B.module into A.module like this?

A.page.module.ts

@NgModule({
  imports: [
      ...,
      BPageModule
  ],
  declarations: [APage]
})
export class APageModule {}

If do like that, then A.page will be fully replaced by B.page. What do you exactly do?

I don’t get what you mean here?

Let’s say A and B are pages
C is your modal

Then you would have to include the module of C in A if you want to display the modal from A. Same, if you want to display the modal from B, then you would have to include the module C in B

@reedrichards Yes, I think your conclusion about the lazy loading is good. Honestly, I’ve had a feeling that the A-B.js files were doing something like that, but I’m glad you checked it and it seems to be correct…

I really have a lot to figure out in the Angular routing… it seemed a lot simpler in Ionic 3 to use their routing… just the sheer number of files generated in my app is quite daunting… I’ve got 119 numbered files (119.js), and about 30 of the page-. files… but most of them seem to be fairly small… and if angular is really only loading them on demand, it will probably work well…

I’ve actually split my components module into 3 component modules now… one for loading all the time, one for member related stuff, and another for a different section of the app, and even in dev it seems fairly zippy… haven’t gotten to a prod build yet.

@bgies I hope so, but if my conclusion about lazy loading is wrong really don’t hesitate to correct me

I noticed too that there way more js files, I was guessing that there are probably one js files pro web components or something

About lazy loading, I continue to use the same strategy as I had with Ionic v3: one module pro pages or components (if I’ve got X pages and Y components then I have X+Y modules)…will see :wink:

Sorry for my description.
I means Page A popup a modal of Page B, if it will follow this way:

@NgModule({
  imports: [
      ...,
      BPageModule
  ],
  declarations: [APage]
})
export class APageModule {}

Ummm not sure I understand, looks a bit weird but maybe it makes sense in your case…

I struggled with this for a day and finally went back to not lazy-loading modal or popover pages. @reedrichards’ solution seems logical, but I couldn’t get it to work for modals loaded from components on lazy-loaded pages. I kept getting into an issue with circular imports. I also saw an Angular 6 feature request that leads me to believe this isn’t possible yet.

1 Like

Mmmh I think I do as I displayed above and I do have modals called from components used in lazy loaded pages, but maybe it’s not exactly the same structure

your app is open source?

Unfortunately not.
This is my structure currently:

  • src
    • app
      app.module.ts imports components, directives, pipes, services, and page module for each modal/popover page
      app-routing.module.ts sets up routes with loadChildren
      • components
        components.module.ts imports & exports all components, imports directives and pipes
      • directives
        directives.module.ts imports & exports
      • pages
        • each-page
          each-page.module.ts declaration (and if needed entryComponent)
      • pipes
        pipes.module.ts declarations & exports
      • services
        services.module.ts providers

All of my main pages are lazy-loaded.
Most of the pages (modal, popover, or top-level) import the components module.

Well definitely not the same as me, in app.module I import only providers

All other pages/pipes/components/modals/etc. have their own modules (I don’t group stuffs)

EDIT: This isn’t a lazy-loading modal, but it is at least not loading the modal until the calling page is lazy-loaded.

Okay. I got it to work in the blank starter app. The TestComponent isn’t required to do the lazy-loading modal, I just wanted to make sure I knew how this was going to translate to my app.

> ionic start myApp blank --type=angular
> ionic g page Modal
> ionic g component Test
> ionic info

Ionic:

   ionic (Ionic CLI)          : 4.1.1 (C:\Users\Bryan\AppData\Roaming\npm\node_modules\ionic)
   Ionic Framework            : @ionic/angular 4.0.0-beta.0
   @angular-devkit/core       : 0.7.0-rc.3
   @angular-devkit/schematics : 0.7.0-rc.3
   @angular/cli               : 6.0.8
   @ionic/ng-toolkit          : 1.0.0
   @ionic/schematics-angular  : 1.0.1

System:

   NodeJS : v8.11.3 (C:\Program Files\nodejs\node.exe)
   npm    : 6.4.0
   OS     : Windows 10

Test Component

test.component.html

I added a button to open the modal here.

<p>
  test works!
  <ion-button (click)="openModal()">Open Modal</ion-button>
</p>

test.component.ts

And the code to open the modal here.

import { Component, OnInit } from '@angular/core';
import { ModalController } from '@ionic/angular';
import { ModalPage } from '../../modal/modal.page';

@Component({
  selector: 'app-test',
  templateUrl: './test.component.html',
  styleUrls: ['./test.component.scss']
})
export class TestComponent implements OnInit {

  constructor(
      private modalCtrl: ModalController
  ) { }

  ngOnInit() {
  }

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

}

Home Page

home.page.html

I added the test component to the home page.

<ion-header>
    <ion-toolbar>
        <ion-title>
            Ionic Blank
        </ion-title>
    </ion-toolbar>
</ion-header>
<ion-content padding justify-content-center align-items-center>
    <div>
        <app-test></app-test>
    </div>
</ion-content>

home.module.ts

Imported the test component and the modal page module here.

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { IonicModule } from '@ionic/angular';
import { FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';

import { HomePage } from './home.page';
import { TestComponent } from '../components/test/test.component';
import { ModalPageModule } from '../modal/modal.module';

@NgModule({
    imports: [
        CommonModule,
        FormsModule,
        IonicModule,
        ModalPageModule,
        RouterModule.forChild([
            {
                path: '',
                component: HomePage
            }
        ])
    ],
    declarations: [HomePage, TestComponent]
})
export class HomePageModule { }

Modal Page

modal.module.ts

This was the tricky part. The page created by the Ionic CLI included angular routing and didn’t include the entryComponents. So not only did I have to add the entry component as mentioned before, but I also had to delete the default Routes and RouterModule code. If I hadn’t delete the route, going to the ‘/home’ route would actually show the ModalPage.

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { IonicModule } from '@ionic/angular';
import { ModalPage } from './modal.page';

@NgModule({
  imports: [
    CommonModule,
    FormsModule,
    IonicModule,
  ],
  declarations: [ModalPage],
  entryComponents: [ModalPage]
})
export class ModalPageModule {}

Because of this, I think the Ionic CLI could use a couple of extra options for generating modal or popover pages. :wink:

4 Likes

Cool congrats! It looks good :slight_smile:

Maybe to improve for a real app, if it isn’t yet the case, not sure how it really behave, your could create a module for the component and include it in the page as module and not as declaration, I think doing so the component would be lazy loaded too

I mean

 imports: [
    CommonModule,
    FormsModule,
    IonicModule,
    ModalPageModule,
    TestComponentModule, // <---- Here a module for the component
    RouterModule.forChild([
        {
            path: '',
            component: HomePage
        }
    ])
],
declarations: [HomePage] // <--- here no component declaration

Ack! Oh wait, that modal isn’t lazy loading yet… Too early in the morning for me (before lunch)…

Yeah, that’s an option. From the standard Ionic blank app perspective, they don’t normally create modules for components. My app makes one big one for all components (like recommended in v3, but not perfect), and your suggestion is to make one per component.

Yep, but then just replace my suggestion with the big component module. I’m really not sure about it, just thought it’s better to include the module than adding in the declarations

Also, using the ionic g component Test command added the TestComponent directly to the app.module.ts. So I removed that… I think that is because it automatically adds the new component to the closest module file, but they don’t create one for the component and the blank app didn’t have one in the components folder by default… Probably could be done with the --module flag…