Obsah článku / Contents
@defer: Keď Angular konečne pochopil lazy loading - Prax a migrácia (Časť 2)

Kapitola 4: Reálny súboj

Poďme porovnať staré vs nové na reálnych scenároch.

Scenár: Heavy Chart Component

Starý spôsob (80+ riadkov):

typescript
// Potrebujete routes, router outlet, navigáciu...
// Plus manuálny state management...
// Plus error handling...
// Celkovo: 80+ riadkov a veľa frustrácie

Nový spôsob (10 riadkov):

typescript
@Component({
  template: `
    <button (click)="showChart.set(true)">Zobraziť Chart</button>

    @defer (when showChart()) {
      <app-heavy-chart [data]="chartData()" />
    } @loading {
      <app-spinner />
    } @error {
      <div>Nepodarilo sa načítať chart</div>
    }
  `
})
export class DashboardComponent {
  showChart = signal(false);
  chartData = signal([/* data */]);
}

Vážne. To je všetko.

Scenár: Infinite Scroll

Starý spôsob: 120+ riadkov s manuálnym IntersectionObserver, cleanup, loading state...

Nový spôsob:

typescript
@Component({
  template: `
    <div class="posts">
      @for (post of posts(); track post.id) {
        <app-post [post]="post" />
      }
    </div>

    @defer (on viewport) {
      <app-more-posts (loaded)="handleMorePosts($event)" />
    } @loading {
      <div class="loader">Načítavam...</div>
    } @placeholder {
      <div class="sentinel">Scrolluj pre viac</div>
    }
  `
})
export class PostsComponent {
  posts = signal<Post[]>([]);

  handleMorePosts(newPosts: Post[]) {
    this.posts.update(posts => [...posts, ...newPosts]);
  }
}

// 30 riadkov. 4× menej kódu. 0 memory leakov.

Kapitola 5: Porovnávacia matica

FunkciaRouterDynamic Import@defer
SetupRoutes config40+ riadkov3 riadky
Loading statesManuálneManuálneVstavané
Error handlingManuálneManuálneVstavané
CleanupN/AManuálne (leak risk!)Automatické
TriggeryLen navigáciaManuálne7+ typov
PrefetchingManuálneManuálneVstavané

Verdikt: @defer vyhráva vo všetkých kategóriách okrem jednej - router je stále potrebný pre page-level navigáciu. Ale pre component-level lazy loading? @defer je kráľ.


Kapitola 6: Pokročilé vzory

Skeleton Loading s kontrolou časovania

typescript
@defer (on viewport; prefetch on idle) {
  <app-user-profile [userId]="userId()" />
} @placeholder (minimum 500ms) {
  <!-- Zabraňuje bliknutiu -->
  <div class="skeleton">
    <div class="skeleton-avatar"></div>
    <div class="skeleton-name"></div>
  </div>
} @loading (minimum 500ms; after 100ms) {
  <!-- Zobrazí len ak trvá dlhšie ako 100ms -->
  <div class="skeleton shimmer">...</div>
}

// Profesionálny UX bez blikania!

Progressive Enhancement

typescript
@Component({
  template: `
    <!-- Základný obsah (vždy načítaný) -->
    <article class="post">
      <h1>{{ post.title }}</h1>
      <div class="content">{{ post.content }}</div>
    </article>

    <!-- Vylepšenia (lazy loaded) -->
    @defer (on viewport) {
      <app-comments [postId]="post.id" />
    }

    @defer (on interaction) {
      <app-share-buttons [post]="post" />
    }

    @defer (on hover; prefetch on idle) {
      <app-related-posts [postId]="post.id" />
    }
  `
})

// Core content funguje okamžite
// Vylepšenia sa načítavajú progresívne

Network-Aware Loading

typescript
@Component({
  template: `
    @defer (when isFastConnection()) {
      <app-high-quality-images />
    }

    @defer (when isSlowConnection(); on interaction) {
      <app-low-quality-images />
    } @placeholder {
      <button>Načítať obrázky</button>
    }
  `
})

// Rýchla sieť? Načítaj hneď.
// Pomalá sieť? Počkaj na interakciu.

Kapitola 7: Úskalia a nástrahy

Každá superschopnosť má svoje limity. Tu sú veci, na ktoré si treba dať pozor.

Nástraha #1: Musí byť standalone

typescript
// ❌ NEBUDE fungovať
@Component({
  standalone: false,  // NgModule komponent
})
export class OldComponent {}

@defer {
  <app-old-component />  // Načíta sa OKAMŽITE! ⚠️
}

// ✅ BUDE fungovať
@Component({
  standalone: true,
})
export class NewComponent {}

@defer {
  <app-new-component />  // Lazy loadne správne ✅
}

Nástraha #2: Použitie mimo @defer

typescript
// ❌ Problém
@defer {
  <app-chart />
}
<app-chart />  // Aj toto! Teraz sa načíta všetko okamžite! ⚠️

// ✅ Riešenie: Rôzne komponenty
@defer {
  <app-chart-heavy />
}
<app-chart-light />

Nástraha #3: ViewChild = smrť lazy loadingu

typescript
// ❌ ViewChild referencia zabije lazy loading
@ViewChild(ChartComponent) chart?: ChartComponent;

@defer {
  <app-chart />  // Načíta sa okamžite! ⚠️
}

Nástraha #4: Vnorené timery sa sčítavajú

typescript
@defer (on timer(2s)) {
  <app-outer />  <!-- 2s -->

  @defer (on timer(2s)) {
    <app-inner />  <!-- 4s celkovo! -->
  }
}

Kapitola 8: Migračná stratégia

Krok 1: Identifikujte kandidátov

Dobrí kandidáti:

  • Below-the-fold obsah
  • Ťažké charty/tabuľky
  • Modálne dialógy
  • Komentáre
  • Analytics widgety

Zlí kandidáti:

  • Above-the-fold obsah
  • Navigácia
  • Kritické užívateľské info

Krok 2: Konvertujte na standalone

bash
ng generate @angular/core:standalone

Krok 3: Nahraďte postupne

typescript
// PRED
async loadChart() {
  const { ChartComponent } = await import('./chart');
  this.vcr.createComponent(ChartComponent);
}

// PO
@defer (on interaction) {
  <app-chart />
}

// 40 riadkov → 3 riadky

Kapitola 9: Reálne výsledky

Pred @defer:

Bundle: 2.5 MB
First Contentful Paint: 3.2s
Lighthouse Score: 65

Po @defer:

Bundle: 800 KB (-68%)
First Contentful Paint: 1.1s (-66%)
Lighthouse Score: 95 (+30)

Nie sú to marketingové čísla. Toto sú reálne výsledky z produkcie.


Epilóg: Konečne pokoj

Pred Angular 17:

Developer: "Chcem lazy loadnúť komponent"
Angular: "Tu máš 4 zložité spôsoby, vyber si"
Developer: *plače*

Po Angular 17:

Developer: "Chcem lazy loadnúť komponent"

@defer {
  <app-component />
}

Developer: "...to je všetko?"
Angular: "To je všetko."
Developer: "Milujem ťa."

Záver: Zlaté pravidlá

1

@defer

pre component-level lazy loading
2

Router

pre page-level lazy loading
3

Používajte oba

pre maximálny výkon
4

Prefetch

pre okamžitý UX
5

Minimum times

proti blikaniu
6

Monitorujte

bundle sizes
typescript
if (you.buildAngularApps()) {
  migrate.toStandalone();
  embrace.defer();
  make.usersHappy();
}

Článok napísal Angular developer, ktorý prežil ViewContainerRef hell a IntersectionObserver nočné mory. Teraz žije v pokoji s @defer blocks.

P.S.: Ak ešte používate NgComponentOutlet pre lazy loading... prosím prestaňte. @defer je lepší. V každom ohľade. Vyskúšajte. Uvidíte. 😅

Všetky články