Deferred loading in Angular using placeholders loading and error blocks

Have you ever built a dashboard that feels slow or unresponsive right after loading? Or a page that tries to load everything at once, even parts the user hasn’t even scrolled to yet? As Angular apps grow, so does the initial rendering cost, which can make the app feel sluggish. While Angular has always offered tools like *ngIf for conditionally showing content and Angular lazy-loaded modules to split up large apps, these tools haven’t made it easy to control conditional rendering in smaller, more specific parts of a page.

You might wonder: why render things no one’s even looking at yet? Why not save those precious CPU cycles for something more important? Enter Angular @defer, our performance hero, swooping in to slash initial load times and bring some sanity back to your rendering logic

What is @defer?

Starting with Angular 17, you can now use @defer syntax in Angular to delay rendering certain parts of your templates. This new feature, called deferred views in Angular, gives you a simple and powerful way to control when specific parts of the UI should appear. Whether it’s waiting for user interaction, scrolling to a section, or even waiting for a certain condition to be met, @defer helps you load only what’s needed, when it’s needed. This can greatly improve both actual performance and how fast the app feels to users, all without adding much complexity for developers. Many companies looking to hire angular developers are now using these strategies to build high-performing web apps

The @defer block is a structural syntax that tells Angular, “Wait to render this part of the view until certain conditions are met.” It’s more than just Angular lazy loading. It’s an intelligent, conditional rendering technique that supports Angular template optimization.

@defer {

  <app-heavy-widget />

 }Code language: HTML, XML (xml)

In the example above, <app-heavy-widget> won’t be created immediately with the rest of the page. Instead, Angular will defer its rendering, resulting in a faster, lighter initial page load.

This aligns with Angular rendering strategies aimed at improving performance and user experience. It’s a common approach in modern web application development, where performance-driven conditional rendering is critical for scalable UI solutions.

Which Dependencies Can Be Deferred?

For anything inside a @defer block to be truly lazy-loaded, these two rules apply:

  • They must be standalone components. If a dependency isn’t standalone, it will still load eagerly, even if it’s inside a @defer block.
  • They shouldn’t be used elsewhere in the same file. If a component, directive, or pipe is referenced outside the @defer block (like with ViewChild), it can’t be deferred.

The good news? Their inner dependencies (transitive ones) can still be a mix of standalone or NgModule-based and will be deferred along with them, making Angular bundle splitting more efficient.

Built-In Triggers: When Does It Load?

Angular hands you the remote. You decide exactly when a deferred view in Angular should come to life. Whether it’s user interaction, viewport visibility, or custom conditions, you’ve got fine-grained control at your fingertips.

  • when <condition>
@defer (when isDataLoaded) {

  <app-user-profile />

}Code language: HTML, XML (xml)

Renders only when isDataLoaded becomes true or any other condition you choose to define. This supports deferred loading in Angular applications where rendering is tied to reactive states.

  • when idle
@defer (when idle) {

  <app-analytics-widget />

}Code language: HTML, XML (xml)

Perfect for non-critical widgets that can wait their turn, rendered when the browser is idle and free from heavy operations. This is useful for improving core web vitals in Angular apps.

  • on timer(<ms>)
@defer (on timer(3000)) {

  <app-product-ad />

}Code language: HTML, XML (xml)

Renders after a 3-second delay. Great for popups, ads or content below-the-fold.

  • when visible
@defer (when visible) {

  <app-footer />

}Code language: HTML, XML (xml)

Uses Intersection Observer to defer rendering until the element scrolls into view.

Enhancing UX: Placeholders, Loading & Error States

Deferred loading in Angular using placeholders loading and error blocks

Deferred views in Angular aren’t just about performance,they’re also a big win for user experience. Without proper feedback, delayed content can leave users confused, staring at a blank space with no idea what’s happening. That’s where sub-blocks like Angular @placeholder block, @loading block, and @error block come to the rescue to improve Angular template optimization.

What Could Go Wrong Without Them?

  • Empty Gaps:  If a deferred component is loading but there’s no visual cue, users may think the app is broken or unresponsive.
  • Uncertainty:  A delay without feedback can make users second-guess their actions: Did I click that button? Is the page stuck?
  • Poor Accessibility: Screen readers may have nothing to announce, making the experience confusing for assistive technologies.
  • Debugging Pain: If something silently fails, users and even developers might miss it entirely.

How Placeholders & States Help

Placeholder

@placeholder lets you show temporary content while the real component is being loaded. This can be a spinner, image, skeleton UI, or anything that makes the wait feel intentional and expected. The @placeholder block also lets you set a minimum time it should stay visible, using the minimum parameter (like 500ms or 2s). This helps prevent a flicker effect when the deferred content loads too quickly. The timer starts right after the placeholder finishes rendering.

For example:

@defer {

<app-heavy-widget />

} @placeholder (minimum 1s) {

<div>Loading...</div>

}Code language: HTML, XML (xml)

Even if the widget loads instantly, the placeholder will stay for at least 1 second to keep the UI smooth and contribute to angular performance optimization.

Loading

@loading provides a hook for when the defer block has started fetching but isn’t ready yet. This is especially helpful for async operations like Angular lazy loading of modules or data. The @loading block also accepts two optional parameters to specify the minimum amount of time that this placeholder should be shown and the amount of time to wait after loading begins before showing the loading template. The purpose of these params is the same as placeholders, to prevent flicker effects. Both the minimum and after timers for the @loading block begin immediately after the loading has been triggered.

Error

@error gives you a graceful fallback if the deferred component fails to load, whether due to a network issue, module resolution error, or some other problem. This enhances core web vitals in Angular by preventing bad user experiences due to invisible failures.

@defer (on timer(2s)) {

<app-heavy-widget />

} @placeholder {

<div class="skeleton">Loading widget...</div>

} @loading {

<div class="spinner">Almost there...</div>

} @error {

<div class="error">Oops! Couldn’t load the widget.</div>

}Code language: HTML, XML (xml)

With these features in place, your app stays informative, accessible, and polished, even when deferring content behind the scenes. This supports both UX and deferred loading in Angular.

Another example below:

@defer (on interaction) {

  <analytics-widget />

  } @placeholder {

  <div>Widget Placeholder</div>

}Code language: HTML, XML (xml)

Hereby default, the placeholder will act as the interaction element as long as it is a single root element node.

  • This is a single root element
@placeholder {

<button>Click to load chart</button>

}Code language: HTML, XML (xml)
  • Multiple elements, not a single root
@placeholder {

<p>Chart coming soon</p>

<button>Load</button>

}Code language: HTML, XML (xml)

Alternatively, you can specify a template reference variable as the element that triggers interaction. This variable is passed in as a parameter on the interaction trigger.

<button type="button" #trigger>View Charts</button>


@defer (on interaction(trigger)) {

  <app-product-charts />

  } @placeholder {

  <div>Product Charts</div>

}Code language: HTML, XML (xml)

Few other options:

on hover

Hover triggers deferred loading when the mouse has hovered over the trigger area. Events used for this are mouseenter and focus.

on immediate

Immediate triggers the deferred load once the client has finished rendering, the deferred chunk would then start fetching right away, supporting Angular rendering strategies.

You could also use multiple conditions together in one statement, and the swap will be triggered if either condition is met.

For Example:

@defer(on viewport; when condition) {

  <app-heavy-widget></app-heavy-widget>

}Code language: HTML, XML (xml)

Hands-on Example: Just Copy and See It Work!

Please take the below example and simply copy-paste it into any component’s HTML file in your Angular 19 project.

  • No changes in .ts files,
  • No services,
  • No special setup.

That’s how easy it is to use @defer (when viewport) in Angular for template-level performance optimization!

<div class="container">
  <div class="card" style="height: 400px;">
    @defer (on timer(3000)){
    <div class="deferred-view" style="background: #a4daa4; padding: 20px; border-radius: 6px;">


      <div class="card-header">

        <h2>Congratulations!</h2>

      </div>

      <div class="card-body">

        <p>

          <strong>Hey, you successfully completed the test!</strong>

        </p>

        <p>

          Your efforts are appreciated. Keep up the great work.

        </p>

        <p>

          We hope you enjoyed the challenge and learned something new. Stay tuned for more exciting content!

        </p>

      </div>

      <div>

        <span class="btn btn-primary">Thank you!</span>

      </div>

    </div>

    }

    @placeholder {

    <div class="loading-placeholder" style="height: 100px; background: #d3e59d; padding: 20px; border-radius: 6px;">

      <p>Loading content...</p>

    </div>

    }

    @error {

    <div class="error-message">

      Something went wrong while loading your content. Please try again later.

    </div>

    }

  </div>
  
</div>Code language: HTML, XML (xml)

If you did it successfully, you’ll see the card content appearing as soon as it scrolls into view. Congratulations! You’ve just done a hands-on experiment with Angular’s @defer feature. You now understand how viewport-based deferred loading in Angular works in a real Angular 19 project.

Now that you’ve successfully seen it in action, feel free to experiment with it further!

If you’d like to clearly observe the transition in real time, try replacing on viewport with on timer(3000).

This gives you enough time to scroll to the component or switch back to your browser tab, and you’ll see the content fade in right before your eyes.

Here’s the result of the @defer in action
Watch how the content smoothly appears in the view:

GIF Demo Below

Angular defer block demo showing conditional and lazy rendering

Prefetching: Get Ready Before the User Is

@defer also supports Angular defer prefetching, which means you can load the required resources before the user triggers the view.

You can use the prefetch keyword along with on or when conditions to control when Angular should start fetching the dependencies, separately from when it renders the component. This is super useful for Angular performance optimization and improving perceived performance. For example, you can start fetching the data when the browser is idle, and render it only when the user interacts:

@defer (on interaction; prefetch on idle) {

  <app-data-table />

} @placeholder {

  <img src="placeholder.png" />

}Code language: HTML, XML (xml)

Here, the browser begins downloading the necessary resources in the background when it’s idle, so when the user finally interacts, everything is already in place, making the UI feel instant and smooth. This technique aligns well with modern rendering strategies and helps improve core web vitals in Angular applications.

Behavior with SSR and NgModules

When using @defer with Server-Side Rendering (SSR) or Static Site Generation (SSG), only the @placeholder content is rendered. Any triggers are ignored during server render. So, make sure your placeholder is meaningful for the first load! 

To dive deeper into setting up Angular Universal for SSR, check out our Complete Guide to SSR with Angular 19 – Angular Universal Setup.”

Also, @defer works with both Angular standalone components and NgModule-based components, directives, and pipes. However, only standalone ones can be truly deferred. NgModule-based dependencies will still be bundled and loaded eagerly, which may impact bundle splitting and initial load performance.

Do’s and Don’ts with @defer

Do:

  • Use for heavy, non-essential components (e.g., widgets, charts, ads, analytics)
  • Defer offscreen content (below the fold) or not immediately needed
  • Use meaningful placeholders and triggers for better UX
  • Adjust nested @defer conditions to avoid multiple loads firing at once

Don’t:

  • Defer critical UI like login forms, primary buttons, or anything users need instantly
  • Trigger deferred blocks too early (e.g., with immediate or timer) for content in the initial viewport
  • Use the same trigger for nested @defer blocks, and avoid cascading requests
  • Leave placeholders blank: missing feedback can confuse users and harm perceived performance

 If you’re exploring modern reactivity in Angular beyond @defer, check out our Angular Signals Guide: Patterns & Best Practices to see how Signals integrate with component rendering and change detection.”

Performance Gains: Why Use Deferred Views?

Using @defer isn’t just about loading less, it’s about loading smart. Here’s how deferred views in Angular boost your app’s performance and user experience:

  • Improve Core Web Vitals like LCP (Largest Contentful Paint) and FCP by reducing the amount of content rendered upfront. Less blocking, faster visual feedback.
  • Minimize the DOM size on first paint, helping the browser do less work and render the initial screen more quickly.
  • Delay heavy components that don’t need to run immediately (like charts, calendars, or widgets), saving memory and processing power.
  • Boost user experience on slower networks or low-powered devices by prioritizing what matters first and loading the rest only when needed.
  • And the best part? It’s declarative, clean, and fits seamlessly into the modern Angular ecosystem, especially with Angular standalone components.

When combined with devops or CI/CD for angular apps, these performance techniques help automate and streamline delivery pipelines efficiently.

Wrap-Up: Smarter Renders, Faster Loads

Angular’s @defer syntax marks a shift in how we think about rendering and performance. It’s simple, powerful, and tailor-made for modern apps that need speed and flexibility.

With deferred views, you can:

  • Render smarter, not harder
  • Speed up initial loads
  • Delight users with faster and more responsive interfaces

Start using @defer today in Angular and transform your app’s performance without the headache.If you have any queries, please check out the official Angular docs or hit me up on LinkedIn.

Build scalable Angular apps using optimized rendering and lazy loading

Author's Bio:

Rohit Kumar- Author of Angular performance tutorial using deferred views and @defer
Rohit Kumar

Rohit Kumar is a frontend developer at Mobisoft Infotech with around four years of experience in building responsive, performance-focused web applications. With strong expertise in Angular and working knowledge of Node.js, he is passionate about crafting smooth user experiences and writing clean, scalable code. Rohit is especially driven by frontend performance, precise UI implementation, and solving real-world challenges through thoughtful design and development.