How to add code splitting to your Angular app


As web apps grow in their capabilities, we also see an increase in their bundle sizes. At one hand we need to ship more code for cool new features. On the other hand, we also need third party libraries and their code for everything to work.

The result? Huge initial bundle size and hence, more load times. This in turn will lead to more users turning away from your app and consequently a loss in business. Code splitting is a useful technique to deal with this problem.

What is code splitting?

Code splitting is the splitting of code into various bundles or components which can then be loaded on demand or in parallel.

- MDN Web Docs

In other words, you divide your application into smaller bundles and keep your initial bundle size as small as possible. The bundle for a feature then is downloaded when the user needs it. Another word used for this is lazy loading.

In this article, I'm going to cover three ways to code split your Angular application - at the level of module, component or package.

Module level code splitting

This is one of the most well documented ways of code splitting an Angular app.

A module in Angular usually refers to a distinct section which can be separated by a navigation route.

For example, if we're building up a school management app, we could have a module for managing academics, a module for sports etc.

Suppose we call our academic module as AcademicsModule. After creating it in our app, the next step would be to add a route for it. All we need to do is to specify the following in our route configuration.

const routes = [
  {
    path: "academics",
    loadChildren: () =>
      import("./academics/academics.module").then((m) => m.AcademicsModule),
  },
];

Make sure your AcademicsModule is not imported in any of your other modules, otherwise it might be included in your main bundle. With the code above, webpack will recognize the import statement and will create a separate bundle for the module containing the code and its dependencies.

The AcademicsModule will then be lazy loaded, as soon as the user navigates to the /academics route.

For more details about code splitting on modules, check out this excellent piece by Minko Gechev on web.dev.

https://web.dev/route-level-code-splitting-in-angular/

Component Level Code Splitting

Components are the building blocks of an Angular app and higher in granularity than modules. While we should always strive to make components simple and reusable, sometimes this is not possible. Your component might be using lots of services or having sub components inside it. Including such a component in the initial bundle will unnecessarily add bloat to your app.

How do you code split a component in Angular?

Well, it wasn't that easy before. But with Angular Ivy, it has got much more convenient. As with the modules, we'll use the webpack dynamic import statement.

Dynamic imports is a webpack feature, through which webpack can identify modules, components etc. which need to be lazy loaded and create separate bundles for them at compile time.

At runtime, the import statement resolves to a Promise, so an app should wait on it using the async/await combo or Promise.then. The imported module can then be used in your code.

For more information, check out the webpack official docs for this here.

Have a look at the code sample below for a ReportCardComponent, for our School Management App.

  dynamicComponent: Type<ReportCardComponent>;

  constructor() { }

  async loadComponent(): Promise<any> {
    const imported = await import('./report-card/report-card.component');
    this.dynamicComponent = imported.ReportCardComponent;
  }

And then your template file.

<ng-template [ngComponentOutlet]="dynamicComponent"> </ng-template>

So what we're doing here is declaring a component type variable - which will contain our lazy loaded component. We're then using a function to load the ReportCardComponent which we want to lazy load and assign it to this variable.

Lastly, we use ngComponentOutlet directive provided by Angular and specify our variable there. This will add the component to our UI as soon as it loads. The function can be called whenever we want - no need to associate it with a route change! This makes it a very flexible way of dynamically loading parts of our app in the form of components.

And there is a lot more to it which is out of the scope of this post. If you're intrigued though, check out this excellent post by Netanal Basal. He explains the different ways of dynamically loading components in this way.

https://netbasal.com/welcome-to-the-ivy-league-lazy-loading-components-in-angular-v9-e76f0ee2854a

Package level code splitting

Last, but not the least, we can also apply code splitting at the level of an npm package. Often while building up a feature we resort to using an existing third party javascript library.

This can be handy and saves you time!

But if the dependency has a large size, it can add to your initial load time. This is especially problematic if the only place you're using the dependency is in a specific part of the app. Why should we then load this upfront for all users, most of whom will not even need it?

It's a no-brainer that such libraries should be lazy loaded when the user needs them. The typical way for developers to include javascript libraries is to use a separate service and use a static import statement at the top of the file like so (this is for the PDFMake library).

import pdfMake from "pdfmake/build/pdfmake";
import pdfFonts from "pdfmake/build/vfs_fonts";
pdfMake.vfs = pdfFonts.pdfMake.vfs;

The problem here is this will add the library to the main bundle. If you want to apply code splitting, use the following way instead.

export class PdfService {
  pdfMake: any;

  constructor() {}

  async loadPdfMaker() {
    if (!this.pdfMake) {
      const pdfMakeModule = await import("pdfmake/build/pdfmake");
      const pdfFontsModule = await import("pdfmake/build/vfs_fonts");
      this.pdfMake = pdfMakeModule.default;
      this.pdfMake.vfs = pdfFontsModule.default.pdfMake.vfs;
    }
  }
}

This ensures the PDFMake library will only be loaded when the loadPdfMaker function is called, whenever the user needs it. A loader can be added to give a visual indication to the user. Note also how the loaded library is saved as a service variable for future use.

This technique can be used with virtually any package out there, making it very flexible in case you have large libraries, dragging down your app.

For more details and a detailed working code example, check out my post sometime ago on adding PDFMake to your Angular app in this way.

https://zoaibkhan.com/blog/generate-pdf-in-angular-with-pdfmake/

Conclusion

In conclusion, these three code splitting techniques in Angular open a whole door of possibilities. We can have a slim and smart initial app for new users, while loading complex features on demand as and when our users need them.

Where to use modules, components and packages, depends on you as a developer and to some extent, on the structure of your app. Use it well and have a great Lighthouse score as a prize!

I hope this post was helpful in your coding journey.

Thanks for reading.

Bye :)

Check out my Angular and Firebase Authentication crash course

thumbnail
Angular Firebase Authentication: Create Full Sign Up App

Use Angular 16, Angular Material and Firebase Authentication, Firestore and Storage to create a complete Sign Up App!

You may also like...