Отложенная загрузка с @defer
Отложенные представления, также известные как блоки @defer, уменьшают размер начального пакета приложения, откладывая загрузку кода, который не является строго необходимым для первоначального отображения страницы. Это часто ускоряет начальную загрузку и улучшает показатели Core Web Vitals (CWV), прежде всего Largest Contentful Paint (LCP) и Time to First Byte (TTFB).
Чтобы использовать эту возможность, можно декларативно обернуть раздел шаблона в блок @defer:
@defer {
<large-component />
}
Код любых компонентов, директив и каналов внутри блока @defer разделяется в отдельный файл JavaScript и загружается только при необходимости, после отображения остальной части шаблона.
Отложенные представления поддерживают различные триггеры, параметры предварительной загрузки и вложенные блоки для управления состояниями-заполнителями, загрузки и ошибки.
Какие зависимости откладываются?
При загрузке приложения можно откладывать компоненты, директивы, каналы и любые CSS-стили компонентов.
Чтобы зависимости внутри блока @defer загружались с задержкой, необходимо выполнение двух условий:
-
Они должны быть самостоятельными. Зависимости, не являющиеся самостоятельными, нельзя откладывать, поэтому они по-прежнему загружаются сразу, даже если находятся внутри блоков
@defer. -
На них нельзя ссылаться за пределами блоков
@deferв том же файле. Если на них ссылаются за пределами блока@deferили в запросах ViewChild, зависимости будут загружены сразу.
Транзитивные зависимости компонентов, директив и каналов, используемых в блоке @defer, не обязательно должны быть самостоятельными; транзитивные зависимости по-прежнему можно объявлять в NgModule и использовать при отложенной загрузке.
Компилятор Angular создает оператор динамического импорта для каждого компонента, директивы и канала, используемых в блоке @defer. Основное содержимое блока отображается после завершения всех импортов. Angular не гарантирует определенный порядок этих импортов.
Как управлять различными этапами отложенной загрузки
Блоки @defer включают несколько вложенных блоков, позволяющих корректно обрабатывать различные этапы процесса отложенной загрузки.
@defer
Это основной блок, определяющий раздел содержимого, загружаемого по требованию. Изначально он не отображается: отложенное содержимое загружается и отображается после срабатывания указанного триггера или выполнения условия when.
По умолчанию блок @defer срабатывает, когда состояние браузера становится неактивным.
@defer {
<large-component />
} Отображение содержимого-заполнителя с помощью @placeholder
По умолчанию блоки defer не отображают содержимое до срабатывания триггера.
Блок @placeholder является необязательным и задает содержимое, которое нужно показать до срабатывания блока @defer.
@defer {
<large-component />
} @placeholder {
<p>Placeholder content</p>
}
Хотя этот блок необязателен, для работы некоторых триггеров может потребоваться блок @placeholder или переменная-ссылка на шаблон. Подробнее см. в разделе «Триггеры».
После завершения загрузки Angular заменяет содержимое-заполнитель основным содержимым. В разделе заполнителя можно использовать любое содержимое, включая обычный HTML, компоненты, директивы и каналы. Учтите, что зависимости блока-заполнителя загружаются сразу.
Блок @placeholder принимает необязательный параметр, задающий minimum время, в течение которого нужно показывать заполнитель после его первоначального отображения.
@defer {
<large-component />
} @placeholder (minimum 500ms) {
<p>Placeholder content</p>
}
Этот параметр minimum задается в миллисекундах (мс) или секундах (с). Его можно использовать, чтобы избежать быстрого мигания содержимого-заполнителя, если отложенные зависимости загружаются быстро.
Отображение содержимого загрузки с помощью @loading
Блок @loading является необязательным и позволяет задать содержимое, отображаемое во время загрузки отложенных зависимостей. После запуска загрузки он заменяет блок @placeholder.
@defer {
<large-component />
} @loading {
<img alt="loading..." src="loading.gif" />
} @placeholder {
<p>Placeholder content</p>
}
Его зависимости загружаются сразу (как и зависимости блока @placeholder).
Блок @loading принимает два необязательных параметра, которые помогают избежать быстрого мигания содержимого, возникающего, когда отложенные зависимости загружаются быстро:
-
minimum— минимальное время отображения этого заполнителя -
after— время ожидания после начала загрузки, прежде чем показать шаблон загрузки
@defer {
<large-component />
} @loading (after 100ms; minimum 1s) {
<img alt="loading..." src="loading.gif" />
}
Оба параметра задаются в миллисекундах (мс) или секундах (с). Кроме того, отсчет времени для обоих параметров начинается сразу после запуска загрузки.
Отображение состояния ошибки при сбое отложенной загрузки с помощью @error
Блок @error является необязательным и отображается при сбое отложенной загрузки. Как и в случае с блоками @placeholder и @loading, зависимости блока @error загружаются сразу.
@defer {
<large-component />
} @error {
<p>Failed to load large component.</p>
} Управление загрузкой отложенного содержимого с помощью триггеров
Можно указать триггеры, определяющие, когда Angular загружает и отображает отложенное содержимое.
Когда срабатывает блок @defer, он заменяет содержимое-заполнитель лениво загруженным содержимым.
Можно определить несколько триггеров событий, разделив их точкой с запятой, ;; они будут вычисляться как условия ИЛИ.
Существует два типа триггеров: on и when.
on
on задает условие срабатывания блока @defer.
Доступны следующие триггеры:
| Триггер | Описание |
|---|---|
idle | Срабатывает, когда браузер переходит в состояние простоя. Поддерживает необязательный тайм-аут. |
viewport | Срабатывает, когда указанное содержимое попадает в область просмотра |
interaction | Срабатывает, когда пользователь взаимодействует с указанным элементом |
hover | Срабатывает, когда указатель мыши оказывается над указанной областью |
immediate | Срабатывает сразу после завершения отображения содержимого, не подлежащего отложенной загрузке |
timer | Срабатывает по истечении заданного времени |
idle
Триггер idle загружает отложенное содержимое, когда браузер переходит в состояние простоя, определяемое на основе requestIdleCallback. Это поведение по умолчанию для блока defer.
При необходимости можно указать тайм-аут в миллисекундах, который передается в requestIdleCallback. Если браузер не запланирует обратный вызов достаточно быстро, операция будет выполнена не позднее указанного тайм-аута.
<!-- @defer (on idle) -->
@defer {
<large-cmp />
} @placeholder {
<div>Large component placeholder</div>
}
<!-- With a 500ms timeout -->
@defer (on idle(500)) {
<large-cmp />
} Настройка поведения idle
Можно настроить триггер idle, предоставив собственную реализацию IdleService и зарегистрировав ее с помощью provideIdleServiceWith в списке провайдеров приложения.
@Service()
class CustomIdleService implements IdleService {
requestOnIdle(callback: (deadline?: IdleDeadline) => void, options?: IdleRequestOptions) {
// Custom idle scheduling logic can be implemented here.
}
cancelOnIdle(id: number) {
// Implement custom idle cancellation here.
}
}
bootstrapApplication(App, {
providers: [provideIdleServiceWith(CustomIdleService)],
}); viewport
Триггер viewport загружает отложенное содержимое, когда указанное содержимое попадает в область просмотра. Для этого используется API Intersection Observer. Отслеживаемым содержимым может быть содержимое @placeholder или ссылка на явный элемент.
По умолчанию триггер @defer отслеживает появление заполнителя в области просмотра. У заполнителей, используемых таким образом, должен быть один корневой элемент.
@defer (on viewport) {
<large-cmp />
} @placeholder {
<div>Large component placeholder</div>
}
Также можно указать переменную-ссылку на шаблон в том же шаблоне, что и блок @defer, чтобы отслеживать попадание этого элемента в область просмотра. Эта переменная передается как параметр триггера viewport.
<div #greeting>Hello!</div>
@defer (on viewport(greeting)) {
<greetings-cmp />
}
Чтобы настроить параметры IntersectionObserver, триггер viewport поддерживает передачу объектного литерала. Литерал поддерживает все свойства второго параметра IntersectionObserver, кроме root. При использовании объектного литерала триггер необходимо передавать через свойство trigger.
<div #greeting>Hello!</div>
<!-- With options and a trigger -->
@defer (on viewport({trigger: greeting, rootMargin: '100px', threshold: 0.5})) {
<greetings-cmp />
}
<!-- With options and an implied trigger -->
@defer (on viewport({rootMargin: '100px', threshold: 0.5})) {
<greetings-cmp />
} @placeholder {
<div>Implied trigger</div>
} interaction
Триггер interaction загружает отложенное содержимое, когда пользователь взаимодействует с указанным элементом посредством событий click или keydown.
По умолчанию элементом взаимодействия служит заполнитель. У заполнителей, используемых таким образом, должен быть один корневой элемент.
@defer (on interaction) {
<large-cmp />
} @placeholder {
<div>Large component placeholder</div>
}
Также можно указать переменную-ссылку на шаблон в том же шаблоне, что и блок @defer, чтобы отслеживать взаимодействие с этим элементом. Эта переменная передается как параметр триггера viewport.
<div #greeting>Hello!</div>
@defer (on interaction(greeting)) {
<greetings-cmp />
} hover
Триггер hover загружает отложенное содержимое, когда указатель мыши оказывается над целевой областью и срабатывают события mouseover и focusin.
По умолчанию элементом взаимодействия служит заполнитель. У заполнителей, используемых таким образом, должен быть один корневой элемент.
@defer (on hover) {
<large-cmp />
} @placeholder {
<div>Large component placeholder</div>
}
Также можно указать переменную-ссылку на шаблон в том же шаблоне, что и блок @defer, чтобы отслеживать попадание этого элемента в область просмотра. Эта переменная передается как параметр триггера viewport.
<div #greeting>Hello!</div>
@defer (on hover(greeting)) {
<greetings-cmp />
} immediate
Триггер immediate немедленно загружает отложенное содержимое. Это значит, что отложенный блок загружается сразу после завершения отображения всего остального содержимого, не подлежащего отложенной загрузке.
@defer (on immediate) {
<large-cmp />
} @placeholder {
<div>Large component placeholder</div>
} timer
Триггер timer загружает отложенное содержимое по истечении заданного времени.
@defer (on timer(500ms)) {
<large-cmp />
} @placeholder {
<div>Large component placeholder</div>
}
Параметр длительности необходимо указать в миллисекундах (ms) или секундах (s).
when
Триггер when принимает пользовательское условное выражение и загружает отложенное содержимое, когда условие становится истинным.
@defer (when condition) {
<large-cmp />
} @placeholder {
<div>Large component placeholder</div>
}
Эта операция выполняется один раз: блок @defer не возвращается к заполнителю, если после того, как условие стало истинным, оно снова принимает ложное значение.
Предварительная загрузка данных с помощью prefetch
Помимо указания условия, определяющего, когда отображается отложенное содержимое, можно задать триггер предварительной загрузки. Он позволяет загрузить JavaScript, связанный с блоком @defer, до отображения отложенного содержимого.
Предварительная загрузка позволяет реализовать более сложные сценарии, например начать загружать ресурсы до того, как пользователь увидит блок defer или взаимодействует с ним, но когда такое взаимодействие может произойти в ближайшее время, чтобы ресурсы стали доступны быстрее.
Триггер предварительной загрузки можно задать так же, как основной триггер блока, добавив перед ним ключевое слово prefetch. Основной триггер блока и триггер предварительной загрузки разделяются точкой с запятой (;).
В приведенном ниже примере предварительная загрузка начинается, когда браузер переходит в состояние простоя, а содержимое блока отображается только после взаимодействия пользователя с заполнителем.
@defer (on interaction; prefetch on idle) {
<large-cmp />
} @placeholder {
<div>Large component placeholder</div>
}
<!-- Prefetching with a 500ms idle timeout -->
@defer (on interaction; prefetch on idle(500)) {
<large-cmp />
} Тестирование блоков @defer
Angular предоставляет API TestBed, упрощающие тестирование блоков @defer и переключение между различными состояниями во время тестирования. По умолчанию блоки @defer в тестах проходят те же этапы, что и блок defer в реальном приложении. Чтобы вручную переключать состояния, можно изменить поведение блока defer на Manual в конфигурации TestBed.
it('should render a defer block in different states', async () => {
// configures the defer block behavior to start in "paused" state for manual control.
TestBed.configureTestingModule({deferBlockBehavior: DeferBlockBehavior.Manual});
@Component({
// ...
template: `
@defer {
<large-component />
} @placeholder {
Placeholder
} @loading {
Loading...
}
`,
})
class ExampleA {}
// Create component fixture.
const componentFixture = TestBed.createComponent(ExampleA);
// Retrieve the list of all defer block fixtures and get the first block.
const deferBlockFixture = (await componentFixture.getDeferBlocks())[0];
// Renders placeholder state by default.
expect(componentFixture.nativeElement.innerHTML).toContain('Placeholder');
// Render loading state and verify rendered output.
await deferBlockFixture.render(DeferBlockState.Loading);
expect(componentFixture.nativeElement.innerHTML).toContain('Loading');
// Render final state and verify the output.
await deferBlockFixture.render(DeferBlockState.Complete);
expect(componentFixture.nativeElement.innerHTML).toContain('large works!');
}); Работает ли @defer с NgModule?
Блоки @defer совместимы как с самостоятельными компонентами, директивами и каналами, так и с компонентами, директивами и каналами на основе NgModule. Однако откладывать можно только самостоятельные компоненты, директивы и каналы. Зависимости на основе NgModule не откладываются и включаются в пакет, загружаемый сразу.
Совместимость блоков @defer с горячей заменой модулей (HMR)
Когда включена горячая замена модулей (HMR), все фрагменты блоков @defer загружаются сразу, независимо от настроенных триггеров. Чтобы восстановить стандартное поведение триггеров, отключите HMR, запустив приложение с флагом --no-hmr.
Как @defer работает с серверным рендерингом (SSR) и генерацией статических сайтов (SSG)?
По умолчанию при рендеринге приложения на сервере (с помощью SSR или SSG) блоки defer всегда отображают свои @placeholder (или ничего, если заполнитель не задан), а триггеры не срабатывают. На клиенте содержимое блока @placeholder гидратируется, и триггеры активируются.
Чтобы отображать основное содержимое блоков @defer на сервере (как при SSR, так и при SSG), включите функцию инкрементальной гидратации и настройте триггеры hydrate для нужных блоков.
Barrel-файлы и лениво загружаемые фрагменты
Если вы используете @defer, но в результатах сборки не видите отдельного лениво загружаемого фрагмента, проверьте, как импортируется отложенный компонент. Частая причина — импорт через barrel-файл (index.ts): сборщики рассматривают barrel-файл как единый модуль и оставляют все его экспорты вместе, поэтому компонент попадает в основной пакет независимо от @defer.
// index.ts
export {HeavyComponent} from './heavy.component';
export {OtherComponent} from './other.component';
// parent.component.ts
import {HeavyComponent} from './index'; // pulls in OtherComponent too
@Component({
imports: [HeavyComponent],
template: `@defer {
<heavy-component />
}`,
})
export class ParentComponent {}
Решение простое — импортируйте компонент непосредственно из его файла:
import {HeavyComponent} from './heavy.component';
Этого достаточно, чтобы сборщик поместил его в отдельный фрагмент и лениво загружал при срабатывании триггера.
Рекомендации по отложенной загрузке представлений
Избегайте каскадной загрузки вложенных блоков @defer
Если у вас есть вложенные блоки @defer, задайте для них разные триггеры, чтобы они не загружались одновременно. Одновременная загрузка приводит к каскадным запросам и может отрицательно сказаться на скорости загрузки страницы.
Избегайте сдвигов макета
Не откладывайте загрузку компонентов, видимых в области просмотра пользователя при первоначальной загрузке. Это может отрицательно повлиять на показатели Core Web Vitals из-за увеличения совокупного сдвига макета (CLS).
Если отложенная загрузка необходима, избегайте триггеров immediate, timer, viewport и пользовательских триггеров when, которые запускают загрузку содержимого во время первоначального отображения страницы.
Помните о доступности
При использовании блоков @defer учитывайте влияние на пользователей вспомогательных технологий, например программ чтения с экрана. Если программа чтения с экрана переводит фокус на отложенный раздел, сначала она прочитает содержимое-заполнитель или загрузки, но может не объявить об изменениях после загрузки отложенного содержимого.
Чтобы программа чтения с экрана сообщала об изменениях отложенного содержимого, оберните блок @defer в элемент с живой областью:
<div aria-live="polite" aria-atomic="true">
@defer (on timer(2000)) {
<user-profile [user]="currentUser" />
} @placeholder {
Loading user profile...
} @loading {
Please wait...
} @error {
Failed to load profile
}
</div>
Это гарантирует, что при переходах (заполнитель → загрузка → содержимое/ошибка) пользователю будут объявлены соответствующие изменения.
Super-powered by Google ©2010–2025.
Code licensed under an MIT-style License. Documentation licensed under CC BY 4.0.
https://angular.dev/guide/templates/defer