Отложенная загрузка через @defer¶
30.09.2026
Откладываемые представления, они же блоки @defer, уменьшают начальный размер пакета приложения: загрузка кода, без которого страница может отрисоваться сразу, откладывается. Часто это ускоряет первую загрузку и улучшает Core Web Vitals (CWV), в первую очередь Largest Contentful Paint (LCP) и Time to First Byte (TTFB).
Чтобы воспользоваться этой возможностью, участок шаблона декларативно оборачивают блоком @defer:
1 2 3 | |
Код любых компонентов, директив и пайпов внутри блока @defer выносится в отдельный файл JavaScript и загружается только когда это нужно, уже после отрисовки остального шаблона.
Откладываемые представления поддерживают разные триггеры, параметры предзагрузки и вложенные блоки для заполнителя, загрузки и состояния ошибки.
Какие зависимости откладываются?¶
При загрузке приложения можно отложить компоненты, директивы, пайпы и любые CSS-стили компонента.
Чтобы зависимости внутри блока @defer действительно откладывались, нужны два условия:
- Они должны быть автономными. Неавтономные зависимости отложить нельзя: они всё равно загружаются сразу, даже если стоят внутри блоков
@defer. - На них нельзя ссылаться вне блоков
@deferв том же файле. Если ссылка есть вне блока@deferили в запросах ViewChild, зависимости загружаются сразу.
Транзитивные зависимости компонентов, директив и пайпов, использованных в блоке @defer, не обязаны быть автономными: их по-прежнему можно объявить в NgModule, и они участвуют в отложенной загрузке.
Компилятор Angular порождает оператор динамического импорта для каждого компонента, директивы и пайпа, использованных в блоке @defer. Основное содержимое блока отрисовывается после того, как все импорты разрешатся. Angular не гарантирует какой-либо порядок этих импортов.
Как вести разные этапы отложенной загрузки¶
У блоков @defer есть несколько вложенных блоков, чтобы аккуратно провести разные этапы отложенной загрузки.
@defer¶
Это основной блок: он задаёт участок содержимого с ленивой загрузкой. Сразу он не отрисовывается — отложенное содержимое загружается и отрисовывается, когда срабатывает указанный триггер или выполняется условие when.
По умолчанию блок @defer срабатывает, когда состояние браузера становится idle.
1 2 3 | |
Содержимое-заполнитель через @placeholder¶
По умолчанию блоки @defer не отрисовывают содержимое, пока не сработают.
@placeholder — необязательный блок: он объявляет, что показывать, пока блок @defer не сработал.
1 2 3 4 5 | |
Блок необязателен, но некоторым триггерам для работы нужен либо @placeholder, либо ссылочная переменная шаблона. Подробнее — в разделе Триггеры.
Когда загрузка завершена, Angular заменяет содержимое заполнителя основным содержимым. В секции заполнителя можно использовать что угодно: обычный HTML, компоненты, директивы и пайпы. Учтите: зависимости блока заполнителя загружаются сразу.
Блок @placeholder принимает необязательный параметр minimum: минимальное время, в течение которого заполнитель показывается после первой отрисовки его содержимого.
1 2 3 4 5 | |
Параметр minimum задают в миллисекундах (ms) или секундах (s). Им можно убрать быстрое мерцание заполнителя, если отложенные зависимости приходят слишком быстро.
Содержимое загрузки через @loading¶
@loading — необязательный блок: он объявляет содержимое, которое показывается, пока загружаются отложенные зависимости. Когда загрузка запускается, он заменяет блок @placeholder.
1 2 3 4 5 6 7 | |
Его зависимости загружаются сразу (как у @placeholder).
Блок @loading принимает два необязательных параметра, чтобы убрать быстрое мерцание содержимого, когда отложенные зависимости приходят слишком быстро:
minimum— минимальное время, в течение которого показывается этот заполнительafter— сколько ждать после начала загрузки, прежде чем показать шаблон загрузки
1 2 3 4 5 | |
Оба параметра задают в миллисекундах (ms) или секундах (s). Таймеры обоих параметров начинаются сразу после того, как загрузка запущена.
Состояние ошибки через @error, если отложенная загрузка не удалась¶
@error — необязательный блок: он показывается, если отложенная загрузка не удалась. Как у @placeholder и @loading, зависимости блока @error загружаются сразу.
1 2 3 4 5 | |
Управление загрузкой отложенного содержимого триггерами¶
Триггеры задают, когда Angular загружает и показывает отложенное содержимое.
Когда блок @defer срабатывает, он заменяет содержимое заполнителя содержимым с ленивой загрузкой.
Несколько триггеров событий разделяют точкой с запятой ;, и они вычисляются как условия ИЛИ.
Есть два вида триггеров: on и when.
on¶
on задаёт условие, при котором срабатывает блок @defer.
Доступные триггеры:
| Триггер | Описание |
|---|---|
idle | Срабатывает, когда браузер простаивает. Поддерживает необязательный таймаут. |
viewport | Срабатывает, когда указанное содержимое входит в область просмотра |
interaction | Срабатывает, когда пользователь взаимодействует с указанным элементом |
hover | Срабатывает, когда указатель мыши оказывается над указанной областью |
immediate | Срабатывает сразу после того, как отрисовка неотложенного содержимого закончилась |
timer | Срабатывает через заданное время |
idle¶
Триггер idle загружает отложенное содержимое, когда браузер достигает состояния простоя, на основе requestIdleCallback. Для блока @defer это поведение по умолчанию.
Необязательно можно указать таймаут в миллисекундах: он передаётся в requestIdleCallback. Если браузер не запланирует обратный вызов достаточно скоро, работа выполнится не позже указанного таймаута.
1 2 3 4 5 6 7 8 9 10 11 | |
Настройка поведения idle¶
Поведение триггера idle настраивают собственной реализацией IdleService и регистрацией через provideIdleServiceWith в провайдерах приложения.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
viewport¶
Триггер viewport загружает отложенное содержимое, когда указанное содержимое входит в область просмотра, через Intersection Observer API. Наблюдаемым содержимым может быть содержимое @placeholder или явная ссылка на элемент.
По умолчанию @defer следит, когда заполнитель входит в область просмотра. У такого заполнителя должен быть один корневой элемент.
1 2 3 4 5 | |
Другой вариант — указать ссылочную переменную шаблона в том же шаблоне, что и блок @defer: это элемент, за входом которого в область просмотра следят. Переменную передают параметром триггера viewport.
1 2 3 4 | |
Чтобы настроить параметры IntersectionObserver, триггер viewport принимает объектный литерал. Литерал поддерживает все свойства второго параметра IntersectionObserver, кроме root. При записи объектным литералом триггер передают через свойство trigger.
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
interaction¶
Триггер interaction загружает отложенное содержимое, когда пользователь взаимодействует с указанным элементом через события click или keydown.
По умолчанию элементом взаимодействия служит заполнитель. У такого заполнителя должен быть один корневой элемент.
1 2 3 4 5 | |
Другой вариант — указать ссылочную переменную шаблона в том же шаблоне, что и блок @defer: это элемент, за взаимодействиями с которым следят. Переменную передают параметром триггера interaction.
1 2 3 4 | |
hover¶
Триггер hover загружает отложенное содержимое, когда указатель мыши оказывается над областью срабатывания, через события mouseover и focusin.
По умолчанию элементом взаимодействия служит заполнитель. У такого заполнителя должен быть один корневой элемент.
1 2 3 4 5 | |
Другой вариант — указать ссылочную переменную шаблона в том же шаблоне, что и блок @defer: это элемент, над которым оказывается указатель. Переменную передают параметром триггера hover.
1 2 3 4 | |
immediate¶
Триггер immediate загружает отложенное содержимое сразу. Отложенный блок начинает загружаться, как только закончилась отрисовка всего остального неотложенного содержимого.
1 2 3 4 5 | |
timer¶
Триггер timer загружает отложенное содержимое через заданное время.
1 2 3 4 5 | |
Параметр длительности задают в миллисекундах (ms) или секундах (s).
when¶
Триггер when принимает собственное условное выражение и загружает отложенное содержимое, когда условие становится истинным.
1 2 3 4 5 | |
Это одноразовая операция: блок @defer не возвращается к заполнителю, если после истинного значения условие становится ложным.
Предзагрузка данных через prefetch¶
Помимо условия, которое решает, когда показывать отложенное содержимое, можно указать триггер предзагрузки. Он загружает JavaScript, связанный с блоком @defer, ещё до показа отложенного содержимого.
Предзагрузка даёт более тонкое поведение: ресурсы можно начать подгружать ещё до того, как пользователь увидел блок @defer или поработал с ним, но когда взаимодействие вероятно скоро. Тогда ресурсы окажутся на месте быстрее.
Триггер предзагрузки записывают так же, как основной триггер блока, но с ключевым словом prefetch впереди. Основной триггер блока и триггер предзагрузки разделяют точкой с запятой (;).
В примере ниже предзагрузка начинается, когда браузер переходит в простой, а содержимое блока отрисовывается только после взаимодействия пользователя с заполнителем.
1 2 3 4 5 6 7 8 9 10 | |
Тестирование блоков @defer¶
Angular даёт API TestBed, чтобы проще тестировать блоки @defer и переключать разные состояния в тестах. По умолчанию блоки @defer в тестах проигрываются так же, как блок @defer вёл бы себя в настоящем приложении. Чтобы проходить состояния вручную, в конфигурации TestBed переключите поведение блока @defer на Manual.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | |
Работает ли @defer с NgModule?¶
Блоки @defer совместимы и с автономными компонентами, директивами и пайпами, и с теми, что основаны на NgModule. При этом отложить можно только автономные компоненты, директивы и пайпы. Зависимости на основе NgModule не откладываются и попадают в пакет, который загружается сразу.
Совместимость блоков @defer и Hot Module Reload (HMR)¶
Когда активна Hot Module Replacement (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
1 2 | |
parent.component.ts
1 2 3 4 5 6 7 8 9 | |
Исправление простое: импортируйте напрямую из собственного файла компонента.
1 | |
Этого достаточно, чтобы сборщик вынес компонент в собственный чанк и загрузил его лениво, когда сработает триггер.
Практические советы по откладыванию представлений¶
Не допускайте каскадной загрузки у вложенных блоков @defer¶
У вложенных блоков @defer должны быть разные триггеры, чтобы они не загружались одновременно: одновременная загрузка даёт каскад запросов и может ухудшить скорость загрузки страницы.
Не допускайте сдвигов вёрстки¶
Не откладывайте компоненты, которые видны в области просмотра пользователя при первой загрузке. Иначе Core Web Vitals могут ухудшиться из-за роста cumulative layout shift (CLS).
Если отложить такой компонент всё же нужно, не используйте триггеры immediate, timer, viewport и собственный when, из-за которых содержимое загружается во время первой отрисовки страницы.
Помните о доступности¶
При использовании блоков @defer учитывайте пользователей вспомогательных технологий, например программ чтения с экрана. Программа чтения, которая фокусируется на отложенном участке, сначала прочитает заполнитель или содержимое загрузки, но может не объявить изменения, когда загрузится отложенное содержимое.
Чтобы изменения отложенного содержимого объявлялись программам чтения с экрана, оберните блок @defer в элемент с живой областью:
1 2 3 4 5 6 7 8 9 10 11 | |
Так изменения объявляются пользователю при переходах (заполнитель → загрузка → содержимое/ошибка).
Источник: https://angular.dev/guide/templates/defer