之前发表过一篇《Angular 项目过大?合理拆分它!》,通过路由形式的按需加载、使用DLL
等方式来优化Angular
项目。
本篇介绍如何不通过路由的形式来实现按需加载模块和组件。
前言
相信大家在开发中遇到过这种情况:A模块
会被其他页面或模块使用,比较烦人的是这个A模块
还比较大,如果按常规方式引入该模块就会导致一个问题:所有引入A模块
的模块打包的时都会包含A模块
的代码,导致模块体积变大,浏览器加载变慢、用户体验相对也就变差。
解决办法的核心很简单:我用的时候你再给我,不用的时候你别加载进来,这就是按需加载。
话不多说,先看效果:
上图中,在点击按钮的时候会按需加载模块,并使用该模块里的组件在页面中渲染,在控制台可以看到只有点击按钮的时候,所需要的模块和组件的代码才会被加载。
核心代码
话不多说直接上代码,下面代码的目的:当我点击按钮的时候 执行动态加载模块、渲染组件,并能正常的给组件传递数据、订阅组件的Output
@Component({
selector: 'work-place',
template:`
<button (click)="lazyLoadModuleAndComponent()">
lazy module and component
</button>
<ng-container #viewContainer></ng-container>
`,
})
export class WorkPlaceComponent implements OnInit {
@ViewChild('viewContainer', { read: ViewContainerRef }) containerRef!: ViewContainerRef;
constructor(private complier: Compiler, private injector: Injector) {}
ngOnInit() {}
async lazyLoadModuleAndComponent() {
const { InfoModule } = await import('./common-modules/info/info.module');
// 异步编译模块,返回模块工厂
const moduleFactory: NgModuleFactory<any> = await this.complier.compileModuleAsync(InfoModule);
// 通过工厂创建模块
const moduleRef: NgModuleRef<any> = moduleFactory.create(this.injector);
// 通过模块实例获取组件
const component = moduleRef.instance.getInfoComponent();
// 解析组件
const componentFactory: any = moduleRef.componentFactoryResolver.resolveComponentFactory(component);
// 清除container
this.containerRef.clear();
// 创建组件
const componentRef: ComponentRef<InfoComponent> = this.containerRef.createComponent(componentFactory);
// 给组件传递参数
componentRef.instance.data = { name: 'Jack', age: 32 };
// 子组件emit到父组件
componentRef.instance.callbackEmit.subscribe((res) => {
console.log(res);
});
}
}
上面代码的思路很简单,代码里有注释我就不多说了。
InfoModule.module.ts
:
import { NgModule } from '@angular/core';
import { NzDatePickerModule } from 'ng-zorro-antd/date-picker';
import { InfoComponent } from './info.component';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
@NgModule({
imports: [CommonModule, FormsModule, ReactiveFormsModule, NzDatePickerModule],
exports: [InfoComponent],
declarations: [InfoComponent],
providers: [],
})
export class InfoModule {
getInfoComponent() {
return InfoComponent;
}
}
这里要说的是在 InfoModule
类中定义了 getInfoComponent
方法,用于返回 InfoComponent
,如果要返回其他组件,可自行编写。
分析
按需加载的核心是通过import()
方法,然后通过 Complier 来动态编译模块,需要注意的是,compileModuleAsync 方法是在 JavaScript 运行时环境中使用预编译器(Precompiler)来编译模块。预编译器会在代码执行之前对模块进行静态分析和优化,以提高执行性能。这个过程不同于 JIT 编译,因为 JIT 编译是在代码运行时动态生成本地机器代码。
还有一点要注意,@ViewChild('viewContainer', { read: ViewContainerRef })
要加上{ read: ViewContainerRef }
,否则是拿不到组件实例的引用,就无法给组件传递数据。
结束
本文涉及的代码在:github.com/Vibing/angu… 中可以找到,你可以 clone 下来安装好依赖后进行 demo 查看。
具体路由如图:
后面会继续针对 Angular 项目的优化和实战写一些文章或心得,欢迎评论。
原文链接:https://juejin.cn/post/7244497027758227516 作者:Ve