Angular
·5 min read·↗

Integrating Third-Party Scripts and CSS into Your Angular App

Main cover illustration for article: Integrating Third-Party Scripts and CSS into Your Angular App
Summarize with AI:

When building real-world applications in Angular, you frequently need to integrate third-party libraries: analytics tracking scripts (Google Analytics, Mixpanel), payment gateways (Stripe, PayPal SDKs), charting libraries, or existing CSS frameworks.

While you could manually paste <script> tags into index.html, doing so bypasses the Angular build pipeline, compromises bundle optimization, and often causes Server-Side Rendering (SSR) crashes.

⚡ Using Modern Angular (v17+)? Jump directly to Modern Angular: SSR Safety with afterNextRender and Standalone Schemas to see how to load scripts safely in hydration-enabled apps.

In this guide, we will explore how to configure third-party assets in angular.json, create separate named bundles, and dynamically load external scripts safely in modern Angular.

Let's begin by looking at the built-in angular.json pipeline.


1. Build-Time Integration with angular.json 📦

The cleanest way to include global CSS and JavaScript files that must be available application-wide is through the workspace configuration file angular.json.

Navigate to architect > build > options and locate the styles and scripts arrays:

{
  "architect": {
    "build": {
      "options": {
        "styles": [
          "src/styles.css",
          "node_modules/bootstrap/dist/css/bootstrap.min.css"
        ],
        "scripts": [
          "node_modules/chart.js/dist/chart.umd.js"
        ]
      }
    }
  }
}

When you run ng build, the Angular CLI automatically minifies, tree-shakes (where applicable), hashes, and bundles these files into your initial distribution.

Now, what if you have a heavy script that is only used on a specific page? Let's see how to create separate named bundles.


2. Separate Named Bundles for Heavy Scripts 🗂️

If you have a large library (like a 3D canvas viewer or specialized uploader) that you do not want to bloat your initial main.js bundle with, you can configure it as a separate chunk in angular.json:

{
  "styles": [
    "src/styles.css",
    {
      "input": "src/assets/custom-uploader/uploader.css",
      "inject": true,
      "bundleName": "custom-uploader-styles"
    }
  ],
  "scripts": [
    {
      "input": "src/assets/custom-uploader/uploader.js",
      "inject": false,
      "bundleName": "custom-uploader-script"
    }
  ]
}

Options Explained:

  • input: Path to the source file (relative to workspace root).
  • bundleName: Name of the generated output file (e.g. dist/my-app/custom-uploader-script.js).
  • inject: Set to false if you want to load the script on-demand via JavaScript instead of including it on initial page load.

Now let's see how to dynamically load scripts at runtime in a component.


3. Modern Angular: Dynamic Scripts & SSR Safety with afterNextRender ⚡

With Angular's modern hydration and Server-Side Rendering (SSR) enabled by default, attempting to access document or window directly during component initialization will crash the server renderer with ReferenceError: document is not defined.

In modern Angular (v17+), we use the afterNextRender lifecycle hook to guarantee that script injection only runs once the component has rendered on the client browser.

Reusable Dynamic Script Service

// script-loader.service.ts
import { Injectable, inject } from '@angular/core';
import { DOCUMENT } from '@angular/common';
 
@Injectable({ providedIn: 'root' })
export class ScriptLoaderService {
  private readonly document = inject(DOCUMENT);
  private readonly loadedScripts = new Set<string>();
 
  loadScript(src: string): Promise<void> {
    if (this.loadedScripts.has(src)) {
      return Promise.resolve();
    }
 
    return new Promise((resolve, reject) => {
      const script = this.document.createElement('script');
      script.type = 'text/javascript';
      script.src = src;
      script.async = true;
      script.onload = () => {
        this.loadedScripts.add(src);
        resolve();
      };
      script.onerror = (err) => reject(err);
      this.document.head.appendChild(script);
    });
  }
}

Safe Usage Inside a Standalone Component

// payment-checkout.component.ts
import { Component, inject, afterNextRender, signal } from '@angular/core';
import { ScriptLoaderService } from './script-loader.service';
 
@Component({
  selector: 'app-payment-checkout',
  standalone: true,
  template: `
    <div class="checkout-card p-6 border rounded-xl">
      <h2>Complete Your Purchase</h2>
 
      @if (scriptReady()) {
        <div id="paypal-button-container"></div>
      } @else {
        <p class="text-neutral-500">Loading payment gateway securely...</p>
      }
    </div>
  `
})
export class PaymentCheckoutComponent {
  private readonly scriptLoader = inject(ScriptLoaderService);
  readonly scriptReady = signal(false);
 
  constructor() {
    // Guaranteed to only execute in the browser DOM (SSR Safe!)
    afterNextRender(async () => {
      try {
        await this.scriptLoader.loadScript('https://www.paypal.com/sdk/js?client-id=YOUR_CLIENT_ID');
        this.scriptReady.set(true);
      } catch (err) {
        console.error('Failed to load payment SDK:', err);
      }
    });
  }
}

Now let's look at integrating Web Components / Custom Elements.


4. Using Third-Party Web Components with CUSTOM_ELEMENTS_SCHEMA 🧩

If your third-party script defines a standard Web Component (e.g. <stripe-pricing-table> or <media-player>), Angular's template compiler will emit an error by default because it doesn't recognize the custom HTML tag.

To allow custom elements in modern Standalone components:

import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
 
@Component({
  selector: 'app-custom-widget',
  standalone: true,
  schemas: [CUSTOM_ELEMENTS_SCHEMA], // 👈 Tells Angular to allow unknown custom tags
  template: `
    <stripe-pricing-table
      pricing-table-id="prctbl_12345"
      publishable-key="pk_live_12345"
    ></stripe-pricing-table>
  `
})
export class CustomWidgetComponent {}

Integration Strategy Comparison 📊

Integration MethodBuild-Time or Runtime?SSR Safe?Best Use Case
angular.json (styles/scripts)Build-time✅ YesGlobal CSS frameworks, shared utility scripts
angular.json Named BundlesBuild-time✅ YesLarge secondary modules loaded conditionally
afterNextRender + Dynamic <script>Runtime (Client)✅ YesAnalytics, payment SDKs (Stripe/PayPal), ad trackers
CUSTOM_ELEMENTS_SCHEMATemplate Compile✅ YesThird-party Web Components and Microfrontends

Recap 🛠️

To keep your Angular application fast, maintainable, and SSR-safe when adding third-party code:

  1. Use angular.json for global build-time stylesheets and scripts.
  2. Use inject(DOCUMENT) and afterNextRender() for client-only dynamic script injection (avoiding SSR breakage).
  3. Use CUSTOM_ELEMENTS_SCHEMA whenever embedding custom Web Component tags in your templates.

For more modern Angular architectural guides, check out my articles on How to Use Route Parameters in Angular and How to Use ng-template, ng-container, and ng-content!

Part of the Angular Series

These are my experiences learning and facing my daily challenges working with Angular.

View Entire Series

Frequently Asked Questions

What is the best way to add global third-party CSS or JS scripts in Angular?

The standard and most maintainable approach is configuring the 'styles' and 'scripts' arrays inside your angular.json file under 'architect > build > options'. The Angular CLI compiles, optimizes, hashes, and injects them automatically into your output bundles.

How do I load a third-party script dynamically on demand in a specific component?

You can inject the DOCUMENT token and create a <script> element dynamically inside the afterNextRender() lifecycle hook. This ensures the script only executes in the browser and prevents server-side rendering (SSR) crashes.

How do I use third-party Web Components / Custom Elements in Angular?

To use custom elements without Angular throwing unknown element errors, add 'CUSTOM_ELEMENTS_SCHEMA' to the schemas array in your Standalone component or NgModule declaration.

How do I create a separate named bundle for a heavy third-party script in angular.json?

In angular.json, specify the script as an object with 'input', 'bundleName', and 'inject: false' (or true). Angular CLI will generate a dedicated output chunk that you can lazy load when needed.

Related Articles

Share this article

If you found this guide helpful, consider sharing it with your team or fellow developers.


Real Software. Real Lessons.

I share the lessons I learned the hard way, so you can either avoid them or be ready when they happen.

User avatar
User avatar
User avatar
User avatar
+13K

Join 13,800+ developers and readers.

No spam ever. Unsubscribe at any time.