This is Part 5 of Learn Angular Tutorial. In this part, we will see how to implement Lazy loading and how to use JQuery with Angular.
Contents
- In the first part, we look into Node, TypeScript, Module loaders/Bundlers and VS Code.
- In the second article, we create a simple basic Angular application with a screen and we run through important concepts like components and modules.
- In the third article, we look into implementing SPA and validations.
- In the fourth article, we understand how to make HTTP calls and how to create custom Angular components using Input and Outputs.
- In the fifth part, we cover two labs - one is how to use Jquery with Angular and Lazy routing.
- In the sixth part, we again cover two labs - Pipes and DI using providers.
In this article, we will see how to implement lazy routing and how to use JQuery with Angular.
Big projects will have lot of components and modules, in other words, we will end up with lot of JS files on the browser client side. Loading these JS files in ONE GO at the client browser side would really hit performance.
If you load the current application at this stage and see the developer tools you will see on the network tab all JS files are getting loaded at the start.
When the user comes to the site for the first time, we would like to just load the welcome component and master component JS only.
When the user clicks on supplier and customer, respective JS files should be loaded at that moment.
|
|
Let’s investigate who is the culprit?
If you see the current architecture of our project, we have one module (MainModule.ts) and all components currently belong to this only ONE Module. So when this module loads it loads all components inside it.
In simple words, we need to BREAK MODULES into separate physical module files.
|
|
So as discussed in the previous part of the theory, we need to first divide our project into three different physical module files: MainModule
, SupplierModule
and CustomerModule
.
So in the module folder, let's create three different physical module files. We already have MainModule.ts, we need to create two more.
MainModule.ts: This module will load “MasterPageComponent.ts” and “WelcomeComponent.ts”.
SupplierModule.ts: This module will load “SupplierComponent.ts”.
CustomerModule.ts: This will load CustomerComponent and GridComponent . Remember grid is used only in Customer UI so this should load only when Customer functionality is loaded.
|
|
The first thing is we need to remove all references of CustomerComponent
, SupplierComponent
and GridComponent
from the MainModule
. Below is the striked out source code which needs to be removed from the MainModule
. In the main module, we only have reference to WelcomeComponent
and MastePageComponent
.
★ Two modules are decoupled from each other when import does not exist between those modules. Even if you do not use the component and there is an import, decoupling is not complete and the JS will be loaded.
Lot of Code has been removed for clarity. Please download source code
for full code.
import { CustomerComponent } from '../Component/CustomerComponent';
import { SupplierComponent } from '../Component/SupplierComponent';
import { WelcomeComponent } from '../Component/WelcomeComponent';
import { GridComponent } from '../Component/GridComponent';
import { MasterPageComponent } from '../Component/MasterPageComponent';
@NgModule({
imports: [RouterModule.forRoot(ApplicationRoutes),
InMemoryWebApiModule.forRoot(CustomerApiService),
BrowserModule,ReactiveFormsModule,
FormsModule,HttpModule],
declarations: [CustomerComponent,
MasterPageComponent,
SupplierComponent,
WelcomeComponent,
GridComponent],
bootstrap: [MasterPageComponent]
})
export class MainModuleLibrary { }
As said previously, “A SIMPLE IMPORT REFERENCE” will make two modules coupled. If the modules are coupled, those JS files will be loaded.
If you remember, “MainModule.ts” loads the Routes from “Routing.ts” and Routing.ts has import references to SupplierComponent and CustomerComponent .
So loading routing will load the other components as well and we will not be able to achieve lazy loading.
So let us remove all references of Customer and Supplier component from MainModule.ts, see the striked out code down below:
|
|
import {Component} from '@angular/core';
import {CustomerComponent} from '../Component/CustomerComponent';
import {SupplierComponent} from "../Component/SupplierComponent";
import {WelcomeComponent} from "../Component/WelcomeComponent";
export const ApplicationRoutes = [
{ path: 'Customer', component: CustomerComponent },
{ path: 'Supplier', component: SupplierComponent },
{ path: '', component:WelcomeComponent },
{ path: 'UI/Index.html', component:WelcomeComponent }
];
But still, we need to still define routes for “Customer
” and “Supplier
” and the same time, not use “import
” statement as that makes the module coupled. If you look at the current syntax of defining route, we need to have that component in the import
or else, we cannot define the route.
{ path: 'CustomerComponent', component:CustomerComponent },
For that, Angular has given a nice property called as “loadChildren
”. In “loadChildren
”, we need to give the module in a single quote like a string
. Which means that this thing will be evaluated during run time and now compile time.
{
path: 'Customer',
loadChildren:'../Module/CustomerModuleLibrary#CustomerModuleLibrary'
}
The structure of “loadChildren
” should follow this pattern:
The full code of the route will look something as shown below:
import {Component} from '@angular/core';
import {WelcomeComponent} from "../Component/WelcomeComponent";
export const ApplicationRoutes = [
{ path: 'Customer',
loadChildren:'../Module/CustomerModuleLibrary#CustomerModuleLibrary' },
{ path: 'Supplier',
loadChildren: '../Module/SupplierModuleLibrary#SupplierModuleLibrary' },
{ path: '', component:WelcomeComponent },
{ path: 'UI/Index.html', component:WelcomeComponent },
{ path: 'UI', component:WelcomeComponent }
];
We also need to create two more route files, one for “Customer
” and one for “Supplier
” as shown below:
import {Component} from '@angular/core';
import {CustomerComponent} from "../Component/CustomerComponent";
export const CustomerRoutes = [
{ path: 'Add', component:CustomerComponent }
];
import {Component} from '@angular/core';
import {SupplierComponent} from "../Component/SupplierComponent";
export const SupplierRoutes = [
{ path: 'Add', component:SupplierComponent }
];
“SupplierRoutes
” and “CustomerRoutes
” are child routes while the “ApplicationRoutes
” is the parent route.
In supplier
module and customer
modules, we need to load their respective routes defined in “Step 3”. To load child routes, we need to use “RouterModule.forChild
”.
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {FormsModule , ReactiveFormsModule} from "@angular/forms"
import { SupplierComponent } from '../Component/SupplierComponent';
import { RouterModule } from '@angular/router';
import { SupplierRoutes } from '../Routing/SupplierRouting';
import {CustomerApiService} from "../Api/CustomerApi"
@NgModule({
imports: [RouterModule.forChild(SupplierRoutes),
CommonModule,ReactiveFormsModule,
FormsModule],
declarations: [SupplierComponent],
bootstrap: [SupplierComponent]
})
export class SupplierModuleLibrary { }
Same way we need to for Customer
Module.
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {FormsModule , ReactiveFormsModule} from "@angular/forms"
import { CustomerComponent } from '../Component/CustomerComponent';
import { GridComponent } from '../Component/GridComponent';
import { RouterModule } from '@angular/router';
import { CustomerRoutes } from '../Routing/CustomerRouting';
import { InMemoryWebApiModule } from 'angular2-in-memory-web-api';
import {CustomerApiService} from "../Api/CustomerApi"
import { HttpModule } from '@angular/http';
@NgModule({
imports: [RouterModule.forChild(CustomerRoutes),
InMemoryWebApiModule.forRoot(CustomerApiService),
CommonModule,ReactiveFormsModule,
FormsModule,HttpModule],
declarations: [CustomerComponent,
GridComponent],
bootstrap: [CustomerComponent]
})
export class CustomerModuleLibrary { }
In step 3 and 4, we have defined parent routes and child routes. Parent routes are defined in “Routing.ts” while child routes are defined in “CustomerRouting.ts” and “SupplierRouting.ts”. So now the router link has to be changed to “Supplier/Add ” and “Customer/Add ” as shown in the below code.
|
|
<a [routerLink]="['Supplier/Add']">Supplier</a> <br />
<a [routerLink]="['Customer/Add']">Customer</a><br>
Now, the full code look of master page looks as shown below:
<table border="0" width="100%">
<tr>
<td width="20%"><img src="http://www.questpond.com/img/logo.jpg" alt="Alternate Text" />
</td>
<td width="80%">Questpond.com Private limited</td>
</tr>
<tr>
<td valign=top>Left Menu<br />
<a [routerLink]="['Supplier/Add']">Supplier</a> <br />
<a [routerLink]="['Customer/Add']">Customer</a><br>
<a [routerLink]="['']">Home</a>
</td>
<td>
<div id="dynamicscreen">
<router-outlet></router-outlet>
</div>
</td>
</tr>
<tr>
<td></td>
<td>Copy right @Questpond</td>
</tr>
</table>
“BrowserModule
” and “CommonModule
” are modules of Angular. “BrowserModule
” has code which starts up services and launches the application while “CommonModule
” has directives like “NgIf
” and “NgFor
”.
“BrowserModule
” re-exports “CommonModule
”. Or if I put in simple words, “BrowserModule
” uses “CommonModule
”. So if you are loading “BrowserModule
”, you are loading “CommonModule
” as well.
So now if you are loading “BrowserModule ” in all three modules, then you will end uploading “CommonModule ” three times. When you are doing Lazy Loading, you really do not want to load things three times, it should be loaded only once.
So if you have “BrowserModule ” in all three modules, then you would end up getting such kind of error as shown in the below figure.
This error says “BrowserModule ” has already been loaded in the “MainModule ”. Please use “CommonModule ” in “CustomerModule ” and “SupplierModule ”.
|
|
So in main module, load the browser module and in the rest of the modules, load “CommonModule
”.
import { BrowserModule } from '@angular/platform-browser';
@NgModule({
imports: [RouterModule.forRoot(ApplicationRoutes),
InMemoryWebApiModule.forRoot(CustomerApiService),
BrowserModule,ReactiveFormsModule,
FormsModule,HttpModule],
declarations: [
MasterPageComponent,
WelcomeComponent],
bootstrap: [MasterPageComponent]
})
export class MainModuleLibrary { }
But in customer and supplier modules, just load common module.
import { CommonModule } from '@angular/common';
@NgModule({
imports: [RouterModule.forChild(SupplierRoutes),
CommonModule,ReactiveFormsModule,
FormsModule],
declarations: [SupplierComponent],
bootstrap: [SupplierComponent]
})
export class SupplierModuleLibrary { }
import { CommonModule } from '@angular/common';
@NgModule({
imports: [RouterModule.forChild(CustomerRoutes),
InMemoryWebApiModule.forRoot(CustomerApiService),
CommonModule,ReactiveFormsModule,
FormsModule,HttpModule],
declarations: [CustomerComponent,
GridComponent],
bootstrap: [CustomerComponent]
})
export class CustomerModuleLibrary { }
Now run your application, go to network tab and check if lazy loading is working. You can see when the application runs at the start, only “WelcomeComponent
” and “MasterPageComponent
” is loaded. Once you click on supplier
and customer
, the respective components will be loaded at that time.
Please put a proper filter so that you do not see all JS files in your network.
jQuery is a very old and trusted JavaScript framework. It has lot of UI components which are stable and trusted. As a practice, you should not use jQuery with Angular because both of them can overlap with DOM manipulation causing confusion.
jQuery manipulates HTML DOM directly using “$ ” syntaxes while Angular creates a sugar-coated DOM over HTML DOM and does the manipulation via this sugar-coated DOM.
So jQuery can manipulate HTML DOM without Angular not having knowledge about this manipulation creating more confusion.
| |
But then, there are instances when we would like to use jQuery UI components like jQuery grids, jQuery calendar and so on which probably are not available in Angular.
★ If Angular is your prime framework, then first see that you get your solution inside Angular, if not then use jQuery or any other framework.
In this lab, we will use jQuery to fade away and fade in our grid component. So let’s create a button with name Hide Grid. When the end user clicks on Hide Grid, the grid should gradually become visible and invisible.
So the first step is to get jQuery. Let’s fire up the node command and also let’s get jQuery as well as let's save the entry into “package.json” file.
npm install jquery –save
JavaScript is divided into two generations, one generation before typescript, i.e., pure JavaScript and other generation after typescript. JavaScript is a dynamic and an untyped language while typescript is strongly typed. We can call methods which do not exist, assign variables which are not created as so on.
On the other side, typescript is strongly typed. Everything is done during design time / compile time, typescript has to know the methods, parameters everything upfront.
Now frameworks like jQuery are made in pure JavaScript, so if they need to be used in typescript, we need to expose their types, parameters and so on. That’s where we need to create typing files. Typings are typescript files which expose the shape and structure of JavaScript objects so that typescript can understand JavaScript types during design time.
You must be wondering so do we need to create the typing’s file manually? No, you do not need to. jQuery already has typings, on the contrary almost all popular JavaScript frameworks have their typings file.
So to get the jQuery typings, we need to do npm install by pointing at “@types/jquery
”. In fact, you can load any types by using “@types
” for example, if you want to load lodash, you can do “npm install
” on “@types/lodash
”.
npm install @types/jquery –save
jQuery references HTML UI elements using selectors. In selectors, we need to provide name or ids by which we can get reference of that HTML element. So let’s wrap our “grid
” component inside a DIV
tag and let's assign some “id
” value to it , like the one we have given in the below code “divgrid
”.
Also, we have created a button which calls the “Fade
” methods from the component.
<input (click)="Fade()" type="button" value="Hide Grid"/>
<div id="divgrid">
<grid-ui
[grid-columns]="[{'colName':'CustomerCode'},
{'colName':'CustomerName'},{'colName':'CustomerAmount'}]"
[grid-data]="Customers"
(grid-selected)="Select($event)"></grid-ui>
<div>
Now the first thing is to import jQuery into your component file. For that, we need to use the below code. You can see the import
statement is bit differently used. We are using “*
” and “as
” keyword. “*” means we want to refer full JQuery file and “$
” is the alias by which we will be referring jQuery syntax in the code.
import * as $ from "jquery";
Once jQuery has been imported, we can now use “$
” to execute jQuery syntaxes. You can see that we have created a method called as “Fade
” in which we are referring the HTML DIV ID and calling the “fadeToggle
” method to fade in and out.
import * as $ from "jquery";
export class CustomerComponent {
Fade(){
$("#divgrid").fadeToggle(3000);
}
}
Our JS files are loaded using SystemJS module loader. So we need to make an entry into “Systemjs.config.js” file stating where the jQuery file is located.
'jquery': '../node_modules/jquery/dist/jquery.js'
★ If you see, we have specified the folder path as “dist”. “dist” stands for distribution folder. The final compiled copy of jQuery is in this folder so we have specified the same.
Now run the program and see the output. If you click on the “Hide” button, you should see the grid fading out and in.
For further reading, do watch the below interview preparation videos and step by step video series.
- 22nd September, 2017: Initial version