Angular Lazy Loading 모듈 사용하기

타운컴퍼니 / 조회수 : 2040

Angular는 비동기식 라우팅이 가능합니다. 나중에 사용할 기능들을 NgModule로 분리하여 사용자의 요청이 들어왔을 때 모듈을 불러와 기능을 사용할 수 있고, 이러한 기술을 지연 로딩이라 합니다.

프로젝트가 진행되고 기능이 추가될수록 어플리케이션 번들 크기가 커지고, 결국엔 초기 로딩 시간도 길어지게 됩니다. 지연 로딩을 사용하면 초기 로딩 시간을 줄일 수 있습니다. 컴파일 단계에서 나중에 사용할 모듈들을 메인 모듈에서 분리하여 번들을 생성합니다. 그리고 사용자가 기능을 요청할 때 비동기로 스크립트를 불러와 실행합니다. 지연 로딩에 대한 소개와 사용법은 Angular 공식 문서의 Routing & Navigation — Milestom 6: Asynchronous routing 을 참고하시길 바랍니다

하지만 지연 로딩을 사용할 때 유의해야할 점이 몇 가지 있습니다.


지연 로딩 모듈과 인젝터(Injector)

지연 로딩이 완료되었을 때 Angular는 지연 로딩된 모듈을 루트 인젝터(Root Injector)의 자식이 되는 자식 인젝터를 이용하여 초기화하고, 서비스들을 자식 인젝터에 추가합니다. 즉, 인젝터가 분리되기에 지연 로딩된 모듈의 클래스들은 자식 인젝터로의 서비스 주입이 가능하지만 루트 인젝터로 만들어진 클래스들은 불가능합니다.

이는 Angular의 독특한 의존성 주입 시스템 때문입니다. Angular의 인젝터는 처음 애플리케이션이 시작되었을 때, 컴포넌트나 다른 서비스에 주입되기 전에 포함된 모든 모듈들의 서비스 제공자들을 블러와 루트 인젝터를 생성합니다. 애플리케이션이 시작되고 나면 인젝터는 서비스들을 생성하고 주입을 시작하고, 새로운 서비스들을 제공자로 추가가 불가능합니다.

그러므로 지연 로딩된 서비스들은 이미 생성이 완료된 루트 인젝터로 추가가 불가능합니다. 따라서 Angular는 지연 로딩된 모듈에 대해서 새로운 자식 인젝터를 만들는 전략을 취하게 된 것입니다.

자식 인젝터가 새로 만들어지기 때문에 공통된 모듈을 사용할 때 주의하여야 합니다. 예를 들어 다음과 같이 SharedModuleCounterService 를 서비스로 추가하고 루트 모듈인 AppModule 과 지연 로딩 모듈인 LazyModule 에 각각 SharedModule 을 import 하였습니다.


import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { SharedModule } from './shared/shared.module';
import { AppShellComponent } from './app-shell.component';


const APP_ROUTES = [
    {
        path: 'lazy',
        loadChildren: 'app/lazy/lazy.module#LazyModule'
    }
];

@NgModule({
    imports: [
        BrowserModule,
        SharedModule,
        RouterModule.forRoot(APP_ROUTES)
    ],
    declarations: [
        AppShellComponent
    ],
    bootstrap: [AppShellComponent]
})
export class AppModule {
}
import { Injectable } from '@angular/core';


@Injectable()
export class CounterService {
    count = 0;

    increase(): void {
        this.count++;
    }

    decrease(): void {
        this.count--;
    }
}
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { SharedModule } from '../shared/shared.module';
import { SomeLazyComponent } from './some-lazy.component';


const LAZY_ROUTES = [
    {
        path: '',
        component: SomeLazyComponent
    }
];


@NgModule({
    imports: [
        SharedModule,
        RouterModule.forChild(LAZY_ROUTES)
    ]
})
export class LazyModule {
}
import { NgModule } from '@angular/core';


@NgModule({
    providers: [
        CounterService
    ]
})
export class SharedModule {
}

그리고 루트 모듈의 컴포넌트와 지연 로딩 모듈의 컴포넌트에서 각각 CounterService 를 사용하여 숫자 값을 바꿔봅니다.

서로 다른 인젝터에 CounterService 인스턴스가 만들어졌기 때문에 두 컴포넌트에 표시되는 숫자값은 다릅니다. 앞에서 말했듯이 지연 로딩 모듈은 루트 인젝터가 아닌 자식 인젝터를 이용하여 초기화하기 때문입니다.

만약, 지연 로딩 모듈에서 제공되는 서비스를 다른 모듈에서 사용하려면 루트 모듈에 포함시켜 줘야 합니다. 다음과 같이 루트 모듈에게만 노출시킬 서비스 제공자들을 따로 빼내어 줄 수 있습니다.

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AccountLoginPageComponent } from './login-page.component';


const ACCOUNT_ROUTES: Routes = [
    { path: 'login', component: AccountLoginPageComponent }
];


@NgModule({
    imports: [
        ...
        RouterModule.forChild(ACCOUNT_ROUTES)
    ],
    decalartions: [
        AccountLoginPageComponent
    ]
})
export class AccountLazyModule {
}
import { ModuleWithProviders, NgModule } from '@angular/core';
import { AccountAuthService } from './auth.service';


@NgModule({
    imports: [...]
})
export class AccountModule {
    static forRoot(): ModuleWithProviders {
        return {
            ngModule: AccountModule,
            providers: [
                AccountAuthService
            ]
        };
    }
}

AccountModule.forRoot() 를 루트 모듈에 import하면 다른 모듈에서도 AccountAuthService 를 사용할 수 있게 됩니다. 물론 이 경우 AccountModule를 지연로딩 모듈로 만들면 루트 모듈에 포함되기 때문에 번들을 나누는 의미가 없어질 수 있으니 AccountLazyModule 을 따로 두어 코드를 분리하였습니다.


#타운컴퍼니 #개발 #개발자 #인사이트 #꿀팁


관련 스택

기업문화 엿볼 때, 더팀스

로그인

/