Skip to content

Lazy Loading

Elliott Hamai edited this page Sep 29, 2017 · 1 revision

Modules

Before, we used to put everything into the default app.module.ts file which caused everything to be loaded on first page request. Now, we have several module files which define each file “chunk” that gets downloaded by the browser. The routes within each module file will define when a new module needs to be loaded by the browser. Whenever you add a component/service to a module, always try to load it as late as possible to prevent top-level modules from growing too large.

AppModule (Root module)

Since the AppModule is bootstrapped to the AppComponent it always loads right away and changes the URL to either “/resources”, “/landing”, or “/try” depending on what environment it’s running in. Eventually we’ll support deep links so that it won’t have to do this, but that’s a separate work item. Once Angular detects that there has been a route change, it looks for a element in the HTML and loads content into it based on the routing rules. So if the URL is “/resources”, it will lazy load the “MainModule” into the outlet.

App.component.html

<busy-state name='global'></busy-state>
<disabled-dashboard></disabled-dashboard>
<error-list></error-list>
<router-outlet></router-outlet>

app.module.ts

const routes = RouterModule.forRoot([
  { path: 'resources', loadChildren: 'app/main/main.module#MainModule' },
  { path: 'landing', loadChildren: 'app/getting-started/getting-started.module#GettingStartedModule' },
  { path: 'try', loadChildren: 'app/try-landing/try-landing.module#TryLandingModule' }
]);
@NgModule(AppModule.moduleDefinition)
export class AppModule {
  static moduleDefinition = {
    declarations: [
      AppComponent,
      
    ],
    imports: [
      SharedModule.forRoot(),
      routes
      
    ],
    bootstrap: [AppComponent]
  };
}

SharedModule

Angular is very strict about preventing you from defining the same component/service multiple times in different modules to keep file sizes small. If it detects that you’ve done this, then the site will break once it tries to load a module that’s already been defined. Unfortunately there isn’t a way to detect this at compile-time so you’ll have to be careful. Our E2E test cases should be able to catch these types of issues pretty easily, but it’s still a lot later than ideal.

The way to reuse a component is to define a “shared module” that exports its components, and then reference it from whatever module needs those components (see AppModule). Currently, Shared.module.ts mostly consists of reusable controls that we use almost everywhere. It also contains all of the service definitions that we use.

One other thing to notice is that SharedModule defines its providers/services as static. This is to ensure that you get the same instance of every service reference in each module. If we had defined it in NgModule instead, it would have created a new service instance per module, which might be useful in some cases.

shared.module.ts

@NgModule({
    declarations: [
        BusyStateComponent,
        MultiDropDownComponent,
        
    ],
    exports: [
        FormsModule,
        BusyStateComponent,
        MultiDropDownComponent,
        
    ],
    imports: [
        FormsModule,
        
    ]
})
export class SharedModule {
    static forRoot(): ModuleWithProviders {
        return {
            ngModule: SharedModule,
            providers: [
                ConfigService,
                FunctionsService,
                
            ]
        };
    }
}

MainModule

The MainModule covers the mainline scenario which is responsible for loading the tree view and any “dashboard” content on the right side of it. Based on the routing rules from AppModule, you’ll notice that this module gets loaded when the URL is “/resources”. Since the first rule for MainModule says to load the MainComponent for an empty string, it always gets loaded. And since MainComponent has its own any components/modules that get loaded within it must be defined by “child routes”.

main.component.html

<div id="content" >
    <side-nav *ngIf="ready" [tryFunctionAppInput]="tryFunctionApp"></side-nav>
    <top-warning></top-warning>
    <div id="dashboard" [hidden]="!ready">
        <router-outlet></router-outlet>
    </div>
</div>

main.module.ts

const routing: ModuleWithProviders = RouterModule.forChild([
    {
        path: '', component: MainComponent,
        children: [
            {
                path: 'blank',
                component: EmptyDashboardComponent,
            },
            {
                path: 'apps',
                loadChildren: 'app/apps-list/apps-list.module#AppsListModule'
            },
            {
                path: 'subscriptions/:subscriptionId/resourcegroups/:resourceGroup/sites/:site',
                loadChildren: 'app/site/site.module#SiteModule'
            },
            {
                path: 'subscriptions/:subscriptionId/resourcegroups/:resourceGroup/sites/:site/functions',
                loadChildren: 'app/functions.module#FunctionsModule'
            },
            {
                path: 'subscriptions/:subscriptionId/resourcegroups/:resourceGroup/sites/:site/proxies',
                loadChildren: 'app/proxies.module#ProxiesModule'
            },
            
        ]
    }
]);
@NgModule({
    imports: [
        TranslateModule.forChild(),
        SharedModule,
        routing
    ],
    declarations: [
        MainComponent,
        SideNavComponent,
        TreeViewComponent,
        EmptyDashboardComponent,
        TrialExpiredComponent,
        TopWarningComponent
    ],
    providers: []
})
export class MainModule { }

Analyzing Bundle Size

Angular comes with a cool tool to analyze bundle sizes. Just run the following from the command line to get a heatmap of how your code is distributed within each chunk:

ng build --prod --stats-json npm run bundle-report