Javascript

Optimizing Page Speeds With Lazyloading

January 2nd, 2017 | By Thomas Greco | 6 min read

We will explore the topic of Lazyloading Angular 2 modules and learn how we can set up our application to be as highly performant as possible.

Lazyloading Modules

In part two of this series, we focused on Angular's ability to route specific components to specific URLs.

The final result is the plnkr shown below. In this example, each of our routes (or routable components) is loaded when the application is instantiated.

This sample application only contains small bits of dummy code. Therefore we are not subjected to any performance penalty due to our entire application being loaded at once. However, as applications grow larger and more complex, users will want to take advantage of the framework's ability to easily perform lazy-load. In simplest terms, this tells our application to refrain from loading certain sections of our application until they are needed.

When working with Angular 2, we can program the application to lazily load specific modules. In the first article, I spoke about the use of modules and briefly mentioned the structure of large Angular 2 applications.

Additionally, I introduced the idea of feature modules, which are Angular modules that hold the code for components, directives, services, etc. inside of a specific module.

An example could be a @NgModule decorator that holds the code for the blog section of a website, BlogrollComponent, and SinglePostComponent.

In addition to feature modules, Angular also recommends the use of shared modules, which contain code that will be shared throughout an application, such as a site's navigation bar or a footer.

Our sample application will expand upon the components introduced in our article on routing. By the end, we will have HomeComponent, AppComponent, and AboutComponent split into individual modules.

Code

Each feature module is going to be placed inside of its folder. The structure will replicate the following:

app
│ _ app.routes.ts
│ _ app.module.ts
│ _ app.component.ts
│ _ // other 
└───home
   │  home.component.ts
   │   home.component.ts
   │   home.routes.ts
└───about
    │   about.routes.ts
    │   about.module.ts
    │   about.component.ts
└───contact
    │   contact.routes.ts
    │   contact.module.ts
    │   contact.component.ts


Explore the final code for this tutorial.

AboutRoutes, ContactRoutes, HomeRoutes

A quick inspection of this code will show us that each of the .module and .routes files for our child modules are generally the same. (Those interested in learning about these files should check articles 1 and 2).

We can tie ContactComponent, AboutComponent, and HomeComponent to their respective routes inside of the <component>.routes.ts file.

Soon, we will see how we can use loadChildren to load our modules in our main app.routes.ts file.

When defining our child routes, we need to pass in an empty string as the path because we will be defining the URL in our main appRoutes configuration. In addition to the URLs, another key part of this file is the RouterModule.forChild method at the bottom of our file.

The route.ts file for each feature module is a child configuration that will ultimately be nested inside of our main appRoutes config. Whereas appRoutes uses the .forRoot method, each of our nested modules will use forChild to initialize their routes.

// home/home.routes
import {
    Route,
    RouterModule
} from '@angular/router';
import {
    HomeComponent
} from './home.component';
export const HomeRoutes: Route[] = [{
    path: '',
    component: HomeComponent
}];
export default RouterModule.forChild(HomeRoutes);
//contact/contact.routes
import {
    Route,
    RouterModule
} from '@angular/router';
import {
    ContactComponent
} from './contact.component';
export const ContactRoutes: Route[] = [{
    path: '',
    component: ContactComponent
}];
export default RouterModule.forChild(ContactRoutes);
//about/about.routes
import {
    Route,
    RouterModule
} from '@angular/router';
import {
    AboutComponent
} from './about.component';
export const AboutRoutes: Route[] = [{
    path: '',
    component: AboutComponent
}];
export default RouterModule.forChild(AboutRoutes);


AboutModule, ContactModule, HomeModule

The only differences between HomeModule, AboutModule, and ContactModule are in the form of file names and imports.

Each of them has their respective routes being imported from the directory's routes.ts file in addition to the CommonModule, which gives our templates access to the framework's common directives like ngIf, ngFor.

Following our imports, we pass in the name of any component tied to a module into the declarations property. By doing this, we are making Angular aware of the component.

When routing to components directly, we passed in the name of our components to the declarations property inside of our main AppModule.

By declaring our component directly inside of our module, Angular will be aware of it through the feature module, so we will no longer have these declarations inside of ourAppModule.

//home/home.module
import {
    NgModule
} from '@angular/core';
import {
    HomeComponent
} from './home.component';
import {
    CommonModule
} from '@angular/common';
import homeRoutes from './home.routes';
@NgModule({
    imports: [
        CommonModule,
        homeRoutes
    ],
    declarations: [HomeComponent]
})
export class HomeModule {}
//contact/contact.module
import {
    NgModule
} from '@angular/core';
import {
    ContactComponent
} from './contact.component';
import {
    CommonModule
} from '@angular/common';
import routes from './contact.routes';
@NgModule({
    imports: [
        CommonModule,
        routes
    ],
    declarations: [ContactComponent]
})
export class ContactModule {}
//about/about.module
import {
    NgModule
} from '@angular/core';
import {
    AboutComponent
} from './about.component';
import {
    CommonModule
} from '@angular/common';
import aboutRoutes from './about.routes';

@NgModule({
    imports: [
        CommonModule,
        aboutRoutes
    ],
    declarations: [AboutComponent]
})
export class AboutModule {}


Loading our modules

When working with routable components, each route definition is tied directly to a component. We see this inside of our <componentName.routes.ts> files.

// src/contact/contact.routes.ts
export const ContactRoutes: Route[] = [
  { path: '', component: ContactComponent }
];
// src/about/about.routes.ts
export cons AboutRoutes: Route[] = [
  { path: '', component: AboutComponent }
];
// src/home/home.routes.ts
export const HomeRoutes: Route[] = [
  { path: '', component: HomeComponent }
];


In addition to our child routes, we need to configure Angular’s RouterModule to load our module inside of app.routes.ts.

By taking a look at ContactRoutes, HomeRoutes, and AboutRoutes configurations above, we see that the path value of each component is an empty string. This allows us to assign a URL path for each of the code tied to this module directly inside of our main app.routes.ts file.

Below, we can see our root appRoutes config which contains the route definitions for our app.

//src/app.routes.ts
import {
    Routes,
    RouterModule
} from '@angular/router';
export const routes: Routes = [{
        path: '',
        loadChildren: './src/home/home.module#HomeModule'
    },
    {
        path: 'contact',
        loadChildren: './src//contact/contact.module#ContactModule'
    },
    {
        path: 'about',
        loadChildren: './src//about/about.module#AboutModule'
    }
];


As we learned before, our modules are tied to empty strings, so we assign our desired paths of each module here. Our example has the following URLs: / /contact and /about.

Further examination of our Routes config shows a special string being passed into the loadChildren property.

When mapping a path to a module we use the # symbol to tell Angular where our module lies. It's important to note that Angular 2 will not render our code directly if this string format is not used correctly.

Below, we can see the app.routes.ts file for this example compared to the app.routes.ts file in the tutorial on routing to components.

Lazy Loaded Routes vs. Component Routes

Below, we can see the differences between the routes used in our first tutorial and this application's routes.

In this code, we see that our lazy-loaded routes use the loadChildren property spoken about above; meanwhile, the config found in our initial application ties our views to specific components via the component property.

// app.routes.ts (Lazy-loading)
import {
    Routes,
    RouterModule
} from '@angular/router';
export const routes: Routes = [{
        path: '',
        loadChildren: './src/home/home.module#HomeModule'
    },
    {
        path: 'contact',
        loadChildren: './src/contact/contact.module#ContactModule'
    },
    {
        path: 'about',
        loadChildren: './src/about/about.module#AboutModule'
    }
];
export default RouterModule.forRoot(routes);
//app.routes.ts (Component Router)
/* Import Routes Config */
import {
    RouterModule
} from '@angular/router';
/* Import Individual Component */
import {
    HomeComponent
} from './home.component';
import {
    AboutComponent
} from './about.component';
import {
    ContactComponent
} from './contact.component';
const routes = [{
        path: '',
        component: HomeComponent
    },
    {
        path: 'about',
        component: AboutComponent
    },
    {
        path: 'contact',
        component: ContactComponent
    }
];
export default RouterModule.forRoot(routes);


Conclusion

And this concludes our article on lazy-loading in Angular 2. So far, we've learned a good bit about the Angular 2 framework.

The ability to perform lazy-loading is a highly sought one, knowledge of how to configure lazily-loaded routes is very useful. Victor Savkin states that some program applications only load certain modules when needed instead of loading all of our code at once.

We recommend checking out his article to anyone interested in learning more about the topic!

Jscrambler

The leader in client-side Web security. With Jscrambler, JavaScript applications become self-defensive and capable of detecting and blocking client-side attacks like Magecart.

View All Articles

Must read next

Web Development

Implementing Angular Lazy Loading

In this post, we'll explore the benefits of Angular lazy loading and how you can implement it in your source code.

October 28, 2021 | By Jay Raj | 5 min read

Javascript

Routing your Angular 2 Application

By the end of this tutorial, we'll have taken a look at the core-concepts behind routing in Angular 2.0.

November 18, 2016 | By Thomas Greco | 6 min read

Section Divider