Introduction

Welcome to Chapter 8! In our journey through Angular system design, we’ve explored how to structure applications, manage state, and build robust routing. But what good is a perfectly designed application if it’s slow and unresponsive? This chapter dives deep into Performance Budgeting and Optimization, a critical aspect of delivering high-quality user experiences.

Performance isn’t just a “nice-to-have”; it’s a fundamental requirement. Slow applications lead to frustrated users, higher bounce rates, lower conversion rates, and even poor search engine rankings. As architects, we need to proactively define performance targets, measure against them, and implement strategies to keep our applications fast and fluid.

By the end of this chapter, you’ll understand what performance budgets are, why they’re essential, and how to implement various Angular-specific and general web optimization techniques. We’ll cover everything from Core Web Vitals to lazy loading, change detection strategies, and even integrating progressive web app (PWA) capabilities for offline resilience. Make sure you’re comfortable with basic Angular application structure, components, services, and routing, as we’ll be building upon that foundation.

Core Concepts: Building a Fast Angular Application

Let’s begin by understanding the foundational ideas behind performance in modern web applications.

What is a Performance Budget?

Imagine you’re building a house. You have a budget for materials, labor, and time. If you exceed that budget, the project runs into trouble. A performance budget is similar but applied to your application’s performance metrics. It’s a set of measurable thresholds that your application must not exceed to be considered “performant.”

Why it’s Important: Without a performance budget, it’s easy for an application’s performance to degrade gradually over time. New features often mean new code, new dependencies, and potentially more assets. If left unchecked, these additions can bloat your application, slowing down load times and responsiveness, often without anyone noticing until it’s too late. A budget acts as an early warning system, preventing performance regressions before they impact users.

How it Works: You define specific limits for various metrics, such as:

  • Bundle size: The total size of JavaScript, CSS, and other assets downloaded by the browser.
  • First Contentful Paint (FCP): How long it takes for the first piece of content to appear on the screen.
  • Largest Contentful Paint (LCP): How long it takes for the largest content element (image or block of text) to become visible.
  • Interaction to Next Paint (INP): Measures the latency of all user interactions with a page. This is a crucial metric for responsiveness.
  • Cumulative Layout Shift (CLS): Measures the visual stability of a page by quantifying unexpected layout shifts.
  • Total Blocking Time (TBT): The sum of all time periods between FCP and TTI (Time to Interactive) where the main thread was blocked for long enough to prevent input responsiveness.

These budgets are often enforced during your build process, failing the build or issuing warnings if thresholds are breached.

Real-world Failure Scenario: A rapidly growing e-commerce startup initially had a very fast Angular application. As the team scaled and added more features (chat widgets, analytics scripts, third-party integrations, complex product configurators), each new addition incrementally increased the bundle size and main thread blocking time. Without a performance budget, these small degradations went unnoticed until a major marketing campaign launched. The influx of new users, many on slower networks or older devices, experienced extremely slow load times and unresponsive UIs, leading to a high bounce rate and lost sales, tarnishing the brand’s first impression.

Key Performance Metrics: Core Web Vitals & Beyond

Modern web performance focuses heavily on user-centric metrics. Google’s Core Web Vitals are a set of standardized metrics that quantify the user experience of a page, focusing on loading, interactivity, and visual stability.

  • Largest Contentful Paint (LCP): (Loading performance) Ideally, less than 2.5 seconds. This is about how quickly the main content of your page is loaded and rendered.
  • Interaction to Next Paint (INP): (Interactivity) Ideally, less than 200 milliseconds. This measures the responsiveness of your page to user input. It replaces First Input Delay (FID) as of March 2024, offering a more comprehensive measure of overall page responsiveness.
  • Cumulative Layout Shift (CLS): (Visual stability) Ideally, a score of less than 0.1. This quantifies how much content unexpectedly shifts around on the page as it loads, which can be incredibly frustrating for users.

Beyond Core Web Vitals, other important metrics include:

  • First Contentful Paint (FCP): The time from when the page starts loading to when any part of the page’s content is rendered on the screen.
  • Total Blocking Time (TBT): The total time between FCP and Time to Interactive where the main thread is blocked, indicating how much the page is unresponsive to user input.
  • Time to Interactive (TTI): The time it takes for the page to become fully interactive, meaning JavaScript has loaded and the page can respond quickly to user input.
  • Bundle Size: The total size of all JavaScript and CSS files. Smaller is almost always better.
  • Request Count: The number of HTTP requests made by the browser. Fewer requests generally mean faster loading.

Angular’s Built-in Performance Tools

Angular provides several features and tools out-of-the-box to help you optimize your applications:

  1. Production Build (ng build --configuration=production): This command applies a suite of optimizations:

    • Ahead-of-Time (AOT) Compilation: Angular templates are compiled into highly optimized JavaScript code during the build process, reducing runtime overhead and improving initial render times.
    • Tree-shaking: Unused code (from your application and third-party libraries) is removed from the final bundle, significantly reducing its size.
    • Minification & Uglification: Code is compressed and variable names are shortened to further reduce file sizes.
    • Bundling: All application code is combined into a few highly optimized bundles.
  2. Differential Loading (Angular 8+): While less critical with modern browser evergreen updates, Angular used to automatically create two bundles: one for modern browsers (ES2015+) and one for legacy browsers (ES5). Modern browsers would download smaller, more efficient code. This is largely handled by modern browser support for ES modules now.

  3. Lazy Loading Modules & Standalone Components: This is a cornerstone of performance optimization for larger Angular applications. Instead of loading all parts of your application when it first starts, you only load the code needed for the currently active route or feature.

Optimization Strategies: Making Your Angular App Fly

Let’s explore practical strategies to implement these concepts.

1. Lazy Loading Modules & Standalone Components

Why it’s Important: For larger applications, loading all features upfront means users download a lot of code they might not even use during their session. Lazy loading dramatically reduces the initial bundle size, leading to faster FCP and TTI.

How it Works: Angular’s router can be configured to load modules or standalone components only when a user navigates to a specific route.

Architectural Diagram (Conceptual Flow):

flowchart TD User[User Navigates] --> Browser[Browser Request] Browser --> InitialBundle[Downloads Initial Bundle] InitialBundle --> AppLoaded[App Shell Renders] AppLoaded --> UserClicks[User Clicks on Admin Link] UserClicks --> Router[Angular Router] Router --> LazyLoad[Dynamically Imports Admin Module Components] LazyLoad --> AdminBundle[Downloads Admin Feature Bundle] AdminBundle --> AdminView[Admin View Renders]

Real-world Failure Scenario: A large enterprise portal, built without lazy loading, had a single massive JavaScript bundle. When users accessed the portal, they had to download gigabytes of code, including features for departments they didn’t belong to. This resulted in initial page loads often exceeding 10-15 seconds, particularly on corporate VPNs or slower internet connections, leading to significant user frustration and calls to IT support.

2. Change Detection Optimization

Angular’s change detection mechanism is powerful but can be a performance bottleneck if not managed correctly. By default, Angular checks every component in the component tree whenever data might have changed.

OnPush Change Detection Strategy: This strategy tells Angular to only run change detection for a component (and its children) if:

  • Its input properties (@Input()) have changed (specifically, if the reference to the object/primitive has changed).
  • An event originated from the component or one of its children.
  • async pipe emits a new value.
  • Change detection is explicitly triggered (e.g., ChangeDetectorRef.detectChanges()).

Why it’s Important: By marking components as OnPush, you significantly reduce the number of checks Angular performs, especially in large, complex component trees with many nested components. This leads to a much more responsive UI, particularly for frequently updated data.

How it Works: Apply changeDetection: ChangeDetectionStrategy.OnPush in your component decorator. When using OnPush, ensure you treat input data as immutable. If you modify an object directly (mutation), Angular won’t detect the change. Instead, create new instances of objects or arrays.

Real-world Failure Scenario: An analytics dashboard application displayed numerous real-time charts and data tables. Many nested components were bound to data streams. Without OnPush strategy, every incoming data update (even if only one chart’s data changed) triggered a full change detection cycle across all components. This caused the UI to become sluggish, with noticeable delays between data updates and visual refreshes, leading to a poor user experience and making the dashboard feel unresponsive.

3. Bundle Size Reduction

Beyond lazy loading and tree-shaking (which are handled by the production build), you can do more to shrink your application’s footprint.

  • Image Optimization:
    • Lazy load images: Use loading="lazy" attribute or an Intersection Observer to load images only when they enter the viewport.
    • Responsive images: Provide different image sizes using <picture> and srcset to serve the most appropriate image based on device characteristics.
    • Modern formats: Convert images to WebP or AVIF formats, which offer superior compression compared to JPEG or PNG.
  • Font Optimization:
    • Font subsetting: Include only the characters you need from a font, especially for icons or specific headlines.
    • font-display CSS property: Control how fonts are loaded and displayed to prevent invisible text during loading (swap is a common choice).
  • Remove Unused Code & Libraries: Regularly audit your dependencies. If you’re only using a small part of a large library, consider if there’s a smaller, more focused alternative or if you can import only the specific functions you need.
  • Gzip/Brotli Compression: Ensure your web server (e.g., Nginx, Apache, or CDN) is configured to serve assets with Gzip or Brotli compression. This significantly reduces transfer sizes.

4. Server-Side Rendering (SSR) & Pre-rendering with Angular Universal

Why it’s Important: Pure SPAs (Single Page Applications) often present a blank page initially while JavaScript loads and renders the content. This negatively impacts FCP, LCP, and SEO (though modern search engines are better at crawling SPAs, SSR provides a robust fallback). SSR renders the initial HTML on the server, sending a fully formed page to the browser. Pre-rendering does this at build time for static routes.

How it Works: Angular Universal allows you to render your Angular application on the server. When a user requests a page, the server executes the Angular app, generates the static HTML, and sends it to the browser. The browser then “hydrates” this static content with the client-side Angular application, making it interactive.

Architectural Diagram (SSR Request Flow):

flowchart TD User[User Request URL] --> Browser Browser --> WebServer[Web Server] WebServer --> AngularUniversal[Angular Universal App] AngularUniversal --> RenderHTML[Renders Angular App to HTML] RenderHTML --> WebServer WebServer --> Browser[Sends HTML + JS Bundle] Browser --> DisplayHTML[Displays Content Immediately] DisplayHTML --> DownloadJS[Downloads & Executes JS Bundle] DownloadJS --> Hydrate[Angular Hydrates Static HTML] Hydrate --> InteractiveApp[Fully Interactive Angular App]

Real-world Failure Scenario: A content-heavy marketing website built as a pure SPA struggled to rank highly on search engines despite having excellent content. Even with client-side rendering, search engine crawlers sometimes had difficulty fully indexing all dynamic content, leading to lower visibility. Implementing Angular Universal for SSR immediately boosted their SEO performance, improved initial page load times, and reduced bounce rates from organic search traffic.

5. Web Workers

Why it’s Important: JavaScript is single-threaded, meaning heavy computations can block the main thread, making your UI unresponsive. Web Workers allow you to run scripts in a background thread, offloading CPU-intensive tasks from the main UI thread.

How it Works: Angular CLI provides schematics to generate web workers (ng generate web-worker). You can then perform complex calculations, data processing, or image manipulation in the worker, and communicate results back to the main thread without freezing the UI.

Real-world Failure Scenario: A financial application had a complex data visualization component that performed extensive real-time calculations on large datasets. When users interacted with this component, the UI would often freeze for several seconds, leading to a frustrating experience. Moving these calculations to a web worker allowed the UI to remain responsive while the heavy lifting happened in the background, significantly improving perceived performance.

6. Caching Layers (Browser & Service Worker for PWAs)

Why it’s Important: Caching is crucial for fast subsequent loads and offline capabilities.

  • Browser Cache (HTTP Caching): Reduces network requests by storing assets locally.
  • Service Workers (PWAs): Provide granular control over network requests, enabling powerful caching strategies, offline access, and push notifications.

How it Works:

  • HTTP Caching: Configure your web server to send appropriate Cache-Control headers for your static assets (e.g., max-age, immutable).
  • Angular Service Worker (PWA): Angular CLI can easily add service worker support (ng add @angular/pwa). This registers a service worker that intercepts network requests, caches assets, and serves them from the cache when offline or on subsequent visits.

Architectural Diagram (Request Flow with Service Worker):

flowchart TD User[User Request] --> Browser Browser --> ServiceWorker[Service Worker] ServiceWorker --> Browser[Serves from Cache] ServiceWorker --> Network[Network Request] Network --> WebServer WebServer --> ServiceWorker[Response from Server] ServiceWorker --> Browser[Serves to Browser & Caches]

Real-world Failure Scenario: A field service application used by technicians in remote areas frequently lost connectivity. Without PWA capabilities and robust caching, the application became unusable offline, forcing technicians to stop work until a connection was re-established. Implementing an Angular Service Worker allowed critical parts of the application to function offline, caching forms, previous data, and assets, ensuring business continuity even in intermittent network conditions.

7. Performance Budgets in angular.json

Angular CLI allows you to define performance budgets directly in your angular.json file. This is your primary tool for enforcing performance targets during the development and build process.

Why it’s Important: This provides an automated safety net. If a developer accidentally adds a large library or new feature that pushes the bundle size beyond the defined limit, the build process will warn or fail, forcing them to address the performance impact immediately.

How it Works: Inside the build configuration for your project in angular.json, you’ll find a budgets array. Each object in this array defines a budget for a specific type of asset.

// angular.json (simplified excerpt)
{
  "projects": {
    "my-app": {
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            // ... other options ...
          },
          "configurations": {
            "production": {
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "500kb",
                  "maximumError": "700kb"
                },
                {
                  "type": "anyComponentStyle",
                  "maximumWarning": "2kb",
                  "maximumError": "4kb"
                },
                {
                  "type": "any",
                  "maximumWarning": "1.5mb",
                  "maximumError": "2mb"
                }
              ]
            }
          }
        }
      }
    }
  }
}
  • type: Specifies what kind of asset the budget applies to (initial for the main bundle, anyComponentStyle for component CSS, any for total bundle size, etc.).
  • maximumWarning: A warning is issued if the budget is exceeded.
  • maximumError: The build fails if the budget is exceeded.

Step-by-Step Implementation: Setting up a Basic Performance Budget and Lazy Loading

Let’s put some of these concepts into practice. We’ll start with a fresh Angular application (or you can adapt an existing one) and implement a basic performance budget and then a lazy-loaded feature module.

Step 1: Create a New Angular Application

If you don’t have one, let’s create a new Angular project. We’ll assume Angular CLI v17.x or later is installed.

  1. Open your terminal or command prompt.

  2. Run the following command:

    ng new angular-performance-app --standalone --routing --style=css
    cd angular-performance-app
    
    • --standalone: Uses standalone components, which is the modern Angular approach.
    • --routing: Sets up basic routing.
    • --style=css: Uses plain CSS.
  3. Once the project is created, open it in your favorite code editor (like VS Code).

Step 2: Configure Performance Budgets in angular.json

Let’s add some strict budgets to our angular.json to ensure our application remains lean.

  1. Open angular.json in your project root.

  2. Navigate to projects -> angular-performance-app -> architect -> build -> configurations -> production.

  3. Locate the budgets array (if it exists, modify it; if not, add it). We’ll set a very tight budget for the initial bundle to make sure we feel its effect.

    // angular.json (excerpt)
    {
      "projects": {
        "angular-performance-app": {
          "architect": {
            "build": {
              "builder": "@angular-devkit/build-angular:browser",
              "options": {
                // ... (existing options) ...
              },
              "configurations": {
                "production": {
                  // ... (existing production options) ...
                  "budgets": [
                    {
                      "type": "initial",
                      "maximumWarning": "200kb", // A bit tight for a new app, but good for demo!
                      "maximumError": "250kb"
                    },
                    {
                      "type": "anyComponentStyle",
                      "maximumWarning": "2kb",
                      "maximumError": "4kb"
                    },
                    {
                      "type": "any",
                      "maximumWarning": "500kb",
                      "maximumError": "700kb"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    }
    

    Explanation:

    • We’re setting a 200kb warning and 250kb error for the initial bundle. This is quite small for a real app but will help us see the budget in action.
    • anyComponentStyle ensures individual component styles don’t get too large.
    • any covers the total size of all bundles.
  4. Try building the app in production mode:

    ng build --configuration=production
    

    You might already see warnings or even errors about exceeding the budget, even for a fresh app. This is because a default Angular app, even standalone, includes a fair amount of code. This immediately tells you that you need to be mindful of your initial bundle.

Step 3: Implement Lazy Loading for a Feature

Now, let’s create a new feature that will be lazy-loaded.

  1. Generate a standalone component for our “dashboard” feature:

    ng generate component dashboard --standalone --skip-tests
    

    This creates src/app/dashboard/dashboard.component.ts and its associated files.

  2. Generate another standalone component for an “admin” feature:

    ng generate component admin --standalone --skip-tests
    

    This creates src/app/admin/admin.component.ts.

  3. Configure Lazy Loading in app.routes.ts: Open src/app/app.routes.ts. This is where our main application routes are defined. We’ll add routes for our new components and configure them to be lazy-loaded.

    // src/app/app.routes.ts
    import { Routes } from '@angular/router';
    import { HomeComponent } from './home/home.component'; // Assuming you have a home component
    
    export const routes: Routes = [
      {
        path: '',
        component: HomeComponent // A basic home route
      },
      {
        path: 'dashboard',
        loadComponent: () => import('./dashboard/dashboard.component').then(m => m.DashboardComponent)
      },
      {
        path: 'admin',
        loadComponent: () => import('./admin/admin.component').then(m => m.AdminComponent)
      },
      {
        path: '**', // Wildcard route for any unmatched URL
        redirectTo: ''
      }
    ];
    

    Explanation:

    • loadComponent: This is the key for lazy loading standalone components.
    • () => import('./dashboard/dashboard.component').then(m => m.DashboardComponent): This dynamically imports the dashboard.component.ts file only when the /dashboard route is accessed. The .then(m => m.DashboardComponent) ensures we return the component class itself.
  4. Add some basic content to dashboard.component.html and admin.component.html

    • dashboard.component.html:
      <h2>Welcome to the Dashboard!</h2>
      <p>This content is lazy-loaded.</p>
      
    • admin.component.html:
      <h2>Admin Panel</h2>
      <p>Only authorized users should see this. It's also lazy-loaded!</p>
      
  5. Add navigation links to app.component.html: To navigate to these routes, let’s add some links to our app.component.html.

    • First, ensure HomeComponent exists. If you created a new app, you might need to generate it: ng generate component home --standalone --skip-tests.
    • Then, add it to app.routes.ts as shown above and import it into app.component.ts.
    • Open src/app/app.component.ts and import RouterOutlet and RouterLink if not already there, and HomeComponent.
    // src/app/app.component.ts
    import { Component } from '@angular/core';
    import { RouterOutlet, RouterLink } from '@angular/router'; // Ensure RouterLink is imported
    import { HomeComponent } from './home/home.component'; // Assuming you generated this
    
    @Component({
      selector: 'app-root',
      standalone: true,
      imports: [RouterOutlet, RouterLink, HomeComponent], // Add RouterLink here
      template: `
        <nav>
          <a routerLink="/">Home</a> |
          <a routerLink="/dashboard">Dashboard</a> |
          <a routerLink="/admin">Admin</a>
        </nav>
        <hr>
        <router-outlet></router-outlet>
      `,
      styleUrl: './app.component.css'
    })
    export class AppComponent {
      title = 'angular-performance-app';
    }
    
  6. Build the application in production mode again:

    ng build --configuration=production --stats-json
    

    Observe the Output: You should now see multiple JavaScript bundles in the output, including separate chunks for dashboard.component.js and admin.component.js. The initial bundle size should be smaller than if these components were eagerly loaded. If your budget was strict, you might still get warnings, but the initial chunk should be closer to the budget.

  7. Serve the application and test lazy loading:

    ng serve
    

    Open your browser to http://localhost:4200.

    • Open your browser’s developer tools (F12), go to the “Network” tab, and filter by “JS”.
    • When you first load the page, you’ll see the main bundles.
    • Now, click on “Dashboard”. You should observe a new JavaScript file (e.g., dashboard-dashboard-component.js or similar) being downloaded.
    • Click on “Admin”. Another new JavaScript file (e.g., admin-admin-component.js) will be downloaded.

    This demonstrates that these features are only loaded when needed, reducing the initial load time of your application.

Mini-Challenge: Optimize for OnPush

You’ve seen how lazy loading helps with initial load. Now, let’s practice optimizing rendering performance with OnPush change detection.

Challenge:

  1. Create a new standalone component called data-display that takes an @Input() property called items (an array of strings or objects).
  2. Make this data-display component use OnPush change detection.
  3. In your HomeComponent, display the data-display component and pass it an array of data.
  4. Add a button in HomeComponent that, when clicked, attempts to mutate (change directly without creating a new reference) one of the items in the items array that is passed to data-display.
  5. Add another button that replaces the items array with a new array (even if the content is the same, ensure it’s a new reference).
  6. Observe the behavior: Which button triggers an update in the data-display component, and why?

Hint:

  • Remember to import ChangeDetectionStrategy and Input from @angular/core.
  • For mutation, you might do this.items[0] = 'New Item';.
  • For replacement, you might do this.items = [...this.items]; or this.items = this.items.map(item => item); or create a completely new array.

What to Observe/Learn: You should observe that only the button that replaces the items array (creating a new reference) causes the data-display component to re-render. The button that mutates the array directly will not trigger a re-render because OnPush only checks for input reference changes. This highlights the importance of immutability when using OnPush.

Common Pitfalls & Troubleshooting

Performance optimization can be tricky. Here are some common issues and how to approach them:

  1. Overly Aggressive Budgets Blocking Development:

    • Pitfall: Setting maximumError budgets too low initially can constantly break builds, frustrating developers.
    • Troubleshooting: Start with maximumWarning for initial budgets. Gradually lower them as you optimize. Use tools like webpack-bundle-analyzer (ng build --stats-json then use npx webpack-bundle-analyzer dist/angular-performance-app/browser/stats.json) to visualize bundle contents and identify large dependencies.
  2. Not Measuring Real-World Performance:

    • Pitfall: Relying solely on local development metrics or Lighthouse scores in a lab environment. These are good starting points but don’t reflect actual user conditions.
    • Troubleshooting: Implement Real User Monitoring (RUM). Tools like Google Analytics, Firebase Performance Monitoring, or dedicated RUM services (e.g., Dynatrace, New Relic) collect performance data from actual users, across various devices, networks, and locations. This “field data” is crucial for understanding true user experience.
  3. Ignoring Third-Party Script Impact:

    • Pitfall: Embedding numerous third-party scripts (analytics, ads, chat widgets, social media buttons) without understanding their performance implications. These scripts can block the main thread, make many network requests, and contribute to layout shifts.
    • Troubleshooting: Audit all third-party scripts. Use tools like Lighthouse or WebPageTest to identify their impact. Consider lazy loading scripts, deferring their execution (async or defer attributes), or self-hosting critical functionality if possible. Prioritize scripts that are essential for core functionality.
  4. Incorrect OnPush Implementation (Mutating Objects):

    • Pitfall: Applying OnPush but still directly modifying objects or arrays passed as @Input() properties. This leads to parts of the UI not updating correctly, creating subtle bugs.
    • Troubleshooting: When using OnPush, always create new references for input properties that are objects or arrays when their content changes. For example, instead of myArray.push(newItem), use myArray = [...myArray, newItem];. For objects, use myObject = { ...myObject, newProp: 'value' };.
  5. Large Images and Unoptimized Fonts:

    • Pitfall: Developers often overlook media assets and fonts, which can be significant contributors to page weight and slow loading.
    • Troubleshooting: Implement an image optimization pipeline (e.g., using tools like ImageOptim, or build-time plugins). Use modern image formats (WebP, AVIF). Lazy load images. For fonts, subset them to include only necessary characters and use font-display: swap to prevent invisible text.

Summary

Phew! We’ve covered a lot of ground in this chapter, but these concepts are fundamental to building truly high-performing Angular applications.

Here are the key takeaways:

  • Performance Budgets are Essential: Define measurable targets for metrics like bundle size, FCP, LCP, and INP to prevent performance degradation over time. Enforce these budgets in angular.json.
  • Core Web Vitals Guide User Experience: Focus on LCP (loading), INP (interactivity), and CLS (visual stability) as key indicators of a good user experience.
  • Leverage Angular’s Built-in Optimizations: Use production builds for AOT compilation, tree-shaking, and minification.
  • Lazy Loading is Your Friend: Dynamically load modules and standalone components only when needed to reduce initial bundle size and speed up first paint.
  • Optimize Change Detection with OnPush: Use the OnPush strategy for components to reduce unnecessary re-renders, ensuring you work with immutable data for inputs.
  • Shrink Your Assets: Optimize images (lazy loading, responsive, modern formats), fonts (subsetting, font-display), and eliminate unused code to reduce overall page weight.
  • Consider SSR/Pre-rendering with Angular Universal: Improve FCP, LCP, and SEO by rendering your application on the server.
  • Use Web Workers for Heavy Tasks: Offload CPU-intensive computations to background threads to keep your UI responsive.
  • Implement Robust Caching: Utilize HTTP caching and Angular Service Workers (PWAs) for faster subsequent loads and offline capabilities.

By integrating these performance considerations into your system design from the outset, you’ll be well on your way to building Angular applications that are not just functional, but also fast, reliable, and delightful for your users.

Next up, in Chapter 9, we’ll explore Offline-First Resilience and Graceful Degradation, building on our understanding of service workers to create applications that work even when the network doesn’t.

References


This page is AI-assisted and reviewed. It references official documentation and recognized resources where relevant.