Skip to main content

Angular Web Performance

Context

By prioritizing frontend performance, government service teams can ensure that their online services are accessible, efficient, and user-friendly for all citizens, regardless of their device or connection. This fosters inclusivity, improves user satisfaction, and ultimately contributes to a more effective and trusted government. This aligns very closely with the topic of Frontend Performance in the GDS Way and the Service Manual. Creating services that are performant is also a fundamental skill of being a Frontend Developer in the Civil Service, according to the Government Digital and Data Profession Capability Framework.

Within this section, we will make recommendations on the Frontend Performance of the shared components that are currently built on the Angular Framework.

Use Web Workers for complex tasks

All browsers are single threaded. This means that they can only tackle a single task at any one time. This single thread is often called the “main thread”. If the main thread is busy, any tasks that the browser needs to complete are queued up for when it is no longer busy.

Excessive JavaScript execution on the main thread can delay visual updates, like CSS rendering or button clicks, causing “page jank” and negatively impacting user experience. Research shows any page interaction taking over 100ms is noticeable, affecting usability.

Web Workers, background threads, offload heavy tasks, preventing UI jank and improving responsiveness. However, their asynchronous nature demands careful integration. Libraries like Comlink simplify Web Worker usage, but may increase JavaScript overhead, impacting performance on low-spec devices. For detailed information, refer to the MDN Web Worker API documentation.

Use either Server-Side Rendering (SSR) or Static Site Generation (SSG)

Server-Side Rendering (SSR)

Server-Side Rendering (SSR) significantly improves performance in Angular applications in several ways:

Pros:

  • Faster initial page load than client-side rendering
  • Better SEO than client-side rendering
  • Supports dynamic content and personalization

Cons:

More details on the advantages of using SSR over purely client-side (browser) rendering of Angular components is discussed below.

Faster Initial Page Load

SSR generates the initial HTML of a page on the server and sends it to the client.

This means the browser receives the first meaningful content much faster than if it had to wait for the Angular application to download and bootstrap on the client-side. This change has a significant positive impact on the Time to First Byte (TTFB).

Since the initial HTML is already rendered, the browser can quickly display content to the user, providing a better First Contentful Paint (FCP) experience. This improves the perceived performance of the page for users.

Improved Core Web Vitals

With SSR, the largest content elements (images, text blocks) are usually included in the initial HTML, leading to faster Largest Contentful Paint (LCP) times. SSR also minimizes layout shifts that can occur when the Angular application is bootstrapped on the client-side and dynamically loads content. Reducing the Cumulative Layout Shift (CLS) of a page leads to a more visually stable experience.

Better Search Engine Optimization (SEO)

Search engine crawlers can easily index the fully rendered HTML content generated by SSR, which is important for SEO visibility and search engine crawlability. SSR ensures that social media platforms can accurately display previews of shared pages, as they can access the complete HTML structure and content, leading to improved Social Sharing.

Enhanced User Experience

As mentioned above, the faster initial rendering and lower CLS contribute to a smoother and more engaging user experience, especially on slower connections or devices. This improves perceived performance for users. SSR makes content available to users with JavaScript disabled or with network connectivity issues. Users using screen readers will see an improvement in accessibility and inclusivity.

Reduced Client-Side Load

SSR shifts some of the rendering work to the server, reducing the amount of JavaScript that needs to be executed on the client-side, potentially leading to faster interactions and reduced battery consumption on mobile devices. Less JavaScript Execution on the client is always a great performance target to aim for.

Considerations

There are some important considerations that should be thought about before using SSR. SSR increases the server-side workload, as the server needs to render each page for each user. This can impact scalability and requires careful server-side optimisation. Caching the rendered pages should be a high priority to reduce the server-side workload.

Before adopting SSR or SSG, evaluate if the added maintenance, documentation, and developer overhead outweigh potential benefits. Consider if the functionality risks becoming redundant.

Summary

Using Server-side Rendering in an Angular application comes with some trade-offs. But it can significantly improve performance, SEO, and user experience. These improvements are especially important for public-facing websites and applications where performance and SEO are crucial.

Static Site Generation (SSG)

Pages are pre-rendered into static HTML files at build time. These files are then served directly to users, eliminating the need for the browser to execute JavaScript to render the content.

SSG is Ideal for content-focused websites or blogs where the data doesn’t change frequently. SSG provides the fastest possible initial load times and excellent SEO since search engine crawlers can easily index the pre-rendered HTML.

Pros:

  • Extremely fast initial page loads
  • Great for SEO
  • Reduced server load

Cons:

  • Not suitable for highly dynamic content
  • Requires a rebuild to update content
  • Limited personalization options

SSR vs SSG Key differences

See the table below for a quick overview of the key differences between SSR and SSG:

Feature Static Site Generation (SSG) Server-Side Rendering (SSR)
Content generation At build time On each request
Initial page load Extremely fast Faster than client-side rendering
SEO Excellent Better than client-side rendering
Dynamic content Not suitable Supported
Personalization Limited Supported
Server load Reduced Increased
Time to Interactive (TTI) Very fast Slower than SSG

Choosing the right approach

The choice between SSG and SSR depends on your specific requirements:

  • SSG: Prioritize speed and SEO for mostly static content.
  • SSR: Prioritize dynamic content, personalization, and SEO for applications with frequent updates.
  • Hybrid approach: Use SSG for static parts of your application and SSR for dynamic sections to get the best of both worlds.

Summary

For content-focused websites with infrequent updates, Static Site Generation (SSG) surpasses Server-Side Rendering (SSR) in web performance and SEO. SSG’s pre-rendered HTML delivers faster initial loads, enhancing user experience and search engine visibility. While SSR excels with dynamic content, SSG is optimal for static sites prioritizing speed and SEO, especially crucial for public-facing platforms. However, both SSG and SSR can both introduce complexity in Angular apps.

Avoid Complex Calculations in Templates

To avoid complex calculations within Angular templates and improve application performance, you can employ the following strategies:

Precompute Values in Component Classes

Move complex calculations or data manipulation logic from the template to the component’s TypeScript class. You should also where possible store the results of these calculations in component properties, thus making them readily available in the template.

Utilize Angular Pipes

Use pure pipes for simple transformations and formatting that depend solely on input values. Also create custom pure pipes for more complex calculations or transformations.

Leverage Observables and Async Pipes

If your calculations involve asynchronous operations (e.g., API calls), use Observables to handle the results. Also remember to employ the async pipe to automatically subscribe to the Observable in the template, display the latest value, and unsubscribe when the component is destroyed.

Caching Data Where Applicable

If you have pure functions with repeated calculations, consider caching the results to avoid redundant computation.

Performance Profiling

Angular comes with a powerful set of DevTools, which can be used along with other profiling tools to identify bottlenecks caused by complex tasks in your templates. Once identified, look for optimisations to fix them and repeat the tests to ensure they have been fixed.

Avoid known Anti-patterns

Not Using Modules Properly

Monolithic Modules: Avoid putting all components, directives, and services into the AppModule. Instead, use feature modules and shared modules to organize code logically and facilitate lazy loading.

Abusing Change Detection

Using default Change Detection Everywhere: Relying solely on the default change detection strategy can lead to performance issues. Use ChangeDetectionStrategy.OnPush where possible to minimize unnecessary change detection cycles.

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

Heavy Logic in Components

Fat Components: Placing business logic and heavy processing in components makes them challenging to test and maintain. Move such logic to services.

export class MyComponent {
  constructor(private myService: MyService) { }

  ngOnInit() {
    this.myService.getData().subscribe(data => {
      // Handle data
    });

Direct DOM Manipulation

Bypassing Angular’s Renderer: Directly manipulating the DOM with native methods instead of using Angular’s Renderer2 can lead to cross-platform compatibility issues and bypass Angular’s change detection.

constructor(private renderer: Renderer2) { }

changeBackgroundColor(element: HTMLElement, color: string) {
  this.renderer.setStyle(element, 'background-color', color);
}

Ignoring RxJS Best Practices

Subscribing without Unsubscribing: Failing to unsubscribe from observables can cause memory leaks. Use takeUntil, async pipe, or Subscription management to handle unsubscriptions.

ngOnInit() {
  this.data$ = this.myService.getData().pipe(takeUntil(this.destroy$));
}

ngOnDestroy() {
  this.destroy$.next();
  this.destroy$.complete();
}

Not Using TrackBy in ngFor

Inefficient ngFor Loops: Failing to use trackBy in *ngFor can lead to performance issues due to Angular re-rendering the entire list on changes.

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

Using Any for Type Declarations

Lack of Strong Typing: Using any instead of proper type declarations defeats the purpose of TypeScript, leading to potential runtime errors.

interface User {
  id: number;
  name: string;
}

getUser(): User {
  return { id: 1, name: 'John Doe' };
}

Tightly Coupled Components

Direct Dependencies: Components that directly depend on each other are harder to maintain. Use services and dependency injection to decouple components.

// Instead of this:
constructor(private parentComponent: ParentComponent) { }

// Do this:
constructor(private dataService: DataService) { }

Excessive Use of Angular Forms API

Complex Template-Driven Forms: For complex forms, prefer reactive forms over template-driven forms for better control and scalability.

this.form = this.fb.group({
  name: ['', Validators.required],
  age: ['', Validators.min(18)]
});

No Separation of Concerns

Mixing Concerns: Avoid mixing different concerns like UI logic, business logic, and state management in a single class or file. Separate these concerns into different layers and services.

Hardcoding Configuration Values

Hardcoding API Endpoints: Avoid hardcoding configuration values like API endpoints in the code. Use environment configuration files to manage these values.

import { environment } from '../environments/environment';

this.apiUrl = environment.apiUrl;

Ignoring Best Practices for Asynchronous Code

Improper Async Handling: Not handling asynchronous code properly can lead to unexpected behavior. Use async/await, observables, or promises appropriately.

async fetchData() {
  try {
    const data = await this.httpClient.get('url').toPromise();
    // Handle data
  } catch (error) {
    // Handle error
  }
}

Poor Error Handling

Neglecting Error Handling: Not handling errors properly can lead to a poor user experience. Implement comprehensive error handling strategies for HTTP calls and other async operations.

this.myService.getData().subscribe(
  data => { /* Handle data */ },
  error => { /* Handle error */ }
);

Not Using Angular CLI

Manual Build Configuration: Avoid manually configuring the build process. Use Angular CLI to generate, develop, and build your application for consistency and best practices.

Fine tune templates especially for loops

Virtual Scrolling

Problem:

Rendering large lists with ngFor can be slow, especially when dealing with thousands of items.

Solution:

Use a virtual scrolling library like ngx-virtual-scroller or cdk-virtual-scroll. These libraries only render the visible portion of the list and dynamically load more items as the user scrolls, dramatically improving performance for large datasets.

​​Optimize Data Structures

Problem:

Inefficient data structures or frequent updates can impact loop performance.

Solution:

Ensure your data is well-structured and updates are batched or optimized to minimize unnecessary changes.

Lazy Loading

Problem:

Loading large amounts of data upfront can slow down initial rendering.

Solution:

Consider lazy loading items in the loop as the user scrolls or interacts with the list, especially if not all items need to be visible immediately.

Nested Subscriptions

Problem:

Subscribing to observables within other subscriptions can lead to complex code and potential memory leaks if subscriptions are not properly managed.

Solution:

Use higher-order mapping operators (e.g., switchMap, concatMap, mergeMap) to flatten nested observables or consider using libraries like RxJS takeUntil to automatically unsubscribe.

Other Angular Performance Tips

  • Use ngIf for conditional rendering: Avoid using hidden attribute, as it still renders elements in the DOM.
  • Avoid nested loops: Deeply nested loops can be expensive.
  • Use the keyvalue pipe: It’s more efficient for iterating over objects than using a for loop.

Optimizing Angular applications for performance, SEO, and maintainability involves a multifaceted approach. Offloading complex calculations to Web Workers prevents UI blocking and improves responsiveness, while utilizing SSR or SSG ensures faster initial page loads and better search engine visibility.

By strategically choosing between SSR and SSG based on content dynamism, developers can strike the right balance between performance and flexibility. Additionally, simplifying Angular templates by avoiding complex calculations enhances code readability, maintainability, and overall efficiency. These combined strategies lead to faster, more SEO-friendly Angular applications that deliver a superior user experience for all users.

This page was last reviewed on 5 June 2024. It needs to be reviewed again on 5 June 2025 by the page owner platops-build-notices .
This page was set to be reviewed before 5 June 2025 by the page owner platops-build-notices. This might mean the content is out of date.