如何在 Angular 中手动延迟加载模块和组件
在Angular企业级应用中,通常需要通过HTTP请求从服务器加载包含UI配置的配置信息。基于此配置数据,需要对多个模块和/或组件进行延迟加载,并将其路由动态添加到应用中。
在这篇博文中,我想演示如何使用 Angular 9+ 在运行时延迟加载模块和组件。
StackBlitz 演示包含了以下章节中描述的代码。
该演示程序的源代码可在GitHub上找到。
使用路由器的懒加载模块
延迟加载:需要时再加载
从 Angular 8 开始,我们可以使用浏览器内置的动态导入功能在 Angular 中异步加载 JavaScript 模块。
import(...)可以使用以下新语法在路由配置中定义延迟加载模块loadChildren:
@NgModule({
imports: [
RouterModule.forRoot([
{
path: 'lazy',
loadChildren: () =>
import('./lazy/lazy.module').then(m => m.LazyModule),
},
]),
],
})
export class AppModule {}
使用 Angular 8(或更早版本)时,您需要编写代码loadChildren: './lazy/lazy.module#LazyModule来启用 Angular 路由器的模块延迟加载,因为它不支持该import(...)语法。
然后,Angular CLI 会自动为该模块创建一个单独的 JavaScript 包,该包仅在选定的路由被激活时才会从服务器加载。
LazyModule如果向模块的任何数组中添加元素imports,它将被立即加载。
手动延迟加载模块
有时,您可能希望更好地控制延迟加载过程,并在特定事件发生后(例如按钮按下)触发加载过程。通常,事件发生后,会异步访问某个资源(例如通过 HTTP 调用后端)以获取配置文件,该文件包含有关需要延迟加载的模块和/或组件的信息。
在我的演示中,我实现了一个功能LazyLoaderService来展示这种行为:
@Injectable({
providedIn: 'root',
})
export class LazyLoaderService {
private lazyMap: Map<string, Promise<unknown>> = new Map();
constructor() {}
getLazyModule(key: string): Promise<unknown> {
return this.lazyMap.get(key);
}
loadLazyModules(): Observable<number | void> {
return of(1).pipe(
delay(2000),
tap(() => {
this.lazyMap.set(
'lazy',
import('./lazy/lazy.module').then(m => m.LazyModule)
);
})
);
}
}
该loadLazyModules方法模拟后端请求。请求成功后,将使用特定语法注册一个模块import(...)。如果您现在运行应用程序,您会看到为该模块创建了一个单独的代码块,但它尚未在浏览器中加载。
模块承诺存储在一个Map带有键的文件中,以便以后能够访问它。
现在我们可以在onClick处理程序中调用此方法AppComponent,并动态地向路由器配置添加路由:
constructor(
private router: Router,
private lazyLoaderService: LazyLoaderService
) {}
loadLazyModule(): void {
this.lazyLoaderService.loadLazyModules().subscribe(() => {
const config = this.router.config;
config.push({
path: 'lazy',
loadChildren: () => this.lazyLoaderService.getLazyModule('lazy')
});
this.router.resetConfig(config);
this.router.navigate(['lazy']);
});
}
我们通过依赖注入从路由器获取当前的路由器配置,并将我们的新路由推送到其中。
如果您的路由配置中包含通配符路由(`$ **`),请务必小心。通配符路由必须始终位于路由数组的最后一个索引位置,因为它匹配所有 URL,只有在没有其他路由匹配成功的情况下才能选择它。
接下来,我们需要重置用于导航和生成链接的路由器配置,方法是调用resetConfig包含延迟加载模块路由的新配置。最后,我们导航到新加载的路由,看看它是否有效:
点击“加载延迟加载模块”按钮后,我们观察到以下三件事发生:
- 已从服务器请求懒加载模块的代码块,同时显示加载指示器。
/lazy加载完成后,浏览器 URL 会更改为新路由。- 延迟加载的模块已加载并
LazyHomeComponent渲染。 - 工具栏显示新条目
工具栏中动态显示可用路由是通过遍历路由器配置中的可用路由来实现的app.component.html:
<mat-toolbar color="primary">
<mat-toolbar-row>
<a
class="router-link"
*ngFor="let route of routes"
[routerLink]="route.path"
routerLinkActive="active-link"
>{{ route.path | uppercase }}</a
>
</mat-toolbar-row>
</mat-toolbar>
收藏懒加载路线
一个典型的需求是,用户希望在应用程序中为经常访问的某些 URL 创建书签。让我们用我们当前的实现来尝试一下:
重新加载延迟加载的路由会导致错误:Error: Cannot match any routes. URL Segment: 'lazy'
ngOnInit在当前的实现中,我们仅通过点击“加载延迟模块”按钮来加载模块,但我们还需要一个基于当前激活路由的触发器。因此,我们需要在方法中添加以下代码块AppComponent:
ngOnInit(): void {
this.router.events.subscribe(async routerEvent => {
if (routerEvent instanceof NavigationStart) {
if (routerEvent.url.includes('lazy') && !this.isLazyRouteAvailable()) {
this.loadLazyModule(routerEvent.url);
}
}
});
this.routes = this.router.config;
}
private isLazyRouteAvailable(): boolean {
return this.router.config.filter(c => c.path === 'lazy').length > 0;
}
我们订阅NavigationStartAngular 路由器的事件,如果 URL 包含我们的懒加载路由,我们会检查它是否已经在路由器配置中,否则我们会加载它。
现在可以将该 URL 添加到书签,应用程序将在路由激活后延迟加载该模块。
手动加载角度分量
我们还可以更进一步,在手动延迟加载的模块中动态加载 Angular 组件。
在 Angular 2 到 8 版本中,动态加载组件相当复杂。如果您需要针对这些版本的解决方案,可以参考流行的hero-loader 包。从 Angular 9 开始,动态加载组件就简单多了,下面我将为您详细介绍具体步骤。
我们的 LazyModule 包含一个带有占位符组件的子路由,该组件应该显示我们动态加载的组件:
export const LAZY_ROUTES: Routes = [
{
path: '',
component: LazyHomeComponent,
children: [
{
path: 'dynamic-component',
component: PlaceholderComponent,
},
],
},
];
占位符组件的模板仅包含一个<ng-template>HTML 标签:
<ng-template></ng-template>
现在,我们在初始化完成后placeholder.component.ts动态加载内部数据:DynamicLazyComponentPlaceholderComponent
@Component({
selector: 'app-placeholder',
templateUrl: './placeholder.component.html',
styleUrls: ['./placeholder.component.css'],
})
export class PlaceholderComponent implements OnInit {
@ViewChild(TemplateRef, { read: ViewContainerRef })
private templateViewContainerRef: ViewContainerRef;
constructor(
private readonly componentFactoryResolver: ComponentFactoryResolver
) {}
async ngOnInit() {
import('../../dynamic-lazy/dynamic-lazy.component').then(
({ DynamicLazyComponent }) => {
const component = this.componentFactoryResolver.resolveComponentFactory(
DynamicLazyComponent
);
const componentRef = this.templateViewContainerRef.createComponent(
component
);
}
);
}
}
关于这段代码块的一些说明:
- 我们使用
@ViewChild()装饰器来查询元素TemplateRef的实例<ng-template>。 - 装饰器的第二个可选参数
@ViewChild()({ read: ViewContainerRef })用于ViewContainerRef从视图查询中读取实例。 - 该
templateViewContainerRef参数用于告诉渲染引擎应该在哪里渲染延迟加载的组件。 - 我们使用
import(...)与模块相同的语法来实现组件的延迟加载。
从 Angular 9 开始,我们不再需要注册组件并将其DynamicLazyComponent作为入口组件添加到任何模块中。如果您想在 Angular 8 中动态加载组件,请参阅“在 Angular 8 中手动延迟加载组件”。
下图展示了该组件的延迟加载过程:
结论
Angular 9 提供了一个非常简洁优雅的解决方案,可以使用import(...)语法在运行时手动导入模块和组件。
现在你应该能够创建非常动态的用户界面,这些界面可以在运行时加载的配置文件中进行配置,并且基于这些信息,不同的模块和组件会使用新的路由进行延迟加载。
文章来源:https://dev.to/mokkaapps/manually-lazy-load-modules-and-components-in-angular-1189



