Angular @defer w 2026: deklaratywny lazy loading dla szybszych aplikacji

Opanuj bloki @defer w Angularze do deklaratywnego lazy loadingu. Dogłębne omówienie wyzwalaczy, prefetchingu, hydratacji przyrostowej, zachowania w SSR i praktycznych wzorców wydajności.

Optymalizacja wydajności Angular defer loading i lazy loading

Bloki @defer w Angularze rozwiązują problem, którego lazy loading oparty na routerze nigdy nie potrafił rozwiązać: ładowanie pojedynczych komponentów, dyrektyw i pipe'ów na żądanie bez przebudowywania aplikacji na osobne trasy. Wprowadzony w Angularze 17 i ustabilizowany do wersji 22, @defer zamienia to, co wcześniej wymagało dynamicznych importów, flag *ngIf i konfiguracji webpacka, w jeden deklaratywny blok szablonu.

Co robi @defer

@defer instruuje kompilator Angulara, aby podzielił opakowane komponenty na osobne fragmenty JavaScript (chunki), ładowane dopiero po spełnieniu warunku wyzwalacza. Przeglądarka pobiera mniej JavaScriptu na starcie, co poprawia Largest Contentful Paint (LCP) i Time to First Byte (TTFB) bez ręcznej konfiguracji code-splittingu.

Jak @defer działa pod maską

Gdy kompilator Angulara napotyka blok @defer, wyodrębnia każdy samodzielny (standalone) komponent, dyrektywę i pipe znajdujące się wewnątrz niego do osobnego fragmentu. Główny pakiet jest dostarczany bez tych zależności. W czasie wykonania Angular ocenia warunek wyzwalacza i pobiera fragment za pomocą dynamicznego wywołania import().

Blok obsługuje cztery podbloki, które kontrolują to, co widzi użytkownik podczas cyklu ładowania:

app.component.htmltypescript
@defer (on viewport) {
  <analytics-dashboard />
} @placeholder {
  <div class="h-64 bg-muted animate-pulse"></div>
} @loading (minimum 200ms) {
  <loading-spinner />
} @error {
  <p>Failed to load the dashboard. Please refresh.</p>
}

@placeholder renderuje się przed uruchomieniem wyzwalacza. @loading pojawia się podczas pobierania fragmentu, z opcjonalnym czasem minimum, który zapobiega migotaniu (flicker). @error przechwytuje błędy sieci lub fragmentu. Parametr minimum w @loading zapobiega błyśnięciu spinnera, gdy fragment ładuje się szybko z pamięci podręcznej.

Jedno ograniczenie: każda zależność wewnątrz @defer musi być standalone. Komponenty niebędące standalone, zadeklarowane w NgModule, nie mogą być odroczone i zostaną załadowane zachłannie (eagerly) niezależnie od opakowania @defer.

Typy wyzwalaczy: kontrola momentu ładowania komponentów

Angular udostępnia siedem wbudowanych wyzwalaczy, z których każdy odpowiada innej strategii ładowania. Wiele wyzwalaczy można łączyć średnikami i są one oceniane jako warunki OR.

product-page.component.htmltypescript
// Loads when the browser goes idle (default)
@defer {
  <recommendation-engine />
}

// Loads when the element enters the viewport
@defer (on viewport) {
  <customer-reviews [productId]="product.id" />
}

// Loads on click or keydown
@defer (on interaction) {
  <product-configurator />
} @placeholder {
  <button>Configure this product</button>
}

// Loads on mouseenter or focusin
@defer (on hover) {
  <quick-preview />
}

// Loads after 3 seconds
@defer (on timer(3s)) {
  <chat-widget />
}

// Loads immediately after initial render
@defer (on immediate) {
  <notification-center />
}

Wyzwalacz on viewport wewnętrznie korzysta z Intersection Observer API. Wyzwalacz on idle deleguje do requestIdleCallback, co czyni go najbezpieczniejszym domyślnym wyborem dla treści poniżej linii zgięcia (below-the-fold). Wyzwalacz on timer przyjmuje czasy w milisekundach (500ms) lub sekundach (3s).

Wyzwalacz when dla warunków reaktywnych

Poza wyzwalaczami opartymi na zdarzeniach, when przyjmuje dowolne wyrażenie logiczne, w tym odczyty sygnałów:

dashboard.component.tstypescript
export class DashboardComponent {
  showAdvanced = signal(false);
}

// dashboard.component.html
@defer (when showAdvanced()) {
  <advanced-analytics />
} @placeholder {
  <button (click)="showAdvanced.set(true)">Show advanced analytics</button>
}

Łączenie on i when jest dozwolone. Angular traktuje je jako OR: blok ładuje się, gdy spełniony jest którykolwiek z warunków.

Prefetching: oddzielenie pobierania od wyświetlania

Prefetching oddziela moment pobierania fragmentu od momentu renderowania komponentu. Eliminuje to odczuwane opóźnienie, pobierając JavaScript zanim użytkownik go wyzwoli.

settings.component.htmltypescript
@defer (on interaction; prefetch on idle) {
  <account-settings />
} @placeholder {
  <button>Open settings</button>
}

W tym wzorcu przeglądarka pobiera fragment account-settings w czasie bezczynności. Gdy użytkownik kliknie, komponent renderuje się natychmiast, ponieważ kod jest już dostępny. Prefetching przyjmuje te same typy wyzwalaczy co główna klauzula on.

Częsty wzorzec dla dashboardów: prefetch ciężkich komponentów w czasie bezczynności, przy jednoczesnym odroczeniu renderowania do momentu wejścia w viewport.

analytics.component.htmltypescript
@defer (on viewport; prefetch on idle) {
  <chart-widget [data]="salesData()" />
} @placeholder (minimum 100ms) {
  <div class="h-48 bg-muted rounded-none"></div>
}

minimum w @placeholder zapobiega przeskokowi układu (layout flash), gdy użytkownik szybko przewija, a odroczony komponent ładuje się niemal natychmiast.

Gotowy na rozmowy o Angular?

Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.

@defer a renderowanie po stronie serwera (SSR)

Na serwerze bloki @defer domyślnie renderują zawartość @placeholder. Odroczony komponent nigdy nie jest renderowany po stronie serwera. To zachowanie jest celowe: pobieranie fragmentu ma sens wyłącznie w kontekście przeglądarki.

Dla treści krytycznej dla SEO oznacza to, że @placeholder musi zawierać sensowny HTML, a nie tylko spinner:

blog-post.component.htmltypescript
@defer (on viewport) {
  <comment-section [postId]="post.id" />
} @placeholder {
  <section aria-label="Comments">
    <h2>Comments</h2>
    <p>Loading comments...</p>
  </section>
}

Wyszukiwarki indeksują zawartość placeholdera podczas SSR. Opisowy placeholder zapewnia, że strona pozostaje kompletna semantycznie.

Hydratacja przyrostowa z @defer

Angular 19 wprowadził hydratację przyrostową w wersji developer preview, a Angular 22 stabilizuje ją jako funkcję gotową do produkcji. Hydratacja przyrostowa rozszerza @defer o wyzwalacz hydrate, który kontroluje, kiedy komponent renderowany na serwerze staje się interaktywny po stronie klienta.

product-page.component.htmltypescript
@defer (hydrate on viewport) {
  <product-gallery [images]="product.images" />
}

@defer (hydrate on interaction) {
  <product-reviews [productId]="product.id" />
}

W przeciwieństwie do standardowego @defer, hydratacja przyrostowa renderuje pełny HTML komponentu na serwerze. Klient otrzymuje kompletny markup, ale pomija hydratację komponentu aż do uruchomienia wyzwalacza. Efekt: przeglądarka natychmiast maluje pełną stronę, ale wykonanie JavaScriptu jest odroczone.

Zespół Angulara raportuje stałe poprawy wyników LCP rzędu 40-50% przy stosowaniu hydratacji przyrostowej na stronach o dużej ilości treści. Angular DevTools w wersji 22 wizualizuje stany hydratacji (oczekujący, zhydratowany, błąd) bezpośrednio w inspektorze komponentów.

Wzorce z praktyki i strategia wydajności

Wzorzec 1: Ciężki dashboard z wieloma odroczonymi panelami

dashboard.component.htmltypescript
<div class="grid grid-cols-2 gap-4">
  @defer (on viewport; prefetch on idle) {
    <sales-chart />
  } @placeholder {
    <skeleton-card height="300px" />
  }

  @defer (on viewport; prefetch on idle) {
    <user-activity-feed />
  } @placeholder {
    <skeleton-card height="300px" />
  }

  @defer (on interaction) {
    <export-panel />
  } @placeholder {
    <button>Export data</button>
  }
</div>

Każdy panel ładuje się niezależnie. Panel eksportu pozostaje niezaładowany, dopóki użytkownik wyraźnie go nie zażąda, co oszczędza pełne pobranie fragmentu dla użytkowników, którzy nigdy nie eksportują danych.

Wzorzec 2: Warunkowe ładowanie funkcji z sygnałami

editor.component.tstypescript
export class EditorComponent {
  user = inject(UserService).currentUser;
  isPremium = computed(() => this.user()?.plan === 'premium');
}

// editor.component.html
@defer (when isPremium()) {
  <ai-assistant />
} @placeholder {
  <upgrade-banner />
}

Użytkownicy z darmowym planem nigdy nie pobierają fragmentu asystenta AI. Baner z propozycją upgrade'u pełni rolę zarówno placeholdera, jak i zachęty do konwersji.

Unikanie typowych pułapek

Zagnieżdżanie bloków @defer z tym samym wyzwalaczem powoduje równoczesne pobieranie fragmentów. Rozłóż wyzwalacze pomiędzy zagnieżdżone bloki:

typescript
// Avoid: both fire on idle simultaneously
@defer {
  @defer {
    <nested-heavy-component />
  }
}

// Better: outer on idle, inner on viewport
@defer (on idle) {
  <wrapper-component />
}
// Inside wrapper-component template:
@defer (on viewport) {
  <nested-heavy-component />
}

Kolejna pułapka: używanie @defer dla komponentów znajdujących się już na krytycznej ścieżce renderowania. Odraczanie treści powyżej linii zgięcia (above-the-fold) zwiększa LCP, ponieważ przeglądarka musi pobrać i wykonać fragment przed namalowaniem. Zarezerwuj @defer dla treści poniżej linii zgięcia lub wyzwalanej przez użytkownika.

@defer kontra lazy loading oparty na routerze

Lazy loading oparty na routerze i @defer uzupełniają się nawzajem. Lazy loading routera dzieli na poziomie trasy, odraczając całe moduły funkcjonalne. @defer dzieli wewnątrz szablonu, odraczając pojedyncze komponenty.

| Aspekt | Lazy loading routera | @defer | |--------|-------------------|--------| | Granularność | Poziom trasy | Poziom komponentu | | Wyzwalacz | Zdarzenie nawigacji | viewport, idle, interaction, hover, timer, warunek | | Zachowanie w SSR | Pełny render na serwerze | Placeholder na serwerze | | Wymaga standalone | Nie (działa z NgModules) | Tak (tylko standalone) | | Prefetching | preloadingStrategy | klauzula prefetch on | | Zastosowanie | Moduły funkcjonalne, strony | Widżety, panele, warunkowy UI |

Typowa aplikacja używa obu: lazy loadingu routera dla obszarów funkcjonalnych najwyższego poziomu oraz @defer dla ciężkich komponentów w obrębie tych obszarów.

Pytania rekrutacyjne dotyczące Angular @defer

Rozmowy techniczne coraz częściej obejmują @defer, w miarę jak staje się on centralnym elementem strategii wydajności Angulara. Oto wzorce, które często pojawiają się w pytaniach rekrutacyjnych z Angulara.

P: Co się stanie, jeśli komponent niebędący standalone zostanie umieszczony wewnątrz @defer? Komponent ładuje się zachłannie, niezależnie od bloku @defer. Kompilator Angulara nie może wyodrębnić komponentów zadeklarowanych w NgModule do osobnych fragmentów. Nie jest zgłaszany żaden błąd, ale optymalizacja po cichu zawodzi.

P: Jak @defer zachowuje się podczas SSR? Serwer renderuje zawartość @placeholder. Odroczony komponent nigdy nie jest renderowany po stronie serwera. Hydratacja następuje po stronie klienta po uruchomieniu wyzwalacza (lub przez hydrate on w przypadku hydratacji przyrostowej).

P: Czy @defer można używać z OnPush change detection? Tak. @defer jest funkcją na poziomie szablonu i działa z dowolną strategią wykrywania zmian. Odroczony komponent po załadowaniu stosuje własną konfigurację wykrywania zmian.

P: Jaka jest różnica między on idle a on immediate? on idle czeka, aż przeglądarka zakończy wszystkie oczekujące zadania poprzez requestIdleCallback. on immediate uruchamia się tuż po zakończeniu przez Angular pierwszego przebiegu renderowania, bez czekania na stan bezczynności. on immediate jest przydatny dla komponentów, które powinny załadować się szybko, ale nie blokować pierwszego malowania.

Aby uzyskać szerszy zestaw materiałów do przygotowania na rozmowę z Angulara, zobacz moduł change detection w Angularze oraz moduł sygnałów Angulara.

Zacznij ćwiczyć!

Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.

Podsumowanie

  • @defer dzieli komponenty standalone na osobne fragmenty ładowane na żądanie, redukując początkowy rozmiar pakietu bez przebudowy tras
  • Siedem typów wyzwalaczy (idle, viewport, interaction, hover, timer, immediate, when) pokrywa każdą strategię ładowania, od pasywnej po sterowaną przez użytkownika
  • prefetch on oddziela pobieranie fragmentu od renderowania komponentu, eliminując odczuwane opóźnienie w komponentach wyzwalanych przez użytkownika
  • Hydratacja przyrostowa (hydrate on) renderuje pełny HTML na serwerze, odraczając wykonanie JavaScriptu do klienta, co daje 40-50% poprawy LCP na stronach o dużej ilości treści
  • Zawartość @placeholder renderuje się podczas SSR i musi być sensowna semantycznie ze względu na SEO
  • @defer uzupełnia lazy loading oparty na routerze: trasy dla podziału na poziomie funkcji, @defer dla podziału na poziomie komponentów w szablonach
  • Komponenty niebędące standalone nie mogą być odroczone i po cichu ładują się zachłannie

Zacznij ćwiczyć!

Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.

Tagi

#angular
#angular-defer
#lazy-loading
#performance
#ssr
#signals
#deep-dive

Udostępnij

Powiązane artykuły