Angular router의 loadChildren 을 사용하지 않고 Lazy loading하는 방법을 알아본다.
Angular Module 에 대한 Lazy Loading
Angular의 Lazy Loading 방식
- NgModule 단위로 lazy loading한다.
- Angular v8부터 import(<module path>).then(mod => mod.<moduleName>) 방식으로 로딩한다. import 구문을 사용하면 자동으로 code splitting이 된다.
- Angular v7버전 이하는 ViewEngine 이라는 Template엔진을 사용했고, 8부터 선택적으로 9부터는 디폴트로 Ivy를 Template엔진으로 사용한다.
- Module을 컴파일/로딩하고 Component를 인스턴스화하기위해 ComponentFactoryResolver를 사용하고, ViewContainer에 인스턴스화된 컴포넌트를 렌더링한다.
우선 manual lazy loading을 위한 모듈과 컴포넌트를 생성한다.
// 라이브러리 생성, nx 또는 ng 둘 다 사용가능하다
$ nx g lib manuallazy
// 컴포넌트 생성
$ nx g c manual --project=manuallazy
CREATE libs/manuallazy/src/lib/manual/manual.component.scss (0 bytes)
CREATE libs/manuallazy/src/lib/manual/manual.component.html (21 bytes)
CREATE libs/manuallazy/src/lib/manual/manual.component.spec.ts (628 bytes)
CREATE libs/manuallazy/src/lib/manual/manual.component.ts (283 bytes)
UPDATE libs/manuallazy/src/lib/manuallazy.module.ts (266 bytes)
lazyload하는 service를 "app-container" 애플리케이션에 생성한다.
$ nx g s lazy-loader --project=app-container
CREATE apps/app-container/src/app/lazy-loader.service.spec.ts (378 bytes)
CREATE apps/app-container/src/app/lazy-loader.service.ts (139 bytes)
서비스에 loadModule 메소드를 추가한다. 8전부터 Ivy 사용만 대응한다. 7버전 이하는 NgModuleFactoryLoader를 사용한다. (참조)
import { Compiler, Injectable, Injector, NgModuleFactory, Type } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class LazyLoaderService {
constructor(private compiler: Compiler, private injector: Injector) {}
loadModule(path: any) {
(path() as Promise<Type<any>>)
.then((elementModule) => {
try {
return this.compiler.compileModuleAsync(elementModule);
} catch (err) {
throw err;
}
})
.then((moduleFactory) => {
try {
const elementModuleRef = moduleFactory.create(this.injector);
const moduleInstance = elementModuleRef.instance;
// instantiate component dynamically.
} catch (err) {
throw err;
}
});
}
}
컴포넌트 인스턴스화 및 ViewContainer에 렌더링하기
다음 순서로 컴포넌트를 인스턴스화 한다.
- ManuallazyComponent를 manuallazy 라이브러리에 생성한다.
- MauallazyModule에 초기 인스턴스화할 Component를 리턴한다.
- LazyLoaderService에서 ComponentFactoryResolver를 통해 인스턴스화하고 ViewContainer에 할당한다.
- "app-container" 애플리케이션의 tsconfig.app.json에 "include"에 MauallazyModule의 index.ts를 추가 설정한다.
- "app-container" 애플리케이션에서 LazyLoaderService를 loadModule을 호출한다.
ManulComponent를 생성한다.
$ ng g c manual --project=manuallazy
// libs/manuallazy/lib/src/manual/manual.component.html
<p>manual lazy component</p>
ManuallazyModule에 ComponentType을 리턴한다.
// libs/manuallazy/src/lib/manuallazy.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ManualComponent } from './manual/manual.component';
@NgModule({
imports: [CommonModule],
declarations: [ManualComponent],
})
export class ManuallazyModule {
bootstrapComponent(): any {
return ManualComponent;
}
}
LazyLoaderService에 컴포넌트를 로딩하는 "addComponent" 메소드을 추가한다.
// apps/app-container/src/app/lazy-loader.service.ts
import {
Compiler,
Injectable,
Injector,
NgModuleFactory,
Type,
ViewContainerRef,
ComponentRef,
ComponentFactory,
ComponentFactoryResolver,
} from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class LazyLoaderService {
constructor(private compiler: Compiler, private injector: Injector, private resolver: ComponentFactoryResolver) {}
loadModule(path: any, viewContainer: ViewContainerRef): void {
(path() as Promise<Type<any>>)
.then((elementModule) => {
try {
return this.compiler.compileModuleAsync(elementModule);
} catch (err) {
throw err;
}
})
.then((moduleFactory) => {
try {
const elementModuleRef = moduleFactory.create(this.injector);
const moduleInstance = elementModuleRef.instance;
// instantiate component dynamically.
this.addComponent(moduleInstance.bootstrapComponent(), viewContainer);
} catch (err) {
throw err;
}
});
}
addComponent(cmpType: any, viewContainer: ViewContainerRef): ComponentRef<any> {
const factory: ComponentFactory<any> = this.resolver.resolveComponentFactory(cmpType);
let cmp: ComponentRef<any>;
if (viewContainer) {
viewContainer.clear();
cmp = viewContainer.createComponent(factory);
}
return cmp;
}
}
"app-container" 애플리케이션의 tsconfig.app.json 에 MauallazyModule의 index.ts 를 추가 설정한다.
// apps/app-container/src/tsconfig.app.json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"types": []
},
"include": ["**/*.ts", "../../libs/lazyview/src/index.ts", "../../libs/manuallazy/src/index.ts"],
"exclude": ["src/test-setup.ts", "**/*.spec.ts", "src/environments/*.ts"]
}
"app-container" 애플리케이션에서 ManullazyModule을 호출한다.
// apps/app-container/src/app/app.component.html 일부분
<li nz-menu-item nzMatchRouter>
<a (click)="manualLazyLoading($event)">Monitor</a>
</li>
... 중략 ...
<div class="inner-content">
<router-outlet></router-outlet>
<div style="padding-top: 100px;" #InnerContent></div>
</div>
// apps/app-container/src/app/app.component.ts
import { LazyLoaderService } from './lazy-loader.service';
import { Component, ViewChild, ViewContainerRef } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
isCollapsed = false;
@ViewChild('InnerContent', { read: ViewContainerRef }) innerContent: ViewContainerRef;
constructor(private readonly lazyLoaderServie: LazyLoaderService) {}
manualLazyLoading(event: MouseEvent): void {
// import 할 때 위치투명하게 nx library를 호출한다.
const manualLoad = () => import('@micro-demo/manuallazy').then((m) => m.ManuallazyModule);
this.lazyLoaderServie.loadModule(manualLoad, this.innerContent);
}
}
구현 소스: https://github.com/ysyun/blog-5730-micro-demo
라우팅으로 통한 Angular 모듈 Lazy loading과 메뉴얼 Lazy loading 방식의 단점
- 둘다 tsconfig.app.json과 같은 사전 설정작업이 필요하다.
- "app-container"라는 Lazy loading된 모듈을 사용하는 애플리케이션과 함께 컴파일이 되어야 한다.
장점은 import구문을 통해 자동 Code Splitting 기능이다. 위이 단점을 해결하기 위해서 Web Components를 지원하는 @angular/elements를 사용한다. 이에 대한 글은 다음을 참조한다.
https://mobicon.tistory.com/573
참조
https://juristr.com/blog/2019/10/lazyload-module-ivy-viewengine/
https://juristr.com/blog/2017/07/ng2-dynamic-tab-component/
'Angular > Concept' 카테고리의 다른 글
[NX] Lazy loading with Router 또는 without Router - 1 (0) | 2021.08.05 |
---|---|
[Angular Schematics] NX기반 Custom Schematic 개발하기 - 1 (0) | 2020.05.22 |
[Angular] Docker에서 수행하기 (0) | 2017.04.23 |
[Typescript] NodeJS에서 Typescript 사용하기 (0) | 2017.03.29 |
[PWA] Progressive Web Apps 만들기 (0) | 2016.12.20 |