@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):
// Potrebujete routes, router outlet, navigáciu...
// Plus manuálny state management...
// Plus error handling...
// Celkovo: 80+ riadkov a veľa frustrácieNový spôsob (10 riadkov):
@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:
@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
| Funkcia | Router | Dynamic Import | @defer |
|---|---|---|---|
| Setup | Routes config | 40+ riadkov | 3 riadky |
| Loading states | Manuálne | Manuálne | Vstavané |
| Error handling | Manuálne | Manuálne | Vstavané |
| Cleanup | N/A | Manuálne (leak risk!) | Automatické |
| Triggery | Len navigácia | Manuálne | 7+ typov |
| Prefetching | Manuálne | Manuálne | Vstavané |
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
@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
@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ívneNetwork-Aware Loading
@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
// ❌ 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
// ❌ 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
// ❌ 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ú
@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
ng generate @angular/core:standaloneKrok 3: Nahraďte postupne
// PRED
async loadChart() {
const { ChartComponent } = await import('./chart');
this.vcr.createComponent(ChartComponent);
}
// PO
@defer (on interaction) {
<app-chart />
}
// 40 riadkov → 3 riadkyKapitola 9: Reálne výsledky
Pred @defer:
Bundle: 2.5 MB
First Contentful Paint: 3.2s
Lighthouse Score: 65Po @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á
@defer
Router
Používajte oba
Prefetch
Minimum times
Monitorujte
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. 😅