AngularPerformance@deferLazy LoadingView Transitions

Mastering @defer: Angular's Instant Load Magic

A
Angular GDE
Featured Guide 12 min read

Stop Lazy Loading Routes.

Lazy loading entire pages (routes) is good, but it's not enough.

With @defer, you can lazy load individual components inside a page. Does the user need to load the Heavy Chart bundle if they haven't scrolled down yet? No.

02. Declarative Loading

Angular handles the code-splitting, chunk generation, and loading state management for you.

// dashboard.component.html
@defer
(on viewport) {'{'}
  <heavy-chart />
{'}'}
@placeholder
{'{'}
  <div>Loading Chart...</div>
{'}'}
@loading
(minimum 500ms) {'{'}
  <spinner />
{'}'}
@error
{'{'}
  <error-msg />
{'}'}

Deep Dive: Prefetching

You can separate the render trigger from the fetch trigger.

@defer (on interaction; prefetch on idle)

This downloads the JS bundle in the background when the browser is idle, so it's ready instantly when the user clicks.

04. The Senior Engineer's Take

LCP Optimization

Pro Tip: Wrap everything below the fold in @defer (on viewport). This dramatically reduces the Initial Bundle Size and improves LCP (Largest Contentful Paint) because the browser prioritizes critical CSS/JS for the header.

Interactive Playground

import React, { useState, useEffect, useRef } from 'react';

// 🚀 @defer Visualizer

export default function DeferDemo() {
    const [isVisible, setIsVisible] = useState(false);
    const [isHovered, setIsHovered] = useState(false);
    const [loadState, setLoadState] = useState('placeholder'); // placeholder, loading, loaded
    const containerRef = useRef(null);

    // Simulate "on viewport" trigger
    const handleScroll = (e) => {
        if (containerRef.current) {
            const top = containerRef.current.getBoundingClientRect().top;
            if (top < 400 && loadState === 'placeholder' && !isHovered) { // Trigger
                startLoading();
            }
        }
    };

    // Simulate "on hover" trigger
    const handleMouseEnter = () => {
        if (loadState === 'placeholder') {
            setIsHovered(true);
            startLoading();
        }
    };

    const startLoading = () => {
        setLoadState('loading');
        setTimeout(() => {
            setLoadState('loaded');
        }, 1500); // Simulate network fetch of chunk
    };

    // In a real app we'd attach scroll listener to window, here we rely on manual interaction for demo or mock
    
    return (
        <div className="bg-slate-50 dark:bg-slate-950 p-8 rounded-3xl border border-slate-200 dark:border-slate-800 shadow-xl overflow-hidden relative">
             <div className="flex justify-between items-center mb-10">
                <h3 className="text-2xl font-black text-gray-900 dark:text-white flex items-center gap-3">
                    <span className="text-red-500">🚀</span> @defer Simulator
                </h3>
                <div className="text-sm bg-gray-200 dark:bg-slate-800 px-3 py-1 rounded">
                     State: <span className="font-bold">{loadState.toUpperCase()}</span>
                </div>
            </div>

            <div className="h-[300px] overflow-y-auto border border-slate-200 dark:border-slate-800 rounded-xl bg-white dark:bg-black relative p-8 shadow-inner" onScroll={handleScroll}>
                
                <div className="space-y-6">
                    {/* Hero Content (Already Loaded) */}
                    <div className="p-6 bg-blue-50 dark:bg-blue-900/20 rounded-xl animate-in fade-in">
                        <h1 className="text-2xl font-bold mb-2">Hero Section</h1>
                        <p className="text-gray-500">This content is critical. It loads immediately with the main bundle.</p>
                    </div>

                    <div className="p-4 border-2 border-dashed border-gray-300 dark:border-gray-700 rounded-xl flex items-center justify-center h-32 text-gray-400">
                        Content...
                    </div>
                    
                    {/* The DEFER Block */}
                    <div 
                        ref={containerRef}
                        onMouseEnter={handleMouseEnter}
                        className="border-4 border-red-500/20 p-2 rounded-2xl relative"
                    >
                         <div className="absolute -top-3 left-4 bg-red-100 dark:bg-red-900 text-red-600 dark:text-red-300 px-2 py-0.5 rounded text-[10px] font-bold border border-red-200 dark:border-red-800">
                             @defer (on viewport; on hover)
                         </div>

                        {loadState === 'placeholder' && (
                            <div className="h-48 bg-gray-100 dark:bg-slate-900 rounded-xl flex flex-col items-center justify-center text-gray-400 cursor-pointer hover:bg-gray-200 transition">
                                <span className="text-4xl mb-4 opacity-50">📊</span>
                                <div className="font-bold text-sm">Heavy Chart Placeholder</div>
                                <div className="text-xs mt-2">Scroll or Hover to Load</div>
                                <span className="mt-4 animate-bounce opacity-50">🖱️</span>
                            </div>
                        )}

                        {loadState === 'loading' && (
                            <div className="h-48 bg-red-50 dark:bg-red-900/10 rounded-xl flex flex-col items-center justify-center text-red-500 animate-pulse">
                                <span className="animate-spin mb-4 text-4xl"></span>
                                <div className="font-bold text-sm">Downloading Chunk...</div>
                            </div>
                        )}

                        {loadState === 'loaded' && (
                            <div className="h-48 bg-gradient-to-br from-red-500 to-pink-600 rounded-xl flex flex-col items-center justify-center text-white shadow-lg animate-in zoom-in">
                                <span className="text-4xl mb-4">📦</span>
                                <div className="font-bold text-lg">Heavy Component Loaded!</div>
                                <div className="text-xs opacity-80">This JS was fetched lazily.</div>
                            </div>
                        )}

                    </div>

                    <div className="p-4 border-2 border-dashed border-gray-300 dark:border-gray-700 rounded-xl flex items-center justify-center h-32 text-gray-400">
                        Footer Content...
                    </div>
                </div>

            </div>

             <div className="mt-6 text-center text-gray-400 text-xs">
                 Try hovering the placeholder box inside the scroll area!
             </div>
        </div>
    );
}