Angular Router: Preloading Modules

Using Lazy loading of modules, can make our application load faster.
in order to use lazy loading, we split our application into multiple modules, and those modules will be downloaded from the server and initialized upon demand.
What does it mean “upon demand” – if for example we have a customers module, and a route that navigates to the customers module in our application – only then that module will be downloaded and initialized.

This post, isn’t about how to configure lazy loaded modules, but how we can decide to pre-load certain modules without actually navigating to them. why ? because navigating to a module (customers module in the previous example) , can take some time for the module to be downloaded and initialized. and sometimes we do not want that behavior.

To fix this problem, the angular team have introduced the “loading strategy” option.
we can mark certain modules, to be pre-loaded  while the user is working with the application: not at application initialization, and not while navigating to the module…. but in the background.

please read Victor Savkin’s post with all the details about this feature:

Preloading Modules

Load configuration from external file

Here is the scenario:
I am using angular-cli for my project.
I can use the environment.ts file in the enviroments folder in order to store configuration information in it. for example: my WebApi base address .

Although i am using a separate environment.ts file for production,  this file is part of the build process angular-cli / webpack do while i build the project using “ng build –prod”.

if i need to update the configuration on my test/qa/prod servers, i need to either open the bundeled js files with an editor and find the right line, or return to my source code, make the change and re-build.

Another option, requested in one of my projects  – to store the configuration in an external json file.

How did i accomplish this ?

  • create a config.ts class, that will be the model for the configuration data structure.
    export class Configuration{
       constructor(public webApiBaseUrl: string,
                   public signalrBaseUrl: string)
    
    }
  • create a sample json file that adheres to the config.ts file
  • save this file (e.g. config.json ) in the assets folder . this folder is configured in the angular-cli.json file . This folder is copied as is, during build, into the dist folder. (you could create a seperate config folder as well, and put the file there)
  • in my environment.ts files, add a property to point to this fileexport const environment = {
    production: false,
    configFile: 'assets/config/config.json'
    };
  • now, we need to create a service that will load this configuration.
    @Injectable()
    
    import {Configuration} from './config-model.ts'
    export class ConfigService {
       private config: Configuration;
       constructor(private http:Http) {}
      
      load(url:string) { 
        return new Promise((resolve) => {
          this.http.get(url).map(res=>res.json())
            .subscribe(config => {
              this.config = config;
              resolve();
            });
      }
    
      getConfiguration():Configuration {
    
        return this.config;
      }
    }

    The actual loading – comes next.

  • we want to initialize the service in our AppModule.
    one of the main things we want to make sure: the configuration is loaded ,
    BEFORE any of our components gets rendered, as they might need information from the configuration file.We will use APP_INITIALIZER , which will help us to execute code an the initialization of our angular 2 application.AppModule.tsadd the following

    import { APP_INITIALIZER } from '@angular/core';
    import { HttpModule } from '@angular/http';

    next import our configuration service

    import { ConfigService } from './config.service';

    create a load function. we use this function out of the module, for AOT reasons.

    import { environment } from '../environments/environment';
    
    export function ConfigLoader(configService: ConfigService) {
    //Note: this factory need to return a function (that return a promise)
    
      return () => configService.load(environment.configFile); 
    }
    

    now, the AppModule declaration and metadata

    @NgModule({
        declarations: [
            AppComponent
        ],
        imports: [
            HttpModule,
            BrowserModule
        ],
        providers: [
            ConfigService,
            {
                provide: APP_INITIALIZER,
                useFactory: ConfigLoader,
                deps: [ConfigService],
                multi:true;
            }],
        bootstrap: [ AppComponent ]
    })
    export class AppModule {
    }
  • now, in our component, for example, we can use the configuration information
    constructor(private configService: ConfigService) {
            //
        }
    
        webApiBaseUrl:string;
        anyMethod() {
            this.webApiBaseUrl = 
                      this.configService.getConfiguration().webApiBaseUrl
        }

    and rest assure, that the value is already loaded into our configService, during initialization, well before we reach this part in our code.

    To Sum up:

    If needed, you can move your configuration (or part of it) into a separate json file
    that will not be part of the bundling process – giving you the ability to change the configuration values in a snap, without a need of a re-build process.

Shared Modules and Service Providers

If you have created a shared module in your project, you might also have some services in it. And you might think, where is the best place to PROVIDE this service, so angular’s DI will create it’s instance.

If you decide to do the declaration in the SharedModule itself like this:

import { NgModule } from '@angular/core';
import { HelloService } from './hello.service';

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

and import this SharedModule in other Modules, you will find that in each Module that imported this SharedModule, a seperate instance of the HelloService was created.

In order to avoid this, we will use the following notation

import { NgModule, ModuleWithProviders } from '@angular/core';
import { HelloService } from './hello.service';

@NgModule({})
export class SharedModule {
  static forRoot(): ModuleWithProviders {
    return {
      ngModule: SharedModule,
      providers: [HelloService]
    };
  }
}

Next, we will import this module in our AppModule, creating a single instance of the provided services

...
import { SharedModule } from './shared/shared.module';

@NgModule({
  imports: [
    SharedModule.forRoot(),
    ...
  ],
  ...
})
export class AppModule {}

The above, will create a single instance of the provided services.

in other feature modules, in which the SharedModule is needed, we will just import it, without the use of the “forRoot” static method call.

Angular 4

angular2

Hi everyone. This is my new blog about Angular 2/4.
During my work with Angular 2 , I encounter many issues that result in long searches for answers on the web.
So i’ve decided to write about those issues, and hopefully that could help others.