DEV Community

Cover image for Deferred loading with @defer: Optimize Your App's Performance
hassantayyab
hassantayyab

Posted on

Deferred loading with @defer: Optimize Your App's Performance

Angular continues to innovate with each release, and the introduction of @defer blocks in Angular v19 is a game-changer for optimizing web application performance. This blog post explores Angular's @defer feature, explaining its functionalities, use cases, and practical code examples to help you leverage it effectively.


What are defer Blocks?

Deferrable views, also known as @defer blocks, enable lazy loading for sections of your Angular templates. They reduce the initial bundle size by deferring the loading of code that is not essential for the initial rendering of a page. This improves initial load times and Core Web Vitals (CWV) such as Largest Contentful Paint (LCP) and Time to First Byte (TTFB).

You can declaratively wrap a section of your template in a @defer block to defer its loading:

@defer {
  <large-component />
}
Enter fullscreen mode Exit fullscreen mode

This loads the dependencies of large-component only when required, improving performance.


Key Features of defer Blocks

1. Placeholders for Better User Experience

The @placeholder block lets you show content while the deferred section is being loaded.

@defer {
  <large-component />
} @placeholder {
  <p>Loading...</p>
}
Enter fullscreen mode Exit fullscreen mode

Use Case: Use placeholders to provide immediate feedback to users while content loads.


2. Loading Indicators

The @loading block is shown after the placeholder and during the loading process. It replaces the placeholder content.

@defer {
  <large-component />
} @loading {
  <img alt="Loading..." src="loading.gif" />
} @placeholder {
  <p>Loading...</p>
}
Enter fullscreen mode Exit fullscreen mode

Parameters:

  • minimum: Minimum time to show the loading block.
  • after: Time after loading starts to display the loading block.

Example:

@defer {
  <large-component />
} @loading (minimum 1s; after 100ms) {
  <img alt="Loading..." src="loading.gif" />
}
Enter fullscreen mode Exit fullscreen mode

3. Error Handling

The @error block displays content when deferred loading fails.

@defer {
  <large-component />
} @error {
  <p>Failed to load content. Please try again.</p>
}
Enter fullscreen mode Exit fullscreen mode

Triggers for Controlling Deferred Content

1. Idle Trigger

Defers loading until the browser is idle:

@defer (on idle) {
  <large-component />
} @placeholder {
  <p>Loading...</p>
}
Enter fullscreen mode Exit fullscreen mode

2. Viewport Trigger

Loads content when it enters the viewport:

@defer (on viewport) {
  <large-component />
} @placeholder {
  <p>Scroll down to load...</p>
}
Enter fullscreen mode Exit fullscreen mode

3. Interaction Trigger

Loads content upon user interaction, such as clicks:

@defer (on interaction) {
  <large-component />
} @placeholder {
  <button>Click to load</button>
}
Enter fullscreen mode Exit fullscreen mode

4. Hover Trigger

Loads content when the mouse hovers over a specified area:

@defer (on hover) {
  <large-component />
} @placeholder {
  <p>Hover to load content...</p>
}
Enter fullscreen mode Exit fullscreen mode

5. Timer Trigger

Loads content after a set duration:

@defer (on timer(2s)) {
  <large-component />
} @placeholder {
  <p>Loading in 2 seconds...</p>
}
Enter fullscreen mode Exit fullscreen mode

6. Custom Conditional Trigger

Loads content based on a custom condition:

@defer (when isReady) {
  <large-component />
} @placeholder {
  <p>Waiting for readiness...</p>
}
Enter fullscreen mode Exit fullscreen mode

Combining Prefetching and Deferred Loading

You can specify a prefetch trigger to preload dependencies before they are needed:

@defer (on interaction; prefetch on idle) {
  <large-component />
} @placeholder {
  <button>Click to load</button>
}
Enter fullscreen mode Exit fullscreen mode

This starts prefetching when the browser is idle but only renders the content when the user interacts.


Testing defer Blocks

Angular provides APIs to test @defer blocks using TestBed. You can control and verify different states manually:

it('should test defer block states', async () => {
  TestBed.configureTestingModule({ deferBlockBehavior: DeferBlockBehavior.Manual });

  @Component({
    template: `
      @defer {
        <large-component />
      } @placeholder {
        Placeholder
      } @loading {
        Loading...
      }
    `
  })
  class TestComponent {}

  const fixture = TestBed.createComponent(TestComponent);
  const deferBlock = (await fixture.getDeferBlocks())[0];

  expect(fixture.nativeElement.innerHTML).toContain('Placeholder');
  await deferBlock.render(DeferBlockState.Loading);
  expect(fixture.nativeElement.innerHTML).toContain('Loading...');
  await deferBlock.render(DeferBlockState.Complete);
  expect(fixture.nativeElement.innerHTML).toContain('large-component works!');
});
Enter fullscreen mode Exit fullscreen mode

Best Practices for Using defer Blocks

  1. Avoid Nested Defers: Use distinct triggers for nested @defer blocks to prevent cascading loads.
  2. Prevent Layout Shifts: Avoid deferring components visible during initial load to minimize cumulative layout shift (CLS).

Cheat Sheet for Angular Blocks

  • Basic usage: @defer { <component /> } - Defers the content inside the block.
  • Placeholder: @defer { <cmp /> } @placeholder { <p>Loading...</p> } - Placeholder shown before loading.
  • Loading: @defer { <cmp /> } @loading { <p>Loading...</p> } - Shown during loading process.
  • Error Handling: @defer { <cmp /> } @error { <p>Error...</p> } - Handles loading failures.
  • Triggers: @defer (on idle) - Controls when loading starts.
  • Prefetch: @defer (on interaction; prefetch on idle) - Preloads dependencies early.

Additional Resources

Start exploring Angular's @defer feature today and transform the performance of your Angular applications!

Top comments (0)