July 10, 2019
  • Engineering
  • Angular
  • performance

Guest Post: Ionic Angular Performance Tuning

Masahiko Sakakibara

This post was written by community member Masahiko Sakakibara. He is a Web Native App Developer (using JavaScript and PHP) and organizer of the Ionic Japan User Group. Recently, he’s started to use Capacitor and is currently developing plugins for Facebook Login and Admob. Follow him on Twitter @rdlabo.

Ionic 4 provides excellent performance out-of-the-box. Even so, I’m a fan of pushing the limits of my web applications. In this post, I’ll be covering some recent experiments I conducted that look at how to improve the performance of Angular within an Ionic 4 app. These hypotheses included:

  • Using Eager Loading (Instead of Lazy Loading)
  • Preloading Ionic Components
  • Prefetching JavaScript files using a Service Worker

Note: I spoke about Ionic Angular performance tips at the July 7th Ionic Meetup in Japan.

Measurement Methods

Ionic App Template

I used the Ionic 4 Starter Template with @ionic/angular version 4.5.0.

Server Environment

All test apps were hosted on Netlify, a fully-featured free hosting service using subdomains. Asset optimization was set to Disable and the HTTP/2 Server Push capability was not used.

Monitoring

We used Catchpoint with the help of Spelldata Inc. I configured the instrumentation node to be in San Francisco, near the Netlify server. More details here.

Simple Measurement

For the purposes of this article, I used the Audits feature from Lighthouse, running within Google Chrome, then used the time and rendered results from trace to Performance. This section of Lighthouse’s report is useful to examine the startup of your app. It can highlight, for example, rendering issues and highlight why they are happening.

Hypothesis #1: Use Eager Loading Modules (Instead of Lazy Loading)

By default, Angular’s NgModules are eagerly loaded, which means that as soon as the app loads, so do all of the NgModules, whether or not they are immediately necessary. For large apps with lots of routes, lazy loading (in Japanese) is recommended, which loads each NgModule only when they are needed. This reduces the initial bundle size and is useful for speeding up large applications.

However, my theory was that this design pattern may actually increase the initial app render time since in effect multiple JavaScript files are loaded over time.

Result: Verified

Within the Ionic tabs starter template, the initial display of Tabs loads two modules:

`app.module.ts` => `tabs.module.ts` => `tab1.module.ts`

I consolidated all this into app.module.ts and removed tabs.module.ts and tab1.module.ts.

Design Pattern Mdn DNS Mdn Start Rendering
Default 7.00 697.00
Eager Loading 7.00 683.50 (-13.5)

Here we can see a slight difference in the render start time. Also, in the speed index, eager loading was faster than Default in the 99th percentile:

Verifying Page Transitions

Changing the destination page to eager loading reduces the latency of the page transition. However, as mentioned at the beginning, not doing lazy loading will increase the bundle size and may slow down the initial display. Therefore, you must set preloadingStrategy to PreloadAllModules and have completed Load before the transition:

// app-routing.module.ts
@NgModule({
  imports: [
    RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules })
  ],
  exports: [RouterModule]
})

However, PreloadAllModules does not allow you to select a Module to be preloaded, so if you have a lot of Modules, you can definitely reduce the latency of page transitions by specifying a Module that is preloaded.

See Preloading Modules in Ionic v4 for more details.

Hypothesis #2: Preload Ionic Components

It takes a bit of time to load Ionic components when the HTML file and/or template is parsed.

Can we decrease the time it takes to parse Ionic Components by preloading them? I tested this by configuring the DOM to use display: none:

  <section style="display: none;">
    <ion-tabs><ion-tab-bar><ion-tab-button></ion-tab-button></ion-tab-bar></ion-tabs>
    <ion-header><ion-toolbar><ion-title></ion-title></ion-toolbar></ion-header>
    <ion-content></ion-content>
    <ion-card><ion-card-header></ion-card-header><ion-card-content></ion-card-content>
    </ion-card>
    <ion-list><ion-list-header><ion-item></ion-item></ion-list-header></ion-list>
    <ion-label></ion-label>
  </section>

Result: Verified

For measurement purposes, Default is compared to eager loading & PreLoad, but you can see that it is faster than with eager loading only:

Pattern Mdn DNS Mdn Start Rendering
Default 7.00 697.00
Eager Loading 7.00 683.50 (-13.5)
Eager Loading & PreLoad 7.00 637.50 (-59.5)

You can see a difference in the render start time. Also, in the speed index, eager loading & PreLoad was faster than Default or eager loading in the 99th percentile.

Verifying Page Transitions

However, during the loading of Ionic Components, the ion-router-outlet blocks transitions, so writing directly to the home.html template will slow down the display of /home. Therefore, we need to prepare the DOM asynchronously:

  // home.page.ts
  ionViewDidEnter() {
    const preloadArea: HTMLElement = document.getElementById('preload');
    preloadArea.appendChild(document.createElement('ion-card'));
    preloadArea.appendChild(document.createElement('ion-card-header'));
    preloadArea.appendChild(document.createElement('ion-card-subtitle'));
    preloadArea.appendChild(document.createElement('ion-card-title'));
    preloadArea.appendChild(document.createElement('ion-card-content'));
    preloadArea.appendChild(document.createElement('ion-back-button'));
  }

The code above is not preloaded, while the GIF below is (the transition animation has been disabled for clarity):

See the demo here: https://blank-default.netlify.com

See the demo here: https://blank-preload.netlify.com

In the GIF animation above, after you click the button, we have to wait for two JavaScript files to load, whereas below, you’ve already loaded them, so you’ll see the next page immediately after clicking the “next” button. You can also notice that the back button displays instantly when the transition is not delayed.

Hypothesis #3: Prefetch JavaScript files using a Service Worker

My theory is that prefetching JavaScript files using Angular’s PWA library (@ angular/pwa) may improve performance. This configuration looks like:

  // ng-sw.config.json
  "assetGroups": [
    {
      "name": "app",
      "installMode": "prefetch",
      "resources": {
        "files": [
          "/favicon.ico",
          "/index.html",
          "/*.css",
          "/*.js"
        ]
      }
    ...

Result: No Effect

Prefetching JavaScript using a Service Worker is executed after the First Meaningful Paint. Therefore, enabling Service Workers does not affect the initial display.

Verify Page Transitions

There may be an effect. However, about 22000 milliseconds are required for loading to complete in a 30 Mbps downstream environment. So if a user is using an app for a long time, you can expect prefetch to speed things up.

(Demo code: https://blank-sw.netlify.com)

Precaching assets can take a long time if there are a lot of assets. So it’s best to instead cache these assets after they are done loading

Bonus (Unverified) Hypothesis: Preloading Ionicons

For this test, I tried preloading Ionicons by requesting them before they were needed. This way the file loads faster because it’s already in memory, versus requesting it over the network.

ionIcon calls the appropriate SVG file and displays it, after rendering the Web Components (see reference code).

My theory is that calling out an SVG file for use prior to render may help speed up.


(The icon does not appear and is not Visually Loaded)

However, once I tried preloading them using display: none for all the ionIcons, but I realized there were no performance improvements.

Demo: https://tabs-bad-lazy.netlify.com

A different approach is needed to complete the call to the SVG file as if a block is occurring, and rel="preload" is a good choice. There are some issues such as how to judge iOS/Android performance, so it has not been verified yet. This is probably the same as ionImg.

Summary

Reviewing the above experiments, we’ve learned a bunch of tips for improving your Ionic Angular apps.

On Improving Initial Display: Use eager loading over lazy loading and server worker prefetching doesn’t have any effect.

On Improving Page Transitions: Preload Angular modules and service worker prefetching may speed up the transitions.

Improving Your Ionic Angular Apps

There are many approaches to performance tuning Angular apps. If you’re focused solely on the front-end engineering aspect of app building, then reducing time-to-render is the most important area to focus on.

I would like to encourage you to develop and test your own hypotheses while developing your own Ionic Angular apps. Even if the difference in the results is small, they may be worth it depending on your performance requirements.

The Catchpoint used in cooperation with Spelldata Inc. is an excellent tool to measure SPA and PWA performance correctly, so I recommend using it when you run your own performance tests.

See you later!


Masahiko Sakakibara