Angular Performance Optimisation

Sraban Pahadasingh    June 24, 2024 11:07 AM

List of performance optimization steps for Angular applications

1. Event Coalescing: {ngZoneEventCoalescing: true}

  • Description: Aggregates multiple events into a single change detection run.
  • Benefits: Reduces the number of change detection cycles.

2. Zone.js Optimization: {ngZone: 'noop'}

  • Description: Disables Zone.js to manage change detection manually.
  • Benefits: Provides fine-grained control over change detection, reducing unnecessary cycles.

3. OnPush Change Detection

  • Description: Changes the default change detection strategy to OnPush, which only checks for changes when input properties change.
  • Benefits: Limits change detection to specific component updates, improving performance.

    @Component({
      selector: 'app-my-component',
      templateUrl: './my-component.component.html',
      changeDetection: ChangeDetectionStrategy.OnPush
    })

4. Track By Function in ngFor

  • Description: Uses a trackBy function in ngFor to identify items in a collection by a unique identifier.
  • Benefits: Minimizes DOM re-renders by tracking changes more efficiently.

    <div *ngFor="let item of items; trackBy: trackByFn">
      {{item.name}}
    </div>
    trackByFn(index: number, item: any): number {
      return item.id; // unique id corresponding to item
    }

5. Lazy Loading Modules

  • Description: Defers loading of modules until they are needed, rather than loading all modules at startup.
  • Benefits: Reduces initial load time and improves application performance.

    const routes: Routes = [
      {
          path: 'feature',
          loadChildren: () => import('./feature/feature.module').then(m => m.FeatureModule)
      }
    ];

6. Preload Lazy-Loaded Modules

  • Description: Uses Angular’s PreloadAllModules strategy to preload lazy-loaded modules after the initial load.
  • Benefits: Balances initial load time with module availability.

    RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules })

7. AOT Compilation (Ahead of Time)

  • Description: Pre-compiles the application at build time instead of runtime.
  • Benefits: Reduces application size and improves startup performance.

    "angularCompilerOptions": {
      "enableIvy": true,
      "aot": true
    }

8. Tree Shaking

  • Description: Removes unused code during the build process.
  • Benefits: Decreases the size of the final JavaScript bundle, leading to faster load times.

    "optimization": {
      "minify": true,
      "removeUnusedDependencies": true
    }

9. Optimize Image Loading

  • Description: Uses modern image formats (like WebP), lazy loading, and responsive images.
  • Benefits: Reduces the load on the network and improves rendering performance.

    <img src="image.webp" loading="lazy" alt="description">

10. Code Splitting

  • Description: Splits the application code into smaller chunks that are loaded on demand.
  • Benefits: Improves load times by only loading the necessary code for each route.

    "optimization": {
      "splitChunks": {
          "chunks": "all"
      }
    }

11. Use Web Workers

  • Description: Offloads heavy computation to background threads.
  • Benefits: Prevents blocking the main thread, maintaining a responsive UI.

    if (typeof Worker !== 'undefined') {
      const worker = new Worker(new URL('./app.worker', import.meta.url));
      worker.onmessage = ({ data }) => {
          console.log(`Page received message: ${data}`);
      };
      worker.postMessage('hello');
    }

12. Service Worker for Caching

  • Description: Uses Angular’s service worker to cache application resources for offline access and faster load times.
  • Benefits: Reduces network requests and improves load performance.

    import { ServiceWorkerModule } from '@angular/service-worker';
    import { environment } from '../environments/environment';
    
    ServiceWorkerModule.register('ngsw-worker.js', {
      enabled: environment.production,
      registrationStrategy: 'registerWhenStable:30000'
    })

13. Use ng-container to Group Elements

  • Description: Uses ng-container to avoid unnecessary <div> tags and reduce the size of the DOM tree.
  • Benefits: Minimizes the number of elements in the DOM, leading to improved rendering performance.

    <ng-container *ngIf="condition">
      <p>Content</p>
    </ng-container>

14. Throttle or Debounce Event Handlers

  • Description: Uses RxJS operators to limit the frequency of event handling.
  • Benefits: Reduces the number of function executions and subsequent change detection runs.

    import { fromEvent } from 'rxjs';
    import { debounceTime } from 'rxjs/operators';
    
    fromEvent(document, 'click')
      .pipe(debounceTime(300))
      .subscribe((event) => console.log(event));

15. Memory Leak Prevention

  • Description: Ensures subscriptions are properly unsubscribed and components are destroyed cleanly.
  • Benefits: Prevents memory leaks, which can degrade performance over time.

    import { Subscription } from 'rxjs';
    let subscription: Subscription = new Subscription();
    
    ngOnDestroy() {
      subscription.unsubscribe();
    }

16. Avoid Direct DOM Manipulation

  • Description: Uses Angular’s templating system instead of direct DOM manipulation.
  • Benefits: Ensures changes are tracked by Angular's change detection, maintaining application consistency.

    this.renderer.setStyle(element, 'display', 'none');

17. Defer and Async Loading for Scripts

  • Description: Loads external scripts with defer or async attributes.
  • Benefits: Prevents blocking the main thread during script loading, improving initial render times.

    <script src="example.js" defer></script>

18. Minimize Use of *ngIf and *ngFor

  • Description: Reduces the use of *ngIf and *ngFor directives to avoid frequent DOM changes.
  • Benefits: Decreases change detection and re-rendering overhead.

    <ng-container *ngIf="condition">
      <!-- Conditional content here -->
    </ng-container>

19. Enable Production Mode

  • Description: Ensures Angular runs in production mode, which disables development mode checks and optimizations.
  • Benefits: Reduces overhead and improves application performance.

    import { enableProdMode } from '@angular/core';
    import { environment } from './environments/environment';
    
    if (environment.production) {
      enableProdMode();
    }

Configure tree shaking in angular app

Tree shaking is an optimization technique used to eliminate dead code (unused code) from a JavaScript bundle. In Angular applications, tree shaking is handled primarily by the build tool, which is usually Webpack, as part of the Angular CLI's build process. Here’s how you can ensure tree shaking is effectively implemented in your Angular application:

Key Steps to Ensure Tree Shaking in Angular Applications

  1. Ensure Production Mode is Enabled

    Angular's build process differentiates between development and production builds. Production builds enable optimizations like tree shaking, minification, and Ahead-of-Time (AOT) compilation.

    How to Enable:

    • Via Command Line: Run your build command with the --prod flag:

      ng build --prod
    • In angular.json Configuration: Ensure your production configuration has optimization enabled.

      "configurations": {
       "production": {
           "optimization": true,
           "outputHashing": "all",
           "sourceMap": false,
           "extractCss": true,
           "namedChunks": false,
           "aot": true,
           "extractLicenses": true,
           "vendorChunk": false,
           "buildOptimizer": true
       }
      }
  2. Use ES6 Module Imports

    Tree shaking works best with ES6 module syntax because the static structure of ES6 imports and exports makes it easier for the build tool to determine which code is used and which isn’t.

    Example:

     // Import only the required parts
     import { Component, OnInit } from '@angular/core';
  3. Avoid Dynamic require Statements

    Avoid using dynamic require statements, as they can prevent tree shaking. Always use static imports.

    Good:

    import { MyModule } from './path/to/module';

    Bad:

    // Avoid dynamic imports that bring in unnecessary code
    const MyModule = require('./path/to/module');
    const Component = require('@angular/core').Component;
    
    // Sometime
      import('large-module').then(module => {
        module.someFunction();
      });
  4. Optimize Angular CLI Configuration (angular.json)

    Ensure your angular.json file is set up to take advantage of the Angular CLI's optimizations.

    Configuration Snippet:

    "optimization": {
       "scripts": true,
       "styles": true,
       "fonts": true
    },
    "buildOptimizer": true,
  5. Minimize Polyfills

    Polyfills can add significant bloat to your application if not carefully managed. Only include necessary polyfills.

    Example:

    • Remove unnecessary polyfills from polyfills.ts.
  6. Use sideEffects in package.json

    Specify that your code is side-effect free to help Webpack in identifying and eliminating dead code. The sideEffects field in package.json indicates which files are safe to prune.

    Example:

    {
       "name": "your-app",
       "version": "1.0.0",
       "sideEffects": false
    }
  7. Use Angular CLI's Tree Shakable Libraries

    Angular CLI supports tree-shakable libraries. Ensure the libraries you use are designed to support tree shaking by checking their package.json for proper module fields like es2015, module, or esnext.

  8. Optimize Third-Party Library Imports

    Import only what you need from third-party libraries instead of the entire library.

    Example:

    // Good: Import only required parts
    import { debounceTime } from 'rxjs/operators';
    
    // Bad: Import the whole library
    import * as _ from 'lodash';
  9. Use AOT Compilation

    AOT compilation precompiles the Angular application before the browser downloads and runs it. This helps reduce the bundle size and improve the runtime performance, indirectly aiding tree shaking.

    How to Enable:

    • Via Command Line:

      ng build --aot
    • In angular.json:
      "aot": true
  10. Review Webpack Configuration (Advanced)

    Although Angular CLI abstracts Webpack configuration, advanced users might want to review and adjust Webpack settings directly to ensure tree shaking is optimized.

    Example:

    • Custom Webpack configuration through Angular CLI builders like @angular-builders/custom-webpack.

      "architect": {
        "build": {
            "builder": "@angular-builders/custom-webpack:browser",
            "options": {
                "customWebpackConfig": {
                    "path": "./extra-webpack.config.js"
                },
                // other options...
            }
        }
      }
  11. Monitor and Analyze Bundle Size

    Regularly analyze your bundle size to ensure tree shaking is effectively reducing the application size. Tools like source-map-explorer or webpack-bundle-analyzer can be used for this purpose.

    Example:

      npm install -g source-map-explorer
      source-map-explorer dist/your-app/main.*.js

Summary

To ensure effective tree shaking in your Angular application:

  1. Enable Production Mode in your build configuration.
  2. Use ES6 Module Imports and avoid dynamic requires.
  3. Optimize Angular CLI Configuration for tree shaking.
  4. Minimize Polyfills and specify side effects in package.json.
  5. Leverage Tree Shakable Libraries and optimize third-party imports.
  6. Utilize AOT Compilation for pre-compilation benefits.
  7. Analyze Bundle Size to verify the effectiveness of tree shaking.

By following these steps, you can significantly reduce your application's bundle size, leading to faster load times and better performance.

Conclusion

Applying these performance optimization steps can significantly enhance the responsiveness, efficiency, and overall user experience of an Angular application. Each step targets specific aspects of application performance, from initial load times to runtime efficiency and resource management.





Comments powered by Disqus