Предварительная загрузка модулей Angular по требованию
Если вы используете Angular, это означает, что вам нужно разрезать код на модули и подумать, какие модули вы хотите загрузить и когда. Чем меньше вы отправите сначала, тем лучше. Модули, которые не загружаются, как только пользователь открывает страницу, загружаются лениво. Это означает, что они будут загружены позже. Но когда?
Доступны разные стратегии:
- Вы можете решить загрузить модули только тогда, когда они будут запрошены. Это поведение по умолчанию при отложенной загрузке модулей.
- Вы можете решить загрузить все модули как можно скорее в фоновом режиме. Это полезно, если вы знаете, что модули будут запрашиваться пользователем с очень высокой вероятностью. Для этого вы должны использовать предопределенную стратегию предварительной загрузки
PreloadAllModules
. - Вы можете решить предварительно загрузить некоторые модули, а некоторые нет. Это полезно, если вы знаете, что модуль запрашивается редко (например, административная часть вашего сайта), тогда как другие почти всегда нужны. Для этого требуется немного больше работы, вам нужно самостоятельно определить собственную стратегию предварительной загрузки. Это хорошо объясняется в документации.
Но что, если вам нужна некоторая информация, прежде чем вы решите, стоит ли предварительно загружать модуль или нет? Например, предположим, у вас есть несколько администраторов на вашем сайте. Если администратор входит в систему, весьма вероятно, что ей понадобится модуль администратора. Тогда имеет смысл загрузить этот модуль как можно скорее, но вы не хотите предварительно загружать его для всех пользователей. Вам нужно загружать этот модуль «по требованию», не когда пользователь заходит на страницу, а после того, как он вошел в систему.
Создать пример приложения
Давайте создадим простое приложение Angular с домашней страницей и административной областью.
ng new preloading --routing --style=scss
Мы будем использовать два модуля: модуль с контентом для всех пользователей, называемый контентом, и модуль для администраторов, называемый админ. В каждом из них давайте создадим простой компонент, к которому мы можем направить.
ng g module content/content --routing --module app
ng g module admin/admin --routing
ng g component content/content
ng g component admin/admin
Обратите внимание на разницу между генерацией административного модуля и модуля контента. Мы не добавляем модуль администратора в приложение, потому что хотим его лениво загрузить.
В модуле маршрутизации администратора мы направляем пустой путь к компоненту администратора.
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AdminComponent } from './admin/admin.component';
const routes: Routes = [{
path: '',
component: AdminComponent,
}];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class AdminRoutingModule { }
Hello admins
Мы делаем нечто подобное в модуле контента:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { ContentComponent } from './content/content.component';
const routes: Routes = [{
path: '',
component: ContentComponent,
}];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class ContentRoutingModule { }
Some content
В app-routing.module.ts
мы определяем маршрут нашего приложения.
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { ContentComponent } from './content/content/content.component';
const routes: Routes = [
{
path: 'admin',
loadChildren: () => import('./admin/admin.module').then(mod => mod.AdminModule),
},
{
path: '',
component: ContentComponent,
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
По умолчанию мы отображаем ContentComponent
, это означает, что модуль контента загружается с самого начала. Модуль администратора, с другой стороны, загружается только при явном запросе, то есть при запросе маршрута «admin».
В app.component.html
мы объявляем наш router-outlet
и добавляем кнопку для входа в админку.
Angular Preloading
Теперь, если вы нажмете кнопку администратора, вы должны увидеть «Hello admins». Если вы проверите свою сеть, вы увидите, что модуль администратора загружается в тот момент, когда вы нажимаете, а не раньше:
Загрузите модуль администратора по требованию
Если модуль администратора становится большим, администратор будет ждать несколько секунд после нажатия кнопки администратора. Мы могли бы сэкономить ее время, загрузив модуль сразу после того, как она войдет в систему. Для примера добавим кнопку «Войти в систему как администратор» на нашей странице. Нажав на эту кнопку, мы хотим видеть, что модуль администрирования загружается на нашей вкладке сети.
Angular Preloading
Таким же образом, если мы хотим при запуске приложения просто решить, какие модули загружать, нам нужно определить PreloadingStrategy
.
import { PreloadingStrategy, Route } from '@angular/router';
import { Observable, of } from 'rxjs';
import { OnDemandPreloadService } from './on-demand-preload.service';
import { Injectable } from '@angular/core';
import { mergeMap } from 'rxjs/operators';
@Injectable({
providedIn: 'root',
})
export class OnDemandPreloadStrategy implements PreloadingStrategy {
private preloadOnDemand$: Observable;
constructor(private preloadOnDemandService: OnDemandPreloadService) {
this.preloadOnDemand$ = this.preloadOnDemandService.subject;
}
preload(route: Route, load: () => Observable): Observable {
return this.preloadOnDemand$.pipe(
mergeMap(preloadPath => {
const shouldPreload = route.path === preloadPath;
return shouldPreload ? load() : of(null);
}),
);
}
}
PreloadingStrategy
должен реализовать функцию предварительной загрузки, которая принимает маршрут и функцию загрузки в качестве параметров и возвращает Observable. Как правило, функция предварительной загрузки проверяет некоторые условия на маршруте.
В нашем случае, однако, мы хотим дождаться, когда появится требование предварительной загрузки. У нас есть параметр preloadOnDemand$
, который является Observable. Требования будут выдаваться этим Observable. Когда приходит запрос, мы проверяем, что запрос касается нашего маршрута, и если да, модуль загружен.
Как вы можете видеть в конструкторе, эти требования исходят от службы предварительной загрузки:
import { Subject } from 'rxjs';
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class OnDemandPreloadService {
subject = new Subject();
startPreload(routePath: string) {
this.subject.next(routePath);
}
}
Когда вызывается метод startPreload
, путь указывается в subject
, что и было задано параметром preloadOnDemand$
в OnDemandPreloadStrategy
.
Мы должны указать нашему маршрутизатору использовать эту стратегию:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { ContentComponent } from './content/content/content.component';
import { OnDemandPreloadStrategy } from './on-demand-preload-strategy.service';
const routes: Routes = [
{
path: 'admin',
loadChildren: () => import('./admin/admin.module').then(mod => mod.AdminModule),
},
{
path: '',
component: ContentComponent,
}
];
@NgModule({
imports: [RouterModule.forRoot(
routes,
{
preloadingStrategy: OnDemandPreloadStrategy,
})],
exports: [RouterModule]
})
export class AppRoutingModule { }
Теперь нам нужно вызывать функцию startPreload
только тогда, когда пользователь нажимает кнопку «Log in as Admin»:
import { Component } from '@angular/core';
import { OnDemandPreloadService } from './on-demand-preload.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
constructor(
private onDemandPreloadService: OnDemandPreloadService,
) {}
preloadAdminModule() {
this.onDemandPreloadService.startPreload('admin');
}
}
Tadaa! Если вы сейчас проверите свою сеть и нажмете на кнопку, вы должны увидеть загружаемый модуль.
В примере это не приносит много. Но теперь вы можете загрузить модуль в любое время, после нажатия кнопки, после входа в систему, после перехода на данную страницу…
Надеюсь, это было полезно. Вы можете найти код в Github.