November 11, 2020
  • Engineering
  • Angular
  • Framework
  • performance

5 Tips to Improve Ionic Angular App Performance

Matt Netkow

angular-performance-tips

Everyone knows that building performant web apps is critical for retaining happy users. However, with the constant influx of bugs to fix and new features to build, this is easier said than done.

Fortunately, there are several steps you can take to improve your Angular app’s performance substantially. Recently, Stephen Fluin from the Angular team delivered a web.dev conference talk titled “How to stay fast and fresh with Angular,” with tips on improving Angular app startup performance and bundle size. His suggestions were excellent and included several I had never tried before, so I decided to put them to the test in a real Angular app.

The following post reviews Stephen’s Angular performance tips applied through the lens of an Ionic app, Ionifits, a Zenefits-inspired human resources demo app. It showcases various Ionic App Platform technologies, including Ionic Framework, Capacitor, and Ionic Native Enterprise solutions. Try it out on the web, iOS, or Android.

By applying these tips, I was able to improve Ionifits’ Lighthouse performance score by ~20 points. Let’s dig in.

Reduce app bundle size with source-map-explorer

Modern web apps, including Angular apps, are transformed in various ways before running in a web browser: JavaScript files are combined then minified or occasionally machine-generated. To debug an app or review its bundle size, the source is inspected using source maps that map the transformed source to the original source. Examining source maps is a great way to understand what’s happening between the code you write and the code shipped to the end-user.

The Angular team recommends the source-map-explorer tool to visualize your app’s code distribution. First, install it:

$ npm i -g source-map-explorer

Next, open angular.json within your project. Find the “configurations” node then set the “sourceMap” and “namedChunks” options to “true”:

// angular.json
"configurations": {
  "sourceMap": true,
  "namedChunks": true,
}

Next, create a production build of your app:

$ ionic build --prod

Finally, run the source-map-explorer tool, specifying the JavaScript file to analyze. In Ionic apps, that’s a file that begins with “main” (along with a constantly changing hash like main-es2015.5e93717f1.js) found under the “www” folder. Run the following to inspect both the es2015 (modern browsers) and es5 (legacy browsers) bundles:

$ source-map-explorer www/main*.js

Running this command opens up a visualization of your app’s code distribution, including the code you’ve written and the libraries you’re using, such as Angular, Ionic Framework, RxJS, and Capacitor.

Begin by inspecting the frameworks/libraries you’ve included for unused code. It can creep in over time, such as when a team member accidentally checks in a prototype. In Ionifits, I removed @angular/animations (specifically, app.module.ts referenced NoopAnimationsModule), which removed 53.81 KB (4.8% total) from the app.

Next, review your application code, represented here as the src section. A common issue is unnecessary imports. In Ionifits, one file, in particular, stood out – data/employeeData.js accounted for over half of the app’s size – 571.57 KB, or 54.2% of the app. The file includes thousands of fake “employees” referenced on an employee directory listing page, so I expected the large file size. However, it’s only referenced on that directory page, which is not the first page a user interacts with, so clearly, something was off.

Looking in src/app/app.module.ts, I noticed that EmployeeService, which loads data from employeeData.js, was imported as a provider:

import { EmployeeService } from './services/employee.service';

providers: [
    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
    EmployeeService
  ],

I removed that unnecessary import then reran the source map explorer analysis. Wow! The total bundle size dropped from 1.03 MB to 481.77 KB, and the src folder (my written app code) became so tiny that it was no longer displayed in the source-map-explorer visualization.

Don’t see a source folder (src)? That’s good – it means your app code is tiny compared to the other code in your project. Focus on the libraries/frameworks you can remove instead.

Finally, audit package.json, looking for any unused libraries. I noticed @angular/material had been added at some point but given we use Ionic Framework’s UI components in Ionifits, I removed it. Fortunately, no Material UI components were in use, so the app’s bundle size didn’t change at all. However, it’s good practice to clean up unused references, lest they find their way into your bundle!

By implementing the above changes, Ionifits’ Lighthouse performance score increased from 61 to 80:

ionifits-lighthouse-score

Configure size budgets

In addition to using the source-map-explorer tool to analyze bundle sizes, you can also configure size budgets. Reducing your app’s size is challenging once it’s been in production, so setting budget thresholds ensures your application stays within the size boundaries you define.

Set size budgets within angular.json’s budgets section:

// angular.json
{
  ...

  "configurations": {
    "production": {
      ...
      budgets: [
        "type": "initial",
        "maximumWarning": "500KB",
        "maximumError": “800KB"
      ]
    }
  }
}

The recommended budgets to set vary, as each application is different. In the above example, if the application’s initial size exceeds 500KB, the Angular CLI will show a console warning when you build the app. If it gets larger than 800KB, the build will fail.

Split your app into modules for native Angular lazy loading

Lazy loaded modules load on-demand when a user navigates to their route the first time. This strategy is excellent for app performance and reducing the initial bundle size. Use the ng generate command to create a lazily loaded module:

$ ng generate module --module app-module --route about about

Ionic Angular apps embrace lazy loading out of the box, so no changes to Ionifits were necessary. New Ionic apps (created using the ionic start command) are pre-configured with lazy loading, and subsequent app pages created using the Ionic CLI’s generate command are automatically configured as lazy loaded modules, too.

$ ionic generate page about/about

Keep Angular up-to-date regularly with “ng update”

Let’s face it – no one likes updating their dependencies. The risk of something breaking always seems high, which of course, distracts from actually building more features for your app. Fortunately, the Angular team does a superb job with the Angular CLI’s update process, which updates the Angular packages and applies code transformations as needed for you.

The Angular team recommends updating the CLI and Core first:

$ ng update @angular/cli @angular/core

Afterward, update the rest of the Angular packages if there are no errors.

Another resource to check out is the Angular update guide: https://update.angular.io. Select the version your app is currently on, then the version you’re updating to. The guide will show you what to do before, during, and after the update. In my experience, updating is painless since there’s very little manual work to do. The CLI takes care of most changes for you!

Updating the Ionic dependencies of your app is also a single command away:

# Ionic Angular
$ npm update @ionic/angular@latest

# Capacitor
$ npm update @capacitor/core@latest @capacitor/ios@latest @capacitor/android@latest

If you’re concerned about ensuring that all dependencies will work together, periodically reference the package.json files in the Ionic starter template apps (here’s Angular’s). These are updated with the latest compatible versions over time by the Ionic Framework and DevRel teams.

Reduce app build times by adding a Browserslist file

A Browserslist file specifies the browsers that an app supports. Developer tools can use that information in various ways, such as applying build process optimizations to reduce app build times.

This is the case with new Angular 10+ projects – including a browserlists file with the default configuration means that ES5 builds become disabled.

If your Angular app is like Ionifits, created in January 2019 as an Angular 7 app, you might not have a browserslist file. However, consider adding one, as the benefit of removing ES5 builds is that app build times are reduced, most noticeably in production builds (ionic build --prod). When I first implemented this, I saw the build reduce from 98 seconds down to 35 seconds. In subsequent builds, I commonly saw another 20 to 40 seconds shaved off.

To get the latest/greatest browserslist configuration, run ng new outside of your app project to create a temporary new Angular project, then copy the browserslist file into your existing project. Or, borrow Ionifits’ file, which looks like:

> 0.5%
last 2 versions
Firefox ESR
not dead
not IE 9-11 # For IE 9-11 support, remove 'not'.

To view a list of the supported browsers designated by the rules defined in the browserslist file, run npx browserslist in the root project directory. For reference, here’s what the above generates:

and_chr 84  
and_ff 68   
and_qq 10.4 
and_uc 12.12
android 81  
baidu 7.12  
chrome 85   
chrome 84   
chrome 83
edge 84
edge 83
edge 18
firefox 80
firefox 79
firefox 78
firefox 77
firefox 68
ios_saf 13.4-13.5
ios_saf 13.3
ios_saf 12.2-12.4
kaios 2.5
op_mini all
op_mob 46
opera 69
opera 68
safari 13.1
safari 13
samsung 12.0
samsung 11.1-11.2

To learn more about browserslist and all available configurations, check out the project’s documentation.

Start Building More Performant Angular Apps

Improving the performance of an Angular app can seem complicated and daunting. Fortunately, leveraging Angular’s out-of-the-box tooling, including configuring size budgets, implementing lazy loading, and adding a browserslist file, goes a long way by doing much of the hard work for you. Keeping the app updated regularly and occasionally using source-map-explorer to identify further optimizations is useful for keeping performance in check over time.

These are just a few examples of Ionic Angular performance optimizations you can make. If you have any additional tips, please share them below.


Matt Netkow