Loading External Libraries from CDN in Angular Application

We often required to use external libraries in Angular applications. For example, libraries like Stripe and PayPal for payment, PDFMake for client-side pdf generation, ExcelJS for client-side excel generation, chart.js for generating beautiful charts and so on.

These external libraries increase the application bundle size when we build our application and so it also increases the application loading time.

Some of the libraries are so large that it creates a huge impact on application performance.

According to 2018 research by Google, 53% of mobile users leave a site that takes longer than three seconds to load.

As Page Load time goes from | Loading external libraries from CDN in Angular
Source : Google/SOASTA Research, 2017.

In this article, we will see How to reduce bundle size and loading time by loading external libraries from CDN in Angular Application?

Before that let me show you a real-world scenario which I had faced, then we will see how I have solved that by loading an external library from CDN.

Recently I had written one article for client-side pdf generation in angular with Pdfmake. In that application, I had used a PDFMake library. I had installed a package from npm. And imported it as below in application component as per the instructions provided in official docs.

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

During application build, this library is get bundled into main.js file and increases the bundle size as shown in below production build bundle.

Production Build Generated with PDFMake | Loading Third Party Libraries from CDN in Angular
Production Build Generated with PDFMake Library
Source Map View for Production build | Loading Third Party Library from CDN in Angular
Source Map View for Production build

As you can see in the source map view generated using source-map-explorer. Pdfmake.js consumes 1.32 MB of space which is 53.6% of the overall main.js and vfs_fonts.js consumes 926.24 KB of space which is 37.5% of overall main.js, in total PDFMake consumes 2.25MB. It is the huge amount of additional space reserved in the initial bundle size. This increases the application bundle size and time latency which is the big concern for application performance.

We can enhance the angular application performance and reduce the bundle size and time latency by loading an external library from CDN.

It is the easiest way to load external libraries from CDN. you just need to add script tag in the < head > section of index.html. And access the global library object exported in the window object.

As shown below I have added pdfmake.js and vfs_fonts.js scripts in index.html​

<!doctype html>
<html lang="en">
 
<head>
  <!-- ... -->
  <script src='https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.59/pdfmake.min.js'></script>
  <script src='https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.59/vfs_fonts.js'></script>
</head>
 
<body>
  <app-root> </app-root>
</body>
 
</html>

We can access the global library loaded from CDN by its global object.

Pdfmake.js exports global object pdfMake in the window object.

You can use this object in components by declaring it on top of the component as shown below.

declare var libraryName: any

You should not import it using an import statement in your TypeScript code (such as import * as $ from ‘jquery’;). If you do, you’ll end up with two different copies of the library: one imported as a global library, and one imported as a module.​

–  Source : Using global libraries inside Angular app

 

import { Component } from '@angular/core';
declare let pdfMake: any ;
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
 generatePdf(){
  const documentDefinition = { content: 'This is an sample PDF printed with pdfMake' };
  pdfMake.createPdf(documentDefinition).open();
 }
}

Now build your application and see the result here.

This approach is used when you required to use a library as soon as the application is get loaded.

In most of the scenario’s external library is not required at the initial level. For example, libraries like Stripe and PayPal are only required at the time of checkout, we can load this type of libraries at runtime.

In this approach, we will add the script element dynamically in head section. By using document.createElement('script'). For easy access, I have created a separate service called ScriptService in an Angular app, which includes the loadScript() method to create a script element. as below

import { Injectable } from '@angular/core';
interface Scripts {
  name: string;
  src: string;
}
export const ScriptStore: Scripts[] = [
  { name: 'pdfMake', src: 'https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.59/pdfmake.min.js' },
  { name: 'vfsFonts', src: 'https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.59/vfs_fonts.js' }
];

@Injectable({
  providedIn: 'root'
})
export class ScriptService {

  private scripts: any = {};

  constructor() {
    ScriptStore.forEach((script: any) => {
      this.scripts[script.name] = {
        loaded: false,
        src: script.src
      };
    });
  }

  load(...scripts: string[]) {
    const promises: any[] = [];
    scripts.forEach((script) => promises.push(this.loadScript(script)));
    return Promise.all(promises);
  }

  loadScript(name: string) {
    return new Promise((resolve, reject) => {
      // resolve if already loaded
      if (this.scripts[name].loaded) {
        resolve({ script: name, loaded: true, status: 'Already Loaded' });
      } else {
        // load script
        const script = document.createElement('script');
        script.type = 'text/javascript';
        script.src = this.scripts[name].src;
        script.onload = () => {
          this.scripts[name].loaded = true;
          console.log(`${name} Loaded.`);
          resolve({ script: name, loaded: true, status: 'Loaded' });
        };
        script.onerror = (error: any) => resolve({ script: name, loaded: false, status: 'Loaded' });
        document.getElementsByTagName('head')[0].appendChild(script);
      }
    });
  }
}

ScriptService source is inspired from the StackOverflow answer by RahulKumar for the Question: How to load external scripts dynamically in Angular ?

In this service, we have created a ScriptStore where we will store the library name and source of the library. source can be the CDN link or scripts from the assets folder.

Now, To dynamically load external library we just need to call the loadScript() method or load() method of ScriptService. here load() is used for loading multiple libraries in a single function call. As shown below :

import { Component } from '@angular/core';
import { ScriptService } from './script.service';
declare let pdfMake: any;
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {

  constructor(private scriptService: ScriptService) {
    console.log('Loading External Scripts');
    this.scriptService.load('pdfMake', 'vfsFonts');
  }

  generatePdf() {
    const documentDefinition = { content: 'This is an sample PDF printed with pdfMake' };
    pdfMake.createPdf(documentDefinition).open();
  }

}

Now build your application and see the result.

After loading an external library from CDN you can see the production build bundle size as below :

Production build after Loading PDFMake from CDN
Production build after Loading PDFMake from CDN

You can see here, size of main.js file in production build is drastically reduced from 2.35MB to 214 KB because now we are loading external library (PDFMake) from CDN. In below source map view you can see PDFMake is completely gone from the bundle.

Source Map View of Production Build after Loading PDFMake from CDN
Source Map View of Production Build after Loading PDFMake from CDN

You can check the network tab to verify whether the external library loaded or not. You can see below, pdfmake.js and vfs_fonts.js are loaded separately.

Network Tab Snapshot | Loading External Libraries from CDN in Angular
Network Tab Snapshot

In this article, we have seen two ways to load external library from CDN in Angular application. This will reduce your application bundle size, which will result in improvement in loading time.

Other than CDN you can use the above approach to load external scripts from assets.

I hope you like this article, please provide your valuable feedback and suggestions in below comment section🙂.

For more updates, Follow us 👍 on NgDevelop Facebook page.