Книга: Angular 2 Cookbook
Назад: Configuring applications to use ahead-of-time compilation
На главную: Предисловие

Configuring an application to use lazy loading

Lazy loaded applications are those that defer the retrieval of relevant resources until they are actually necessary. Once applications begin to scale, this can yield meaningful gains in performance, and Angular 2 supports lazy loading right out of the box.

Note

The code, links, and a live example related to this recipe are available at .

Getting ready

Suppose you begin with the following simple application:

[app/root.component.ts]      import {Component} from '@angular/core';      @Component({     selector: 'root',     template: `       <h1>Root component</h1>       <router-outlet></router-outlet>     `   })   export class RootComponent {}   [app/link.component.ts]      import {Component} from '@angular/core';      @Component({     selector: 'app-link',     template: `       <a routerLink="/article">article</a>     `   })   export class LinkComponent {}   [app/article.component.ts]      import {Component} from '@angular/core';      @Component({     selector: 'article',     template: `       <h1>{{title}}</h1>     `   })   export class ArticleComponent {     title:string =        'Baboon's Stock Picks Crush Top-Performing Hedge Fund';   }   [app/app.module.ts]      import {NgModule} from '@angular/core';   import {BrowserModule} from '@angular/platform-browser';   import {RouterModule, Routes} from '@angular/router';   import {RootComponent} from './root.component';   import {ArticleComponent} from './article.component';   import {LinkComponent} from './link.component';      const appRoutes:Routes = [     {       path: 'article',       component: ArticleComponent     },     {       path: '**',       component: LinkComponent     }   ];      @NgModule({     imports: [       BrowserModule,       RouterModule.forRoot(appRoutes)     ],     declarations: [       ArticleComponent,       LinkComponent,       RootComponent     ],     bootstrap: [       RootComponent     ]   })   export class AppModule {}   

This application has only two routes: the default route, which displays a link to the article page, and the article route, which display ArticleComponent. Your objective is to defer the loading of the resources required by the article route until it is actually visited.

How to do it...

Lazy loading means the initial application module that is loaded cannot have any dependencies on the module that you wish to lazily load since none of that code will be present before the new URL is visited. First, move the ArticleComponent reference to its own module:

[app/article.module.ts]      import {NgModule} from '@angular/core';   import {ArticleComponent} from './article.component';      @NgModule({     declarations: [       ArticleComponent     ],     exports: [       ArticleComponent     ]   })   export class ArticleModule {}   

Removing all dependencies means moving the relevant route definitions to this module as well:

[app/article.module.ts]      import {NgModule} from '@angular/core';   import {ArticleComponent} from './article.component';   import {Routes, RouterModule} from '@angular/router'; const articleRoutes:Routes = [     {       path: '',       component: ArticleComponent     }   ];      @NgModule({     imports: [       RouterModule.forChild(articleRoutes)     ],     declarations: [       ArticleComponent     ],     exports: [       ArticleComponent     ]   })   export class ArticleModule {}   

Next, remove all these module references from AppModule. In addition, modify the route definition, so that instead of specifying a component to render, it simply references a path to the lazily loaded module, as well as the name of the module using a special # syntax:

[app/app.module.ts]      import {NgModule} from '@angular/core';   import {BrowserModule} from '@angular/platform-browser';   import {RouterModule, Routes} from '@angular/router';   import {RootComponent} from './root.component';   import {LinkComponent} from './link.component';      const appRoutes:Routes = [     {       path: 'article',       loadChildren: './app/article.module#ArticleModule'     },     {       path: '**',       component: LinkComponent     }   ];      @NgModule({     imports: [       BrowserModule,       RouterModule.forRoot(appRoutes)     ],     declarations: [       LinkComponent,       RootComponent     ],     bootstrap: [       RootComponent     ]   })   export class AppModule {}   

This is all that's required to set up lazy loading. Your application should behave identically to when you began this recipe.

How it works...

To verify that it is in fact performing a lazy load, start the application and keep an eye on the Network tab of your browser's developer console.

When you click on the article routerLink, you should see article.module.ts and article.component.ts requests go out before the rendering occurs. This means Angular is only fetching the required files once you actually visit the route. The loadChildren route property tells Angular that when it visits this route, if it hasn't loaded the module already, it should use the relative path you may have provided to fetch the module. Once the module file is retrieved, Angular is able to parse it and know which other files it needs to request to load all the module's dependencies.

There's more...

You'll note that this introduces a bit of additional latency to your application since Angular waits to load the resources right when it actually needs them. What's more, in this example, it has to actually perform two additional round trips to the server when it visits the article URL: one to request the module file and one to request the module's dependencies.

In a production environment, this latency might be unacceptable. A workaround might be to compile the lazily loaded payload into a single file that can be fetched with one request. Depending on how your application is built, your mileage may vary.

Accounting for shared modules

The lazily loaded module is totally separated from your main application module, and this includes injectables. If a service is provided to the top-level application module, you will find that it will create two separate instances of that service for each place it is provided—certainly unexpected behavior, given that an application loaded normally will only create one instance if it is only provided once.

The solution is to piggyback on the forRoot method that Angular uses to simultaneously provide and configure services. More relevant to this recipe, it allows you to technically provide a service at multiple locations; however, Angular will know how to ignore duplicates of this, provided it is done inside forRoot().

First, define the AuthService that you wish to create only a single instance of:

[app/auth.service.ts]      import {Injectable} from '@angular/core';      @Injectable()   export class AuthService {     constructor() {       console.log('instantiated AuthService');     }   }   

This includes a log statement so you can see that only one instantiation occurs.

Next, create an NgModule wrapper specially for this service:

[app/auth.module.ts]      import {NgModule, ModuleWithProviders} from "@angular/core";   import {AuthService} from "./auth.service";      @NgModule({})   export class AuthModule {     static forRoot():ModuleWithProviders {       return {         ngModule: AuthModule,         providers: [           AuthService         ]       };     }   }   

Since this utilizes the forRoot() strategy as detailed in the preceding code, you're free to import this module both inside the application module as well as the lazily loaded module:

[app/app.module.ts]      import {NgModule} from '@angular/core';   import {BrowserModule} from '@angular/platform-browser';   import {RouterModule, Routes} from '@angular/router';   import {RootComponent} from './root.component';   import {LinkComponent} from './link.component';   import {AuthModule} from './auth.module';      const appRoutes:Routes = [     {       path: 'article',       loadChildren: './app/article.module#ArticleModule'     },     {       path: '**',       component: LinkComponent     }   ];      @NgModule({     imports: [       BrowserModule,       RouterModule.forRoot(appRoutes),       AuthModule.forRoot()     ],     declarations: [       LinkComponent,       RootComponent     ],     bootstrap: [       RootComponent     ]   })   export class AppModule {}   

You'll add it to the lazily loaded module too, but don't invoke forRoot(). This method is reserved for only the root application module:

[app/article.module.ts]      import {NgModule} from '@angular/core';   import {ArticleComponent} from './article.component';   import {Routes, RouterModule} from '@angular/router';   import {AuthModule} from './auth.module';      const articleRoutes:Routes = [     {       path: '',       component: ArticleComponent     }   ];      @NgModule({     imports: [       RouterModule.forChild(articleRoutes),       AuthModule     ],     declarations: [       ArticleComponent     ],     exports: [       ArticleComponent     ]   })   export class ArticleModule {}   

Finally, inject the service into RootComponent and ArticleComponent and use log statements to see that it does indeed reach both the components:

[app/root.component.ts]      import {Component} from '@angular/core';   import {AuthService} from './auth.service';      @Component({     selector: 'root',     template: `       <h1>Root component</h1>       <router-outlet></router-outlet>     `   })   export class RootComponent {     constructor(private authService_:AuthService) {       console.log(authService_);     }   }   [app/article.component.ts]      import {Component} from '@angular/core';   import {AuthService} from './auth.service';      @Component({     selector: 'article',     template: `       <h1>{{title}}</h1>     `   })   export class ArticleComponent {     title:string =        'Baboon's Stock Picks Crush Top-Performing Hedge Fund';        constructor(private authService_:AuthService) {       console.log(authService_);     }   }   

You should see a single service instantiation and successful injection into both the components.

See also

  • Configuring the Angular 2 renderer service to use web workers guides you through the process of setting up your application to render on a web worker
  • Configuring applications to use ahead-of-time compilation guides you through how to compile an application during the build
Назад: Configuring applications to use ahead-of-time compilation
На главную: Предисловие

thank you
Flame
cant read the code since it is all on a single line. Also this comments section is russian
Rakuneque
DATA COLLECTION AND ANALYSIS Two reviewers extracted data and assessed methodological quality independently lasix torsemide conversion Many others were in that space already