Spec-Zone.ru › Angular

Отложенная загрузка с @defer

Отложенные представления, также известные как блоки @defer, уменьшают размер начального пакета приложения, откладывая загрузку кода, который не является строго необходимым для первоначального отображения страницы. Это часто ускоряет начальную загрузку и улучшает показатели Core Web Vitals (CWV), прежде всего Largest Contentful Paint (LCP) и Time to First Byte (TTFB).

Чтобы использовать эту возможность, можно декларативно обернуть раздел шаблона в блок @defer:

@defer {
  <large-component />
}

Код любых компонентов, директив и каналов внутри блока @defer разделяется в отдельный файл JavaScript и загружается только при необходимости, после отображения остальной части шаблона.

Отложенные представления поддерживают различные триггеры, параметры предварительной загрузки и вложенные блоки для управления состояниями-заполнителями, загрузки и ошибки.

Какие зависимости откладываются?

При загрузке приложения можно откладывать компоненты, директивы, каналы и любые CSS-стили компонентов.

Чтобы зависимости внутри блока @defer загружались с задержкой, необходимо выполнение двух условий:

  1. Они должны быть самостоятельными. Зависимости, не являющиеся самостоятельными, нельзя откладывать, поэтому они по-прежнему загружаются сразу, даже если находятся внутри блоков @defer.
  2. На них нельзя ссылаться за пределами блоков @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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API