Sunday, March 5, 2017

[Angular] Lazy loading and Preloading

 Angular    Lazy loading     Pre-loading  


Introduction


We will learn how to apply lazy loading and preloading on Angular modules, so that our application will have less startup time and better UX.

Lazy loading :
The split module is loaded on demand.

Preloading :
The split module is loaded on background (asynchronously).



Environment

Angular 2.4.0
Angular CLI 1.0.0-beta.31




Split modules


First of all, we have to think about the architecture of modules and components. This is essential and important cus the lazy-loading or preloading modules must have no relation between each other!

Let’s create a simple example here. We have a website which could be split into:

1.  Main content for normal users
2.  CMS (Content Management) for advanced user
3.  Share components/class/service for other modules





We will make MainModule and CmsModule lazy-loading.



Original architecture



  
app.module.ts

import { MainModule } from './modules/main/main.module';
import { CmsModule } from './modules/cms/cms.module';
import { ShareModule } from './modules/share/share.module';

@NgModule({
    declarations: [
      AppComponent
    ],
    imports: [
      BrowserModule,
      FormsModule,
      HttpModule,
      ShareModule,
      MainModule,
      CmsModule,
      AppRouteModule
    ],
    providers: [],
    bootstrap: [AppComponent]
})
export class AppModule { }




main-routing.module.ts

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { NewsComponent } from './components/news/news.component';
import { AboutComponent } from './components/about/about.component';
import { IndexComponent } from './components/index/index.component';
import { MainComponent } from './main.component';


const routes: Routes = [{
    path: 'Main', component: MainComponent, children: [
        {
            path: 'ZH', children: [
              { path: 'Index', component: IndexComponent },
              { path: 'About', component: AboutComponent },
              { path: 'News', component: NewsComponent }
            ]
        }];
}];

@NgModule({
    imports: [RouterModule.forChild(routes)],
    exports: [RouterModule],
    providers: []
})
export class MainRoutingModule { }



main.module.ts

Main module will do nothing with lazy loading, just don’t forget to inject the routing module.

import { MainRoutingModule } from './main-routing.module';

@NgModule({
    imports: [
      FormsModule,
      HttpModule,
      CommonModule,
      MainRoutingModule
    ],
    declarations: [
      MainComponent,
      MainHeaderComponent,
      MainFooterComponent,
      IndexComponent
    ]
})
export class MainModule { }



I will skip the CMS module codes here.
Now use $> ng serve --prod  to start the application, and take a look at the http requests on the browser.

You will find that all the modules(*.js) was loaded when enter the website and when you switch to another module, there won’t be any js file loaded from server.




Lazy Loading


Remove every dependency on Main/CMS module from AppModule

app.module.ts

import { MainModule } from './modules/main/main.module';
import { CmsModule } from './modules/cms/cms.module';
import { ShareModule } from './modules/share/share.module';

@NgModule({
    declarations: [
      AppComponent
    ],
    imports: [
      BrowserModule,
      FormsModule,
      HttpModule,
      ShareModule,
      MainModule,
      CmsModule,
      AppRouteModule
    ],
    providers: [],
    bootstrap: [AppComponent]
})
export class AppModule { }



Enable Lazy loading on AppRoutingModule

app-routing.module.ts

import { NgModule, Component } from '@angular/core';
import { Routes, RouterModule, PreloadAllModules } from '@angular/router';
import { MainComponent } from './../modules/main/main.component';

const routes: Routes = [
  {
      path: 'Main', component: MainComponent,
      loadChildren: '../modules/main/main.module#MainModule'
  },
   {
       path: 'Cms',                                     
       loadChildren: '../modules/cms/cms.module#CmsModule'
   }
];

@NgModule({
    imports: [RouterModule.forRoot(routes, {
        enableTracing: false
    })],
    exports: [RouterModule],
    providers: []
})
export class AppRouteModule { }



Also we have to remove the extra routing level on MainRouteModule and CmsRouteModule.

main-routing.module.ts

const routes: Routes = [{
    path: 'Main', component: MainComponent, children: [
        {
            path: 'ZH', children: [
              { path: 'Index', component: IndexComponent },
              { path: 'About', component: AboutComponent },
              { path: 'News', component: NewsComponent }
            ]
        }]
}];

//...



Demo

Okay, the lazy loading is done, let’s restart the application by
$> ng serve --prod --aot
and take a look on the http requests again. This time while you switch from the MainModule to CmsModule, a js file will be loaded.


No Lazy loading




Requests for JS




Lazy loading




Requests for JS after applying lazy loading.




The following demo shows the timing of loading 0.XXX.chunk.js and 1.XXX.chunk.js.






Preloading


We are going to support preloading for all modules.

app-routing.module.ts

//...
import { Routes, RouterModule, PreloadAllModules } from '@angular/router';

const routes: Routes = [
  {
      path: 'Main', component: MainComponent,
      loadChildren: '../modules/main/main.module#MainModule'
  },
   {
       path: 'Cms',
       loadChildren: '../modules/cms/cms.module#CmsModule'
   },
  fallbackRoute
];

@NgModule({
    imports: [RouterModule.forRoot(routes, {
        enableTracing: false,
        preloadingStrategy: PreloadAllModules
    })],
    exports: [RouterModule],
    providers: []
})
export class AppRouteModule { }




Demo

Let’s go to one of the MainModule’s routes, we will see that the MainModule’s JS package loaded before rendering webpage, however the CmsModule’s JS package will be loaded asynchronously while the webpage is rendering.







Here is a demo for monitoring the loading order of the JS packages.





Summary


The question is when we need a Lazy loading or a Preloading strategy?
If the user only view some sections(modules) of the website, Lazy loading will be excellent.
On the other hand, if the user has great possibility to browse all sections of the website, then Preloading will be sweet and helpful.



Reference




Monday, February 13, 2017

[Angular] Upgrade Angular CLI issues

 Angular2    Angular/CLI    Upgrade    issues  


Upgrade Angular/Cli


Notice that angular team move the npm package from angular-cli to @angular/cli.

You can use the following commands to upgrade it.

Uninstall

$> npm uninstall -g angular-cli
$> npm cache clean

Install

$> npm install -g @angular/cli


Solve the issues


Here are some issues with my existed project when upgrading Angular CLI to 1.0.0-beta.31.

1. ng serve not working
2. ng build not working





The reason why ng serve and ng build don’t work because the global Angular CLI is updated to the new version and the earlier angular modules/packages are not compatible with it.

So we have to use the local Angular CLI (the old one fit our project) to run the command.

Update package.json

Add the following script into the “scripts” section in package.json

"ng": "ng"


Just like this…

"scripts": {
    "start": "ng serve",
    "build": "ng build --prod",
    "ng": "ng",
    "lint": "tslint \"src/**/*.ts\"",
    "test": "ng test",
    "pree2e": "webdriver-manager update",
    "e2e": "protractor"
  }

Thus we could use the following command with the local Angular CLI.

$> npm run ng


Examples,

npm run ng serve
npm run ng build --prod


Furthermore, we could ease our command by adding something into package.json like this

"scripts": {
    "start": "ng serve",
    "build": "ng build --prod",
    "ng": "ng",
    "lint": "tslint \"src/**/*.ts\"",
    "test": "ng test",
    "pree2e": "webdriver-manager update",
    "e2e": "protractor"
  }

And just type the following command to build the angular app!

npm run build




Reference



Thursday, February 9, 2017

[Angular] Use Sanitizer to ensure safe html string

 Angular    Sanitizer     Directive  


Introduction


In AngularJS, you may use $sanitize to ensure the safe html.
In Angular2, we will use Sanitizer class to do the same thing, the following is a simple sample and we will create a directive to enhance [innerHtml].


Environment
angular 2: 2.1.0


Implement


How to use Sanitizer

app.component.ts

import { Sanitizer, SecurityContext } from '@angular/core';

constructor(private sanitizer: Sanitizer) {
    let safeHtml = this.sanitizer.sanitize(SecurityContext.HTML, "<script>alert('I am bad!')</script>");
}


HTML

<input type="text" [(ngModel)]="safeHtml" />


Result:







Directive


import { Directive, ElementRef, Input, ViewContainerRef, Compiler, OnChanges, Sanitizer, SecurityContext } from '@angular/core';

@Directive({
    selector: '[safe-html]'
})
export class SafeHtml implements OnChanges {
    @Input() html: string;

    constructor(private elementRef: ElementRef, private sanitizer: Sanitizer) {


    }

    ngOnChanges() {

        if (this.html) {
            this.elementRef.nativeElement.innerHTML =
                this.sanitizer.sanitize(SecurityContext.HTML, this.html);
        }
    }
}


Usage

<div safe-html [html]="htmlStr"></div>



Reference