Real-time search with ASP.NET and Elasticsearch

A common problem that we are faced when we have deployed our applications is improve the performance of a page or feature.

In my case for example I had a field where I could search and select a city, so the starting elements were a lot and the search was quite slow; I wanted a better user experience.

We can solve performance problems like these with the help of a cache or a full-text search.

I have chosen the last one and elastic search as full-text engine, so I’ll describe the steps that I followed to configure and use it in my application.

Installation

The first step is install the elastic search server, that you can download here.

Once installed we have to start it by executing the following executable:

<Installation path>\bin\elasticsearch.bat

This is the server log:

log

The server will take care to index the content that we will pass to it; in order to do that we need a client to use in our application; in my case the application was .NET and I used NEST.

NEST

As said above, NEST is an elastic search high level client for .NET applications.

The first step is install it in the application with nuget:

Install-package NEST

And in the package.config we’ll have:

package

Now we have all the necessary tools and we can develop the code for the search feature.

Client

We define a client class that has one responsability, that is setup the url and the default index of the client, and that instantiate it:


public class ElasticSearchClient
{
privatereadonlyIElasticClient _client;

publicElasticSearchClient(IElasticClient client)
{
_client = client;
}

publicElasticSearchClient(string uri, string indexName) : this(CreateElasticClient(uri, indexName)) {}

publicIElasticClientGetClient()
{
return_client;
}

privatestaticElasticClientCreateElasticClient(string uri, string indexName)
{
var node = newUri(uri);
var setting = newConnectionSettings(node);
setting.DefaultIndex(indexName);
returnnewElasticClient(setting);
}
}

Once instantiated, the class returns a new instance of the client; we can register it in the startup class of the application with autofac:


public partial class Startup
{
publicvoidConfiguration(IAppBuilder app)
{
var builder = newContainerBuilder();

builder.Register(c => newElasticSearchClient("http://localhost:9200", "cities"))
.AsSelf()
.SingleInstance();
...
}
}

Service base class

A service that uses an elasticsearch index should be able to do some basic operations, that concerns the logics of the full-text indexes.

We have to deal with the initialize a specific index, populate the index with the contents, obviously performs a search on the index with specific parameters.

So, we have to define an interface like this:


internal interface IElasticSearchService<T> where T : class
{
voidInit();
voidCheckIndex();
voidBulkInsert(List<T> objects);
IEnumerable<T> Search(string query);
}

I like to separate the init method, that create the index, from the checkindex method, that check if the index already exists.

Now we can implement the basic service:


public class ElasticSearchService<T> : IElasticSearchService<T> where T : class
{
protectedreadonlyContext Db = newContext();
protectedreadonlyElasticSearchClient ElasticSearchClient;
protectedreadonlystring IndexName;

publicElasticSearchService(ElasticSearchClient elasticSearchClient, string indexName)
{
ElasticSearchClient = elasticSearchClient;
IndexName = indexName;
}

publicvirtualvoidInit()
{
CheckIndex();
BulkInsert(Db.Set<T>().ToList());
}

publicvoidCheckIndex()
{
if (IndexExist()) return;
var response = CreateIndex();

if (!response.IsValid)
{
thrownewException(response.ServerError.ToString(), response.OriginalException);
}
}

publicvoidBulkInsert(List<T> objects)
{
var response = ElasticSearchClient.GetClient().IndexMany(objects, IndexName);
if (!response.IsValid)
{
thrownewException(response.ServerError.ToString(), response.OriginalException);
}
}

publicvirtualIEnumerable<T> Search(string query)
{
var results = ElasticSearchClient.GetClient().Search<T>(c => c.From(0).Size(10).Query(q => q.Prefix("_all", query)));

returnresults.Documents;
}

protectedvirtualIResponseCreateIndex()
{
var indexDescriptor = newCreateIndexDescriptor(IndexName).Mappings(ms => ms.Map<T>(m => m.AutoMap()));
returnElasticSearchClient.GetClient().CreateIndex(indexDescriptor);
}

protectedboolIndexExist()
{
returnElasticSearchClient.GetClient().IndexExists(IndexName).Exists;
}
}

The constructor accept the client and the index name.

We define a virtual init method, that check if the index exists and do a bulkinsert of a list of object; the method is virtual, we think that a derived service could override the method.

This bulkinsert method leverage the client to index the object list and the search method implements a basic search, that searchs in all the fields of the objects by using the special field _all, which contains the concatenate values of all fields.

The method returns the first 10 elements.

Createindex create a specific index with automap option, that infers the elasticsearch fields datatypes from the POCO object that we pass to it; it’s protected, so the derived class could use it.

IndexExists check if an index exists and it can be used from the derived class as well.

Service

Now we can implement a specific service, that inherits from ElasticSearchService class.

In this example I need to search in a list of cities and related districts, so I need to override the CreateIndex method like this:


public sealed class CitiesService : ElasticSearchService<City>
{
publicCitiesService(ElasticSearchClient elasticSearchClient, string indexName): base(elasticSearchClient, indexName) {}

protectedoverrideIResponseCreateIndex()
{
var indexDescriptor = newCreateIndexDescriptor(IndexName).Mappings(
ms => ms.Map<City>(m => m.AutoMap().Properties(ps =>
ps.Nested<District>(n => n
.Name(nn => nn.District)
.AutoMap()))));

returnElasticSearchClient.GetClient().CreateIndex(indexDescriptor);
}

publicoverrideIEnumerable<City> Search(string query)
{
var results = ElasticSearchClient.GetClient().Search<City>(c => c.From(0).Size(10).Query(q => q.Prefix(p => p.Name, query) || q.Term("district.name", query)));

returnresults.Documents.OrderBy(d => d.Name);
}
}

What I need to do is automap the city object and the district, that is a closely related entity of the city; so I have to map the District property as nested with the automap option as well.

Thus I will able to search for all the properties of the city and the district.

The other method that I override is the Search method; I search partially in the name of the city (Prefix) and the specific term in the district name (Term) and I returns the first 10 elements.

Now I have to register the service with autofac:


public partial class Startup
{
public void Configuration(IAppBuilder app)
{
var builder = newContainerBuilder();

builder.Register(c => newElasticSearchClient("http://localhost:9200", "cities"))
.AsSelf()
.SingleInstance();

builder.Register(c => newCitiesService(c.Resolve<ElasticSearchClient>(), "cities"))
.AsSelf()
.AsImplementedInterfaces()
.SingleInstance();

...
}
}

The last step is initialize the full text index of my service:


public partial class Startup
{
publicvoidConfiguration(IAppBuilder app)
{
var builder = newContainerBuilder();

builder.Register(c => newElasticSearchClient("http://localhost:9200", "cities"))
.AsSelf()
.SingleInstance();

builder.Register(c => newCitiesService(c.Resolve<ElasticSearchClient>(), "cities"))
.AsSelf()
.AsImplementedInterfaces()
.SingleInstance();

...

InitElasticSearchServices(containerBuilder);
}

privatestaticvoidInitElasticSearchServices(IContainer containerBuilder)
{
var citiesServices = containerBuilder.Resolve<CitiesService>();
citiesServices.Init();
}
}

I make a new instance of the service and call the Init method of the ElasticSearchService that we have seen above.

This method will create and populate the index.

Web API

Now I can use the service in my Web API, like this:


public class CitiesController : ApiController
{
privatereadonlyCitiesService _elasticSearchService;

publicCitiesController(CitiesService elasticSearchService)
{
_elasticSearchService = elasticSearchService;
}

// GET: api/Cities
publicIEnumerable<City> GetCities(string query)
{
return_elasticSearchService.Search(query);
}

protectedoverridevoidDispose(bool disposing)
{
base.Dispose(disposing);
}
}

You can find the source code of this topic here.

Real-time search with ASP.NET and Elasticsearch

Attachments management with Angular 2

A common issue that we faced in our applications is implement a component to allow the management of the attachment upload.

We need to insert a file input field in the page, grab the change event of the field, extract the file and send it to a service.

Recently I have needed to implement this functionality with Angular 2, so I’m going to explain what I have done.

Services

First of all I implement two different services, one for the file metadata and one for the blob object.

Based on a recent post, I use a base class WebApi and I define the service url:


import { Injectable } from "@angular/core";
import { Http } from "@angular/http";
import { Attachment } from "./attachment.model";
import { WebApi } from "../shared/webapi";

@Injectable()
export class AttachmentService extends WebApi<Attachment> {
constructor(public http: Http) {
super("/api/attachments", http);
}
}

The referenced service is a simple Restful service.

The second one is a service for the blob upload:


import { Injectable } from "@angular/core";
import { Http, Headers, RequestOptions, Response } from "@angular/http";
import { Observable } from "rxjs/Observable";
import { FileBlob } from "./fileBlob.model";
import { WebApi } from "../shared/webapi";

@Injectable()
export class FileBlobService extends WebApi<FileBlob> {
constructor(public http: Http) {
super("/api/fileBlobs", http);
}

public DownloadFile(id: string) {
window.open("api/fileBlobs/GetFileBlob?id=" + id, '_blank');
}

public PostFile(entity: File): Observable<File> {
let formData = new FormData();
formData.append(entity.name, entity);

return this.http.post(this.url, formData).map(this.extractData).catch(this.handleError);
}
}

The PostFile method compose a HTML FormData object with the content of the file and post it to a specific WebApi.

The DownloadFile method is simplier and call a service in a new window that returns the content of the file.

The server-side method is look like this:


public class FileBlobsController : ApiController
{
private readonly Context _db = new Context();

[ResponseType(typeof(Guid))]
public async Task<IHttpActionResult> PostFileBlob()
{
if (!Request.Content.IsMimeMultipartContent())
throw new Exception();

var provider = new MultipartMemoryStreamProvider();
await Request.Content.ReadAsMultipartAsync(provider);

HttpContent content = provider.Contents.First();
var fileName = content.Headers.ContentDisposition.FileName.Trim('\"');
var buffer = await content.ReadAsByteArrayAsync();

var fileBlob = new FileBlob()
{
Id = Guid.NewGuid(),
Name = fileName,
File = buffer
};

_db.FileBlobs.Add(fileBlob);
await _db.SaveChangesAsync();

return Ok(fileBlob.Id);
}
}

We have to use the MultiPartMemoryStreamProvider to retrieve the content of the file and store it in a specific table.

Component

We need two methods, the first one to download the existing attachment, the second one to add a new attachment:


import { Component, Input, Output, EventEmitter } from "@angular/core";
import { Constants } from "../shared/commons";
import { Attachment } from "./attachment.model";
import { FileBlobService } from "./fileBlob.service";
import { AlertService } from "../core/alert.service";

@Component({
moduleId: module.id,
selector: "attachment",
templateUrl: "attachment.component.html"
})

export class AttachmentComponent {
@Input() placeholder: string;
@Input() name: string;
@Input() validationEnabled: boolean;
@Input() attachment: Attachment;
@Output() onSaved = new EventEmitter<Attachment>();
public fileBlob: File;

constructor (private fileBlobService: FileBlobService, private alertService: AlertService) {}

public DownloadAttachment() {
this.fileBlobService.DownloadFile(this.attachment.IdFileBlob);
}

public AddAttachment(event) {
let attachments = event.target.files;
if (attachments.length > 0) {
let file:File = attachments[0];
this.fileBlobService.PostFile(file).subscribe(
(res) => {
let id: string = Constants.guidEmpty;

if (this.attachment != null)
id = this.attachment.Id;

this.attachment = {
Id: id,
IdFileBlob: res.toString(),
Name: file.name,
Size: file.size
};

this.onSaved.emit(this.attachment);
},
(error) => this.alertService.Error(error));
}
}

...
}

The AddAttachment method deserves an explanation; it accepts an event parameter, fired by the file input filed of the ui when a new attachment is selected.

The method retrieves the file from the event and pass it as a parameter to the PostFile method that we have seen above.

Once saved, an object with the file metadata is created and passed with the onSaved event to the parent component, that it deal with the object:


export class InvoiceDetailComponent {
...

public onAttachmentSaved(attachment: Attachment) {
this.attachment = attachment;
}
}

Module

We define a feature module like this:


import { NgModule } from "@angular/core";
import { HttpModule } from "@angular/http";

import { SharedModule } from "../shared/shared.module";
import { AttachmentComponent } from "./attachment.component";
import { AttachmentService } from "./attachment.service";
import { FileBlobService } from "./fileBlob.service";

let options: any = {
autoDismiss: true,
positionClass: 'toast-bottom-right',
};

@NgModule ({
imports: [
SharedModule,
HttpModule
],
exports: [
AttachmentComponent
],
declarations: [
AttachmentComponent
],
providers: [
AttachmentService,
FileBlobService
]
})

export class AttachmentModule {}

The module exports the component and provides the services discussed above.

View

We have to implement the view for the attachment module:

<div *ngIf="AttachmentIsNull()">
<label class="btn btn-primary" for="fileBlob">
<i class="fa fa-paperclip"></i> {{ "ATTACHINVOICE" | translate }}
<input id="fileBlob" type="file" [(ngModel)]="fileBlob" (change)="AddAttachment($event)" [required]="validationEnabled" style="display: none;" />
</label>
</div>
<div *ngIf="!AttachmentIsNull()">
<span *ngIf="attachment" (click)="DownloadAttachment()">{{attachment.Name}}</span>
<input type="button" class="btn btn-primary" value="Upload new" (click)="UploadNewAttachment()" />
</div>

In the view we have a file input field that bind the change event with the AddAttachment method.

The additional buttons allow us to clear the current attachment and upload a new one.

The last change is in the parent view:


<form #invoiceForm="ngForm">
<div class="form">
...
<div class="form-group">
<label for="attachment">{{ "ATTACHMENT" | translate }}</label>
<attachment placeholder="ATTACHMENT" name="attachment" [attachment]="attachment" (onSaved)="onAttachmentSaved($event)" [validationEnabled]="validationEnabled"></attachment>
</div>
</div>
</form>

We have added the attachment component in the view and we have binded the onSaved event, in order to retrieve the file metadata.

You can find the source code here.

 

 

 

 

Attachments management with Angular 2

Http service in Angular 2

Use the http service in Angular 1 meant to deal with promises and defer, because it was based on APIs exposed by the $q service.

Angular 2 makes a step ahead and the new implementation of the http service involves the observable pattern, with the using of RxJS javascript library.

Why this big change? Because an observable is a powerful way to observe the behavior of a variable and its changes, for example when we assign a value or change it.

What we can do to use the http service efficiently is implement a typescript class that exposes the CRUD operation on a generic entity, based on a Web API.

Base class

This class exposes five basic methods to do simple CRUD operations; the first step is import the libraries:


import { Http, Response, Headers, RequestOptions } from "@angular/http";
import { Observable } from "rxjs/Observable";

We import a bunch of objects of the http angular library and the observable library.

Now we can implement the class:


export class WebApi<T> {
protected url: string;
protected options: RequestOptions;

constructor(url: string, public http: Http) {
this.url = url;
let headers = new Headers({ "Content-Type": "application/json" });
this.options = new RequestOptions({ headers: headers });
}

public GetAll(): Observable<T[]> {
return this.http.get(this.url, this.options).map(this.extractData).catch(this.handleError);
};

public Get(id: string): Observable<T> {
return this.http.get(this.url + "/" + id, this.options).map(this.extractData).catch(this.handleError);
}

public Put(id: string, entity: T): Observable<boolean> {
return this.http.put(this.url + "/" + id, JSON.stringify(entity), this.options).map(this.extractResponseStatus).catch(this.handleError);
}

public Post(entity: T): Observable<T> {
return this.http.post(this.url, JSON.stringify(entity), this.options).map(this.extractData).catch(this.handleError);
}

public Delete(id: string): Observable<boolean> {
return this.http.delete(this.url + "/" + id, this.options).map(this.extractResponseStatus).catch(this.handleError);
}

protected extractData(res: Response) {
let body = res.json();
return body || {};
}

protected handleError(error: any) {
let errMsg = (error.message) ? error.message :
error.status ? `${error.status} - ${error.statusText}` : 'Server error';
console.error(errMsg);
return Observable.throw(errMsg);
}

private extractResponseStatus(res: Response) {
return res.ok;
}
}

This is a typed class, with it we are able to define an instance of the class for a specific entity.

All the methods return an Observable property, dealing with the http service; every observable response uses a method to return the json part of the response and another method to handle and log response errors.

This is a class that will be used for our services.

The service

A specific service extends the base class and it can add some specific methods.

The implementation is look like this:


import { Injectable } from "@angular/core";
import { Http } from "@angular/http";
import { Customer } from "./customer.model";
import { WebApi } from "../shared/webapi";

@Injectable()
export class CustomerService extends WebApi<Customer> {
constructor(public http: Http) {
super("/api/customers", http);
}
}

The service extends the WebApi typed class and injects the base url of the Web API and the http service instance.

Using

In a controller we can use the service to retrieve the list of the customers:


.....

import { CustomerService } from "./customer.service";
.....
export class CustomerComponent implements OnInit {
.....
constructor(private customerService: CustomerService, .....) {}

ngOnInit() {
.....
this.Load();
}

public Load() {
.....
this.customerService.GetAll().subscribe(
(data) => {
.....
this.customers = data;
.....
},
(error) => this.alertService.Error(error));
}
}

As you seen above, the GetAll method returns an observable property, so in order to retrieve the reponse, we need to subscribe to it.

The subscribe method can accepts two functions as parameters; the first one will be called if we’ll receive a successfully response; the second one will be executed if an error occurred.

The source code of this topic is available here.

 

Http service in Angular 2

Translations in Angular 2

A common requirement in Angular 2 is configure and share a service used from all the components of the application.

In a multilanguage application, a service that we need to use is the provider for the language translations; obviously this module will be used in all the components, or at least in those that have an html template.

The service that we use is ng2-translate; what we need to do is installing the package, provide it with a shared module and use the translation service in the components.

Installation

First we install the package:

npm install ng2-translate –save

Once installed, we have to configure the package with a module loader like SystemJS (or Webpack):

(function (global) {
System.config({
paths: {
'npm:': 'node_modules/'
},
map: {
app: 'app',
'@angular/core': 'npm:@angular/core/bundles/core.umd.js',
.....
'ng2-translate': 'npm:ng2-translate/bundles/index.js'
},
packages: {
.....
}
});
})(this);

Now the package is installed and configured and we can going to provide the service.

Providing

As explained below, in order to provide the service we have to use a shared module of the application; we already have discussed in a previous post a module like that, and we can edit it:

import { NgModule, ModuleWithProviders, Optional, SkipSelf } from "@angular/core";
.....
import { TranslateModule } from "ng2-translate";
.....
@NgModule ({
imports: [
.....
],
exports: [
.....
TranslateModule,
.....
]
})
export class SharedModule {}

We have to export the TraslateModule in order to make it shared; otherwise the others modules won’t be able to use it.

Now the translate module is provided and we can define the translations; by using the default configuration, we can create the i18n folder in the root of the project and create a json file for every language supported.

For example, en.json:

{
"NEW": "New",
"SAVE": "Save",
"CLOSE": "Close",
"DELETE": "Delete",
"NAME": "Name",
"ADDRESS": "Address",
"ATTACHMENT": "Attachment",
"ATTACHINVOICE": "Attach invoice",
"CITY": "City",
"ZIP": "Zip",
"DISTRICT": "District",
"COUNTRY": "Country",
"NUMBER": "Number",
"YEAR": "Year",
"CUSTOMER": "Customer",
"EMISSIONDATE": "Emission Date",
"DUEDATE": "Due Date",
"PAYMENTDATE": "Payment Date",
"CUSTOMERSLOADED": "Customers loaded successfully",
"CUSTOMERSAVED": "Customer saved",
"CUSTOMERDELETED": "Customer deleted",
"SEARCH": "Search",
"INVOICESLOADED": "Invoices loaded successfully",
"INVOICESAVED": "Invoice saved",
"INVOICEDELETED": "Invoice deleted"
}

Now we are ready to use the translations in the components of the application.

Translations

The service that make the work of translation is the TranslateService, and we import it:

import { Component, OnInit } from "@angular/core";
import { TranslateService } from "ng2-translate";
.....
@Component({
moduleId: module.id,
selector: "customer",
templateUrl: "customer.component.html"
})
export class CustomerComponent implements OnInit {
constructor(private customerService: CustomerService, private alertService: AlertService, private searchService: SearchService, <strong>private translateService: TranslateService</strong>) {}
public Load() {
this.alertService.isLoading = true;
this.customerService.GetAll().subscribe(
(data) => {
this.alertService.isLoading = false;
this.customers = data;
this.customer = null;
this.translateService.get("CUSTOMERSLOADED").subscribe((res: string) => {
this.alertService.Success(res);
});
},
(error) => this.alertService.Error(error));
}
.....
}

In this code, we inject the service in the component constructor and we use it to translate the CUSTOMERLOADED resource and show an alert message.

Another purpose of the translation service is translate the labels of a view:

<div [hidden]="edit">
<input type="button" class="btn btn-primary" value="New" (click)="New()" />
<table class="table table-striped">
<thead>
<tr>
<th>
{{ "NAME" | translate }}</th>
<th>
{{ "ADDRESS" | translate }}</th>
<th>
{{ "CITY" | translate }}</th>
</tr>
</thead>
<tbody>
.....</tbody>
</table>
</div>
.....

Here you can find the github repository with the source code.

 

Translations in Angular 2

Lazy loading of modules in Angular 2

In the last post I shortly mentioned the ability of Angular 2 router to load lazily the application modules.

This topic deserves more explanations, so let’s go into details about this feature.

The start point is an Angular 2 applications with a couple of indipendent modules; in orther to have better performances we want to load these module lazily.

Let’s go to the implementation details.

Modules

In the application we have the usual modules Customer and Invoice; the first one:


import { NgModule } from "@angular/core";
import { Routes, RouterModule } from "@angular/router";
import { HttpModule } from "@angular/http";
import { CustomerComponent } from "./customer.component";
......

@NgModule ({
imports: [
HttpModule,
RouterModule.forChild([
{
path: "customers",
component: CustomerComponent
}])
],
exports: [
RouterModule
],
declarations: [
CustomerComponent
]
})

export class CustomerModule {}

And the second one:

import { NgModule } from "@angular/core";
import { Routes, RouterModule } from "@angular/router";
import { HttpModule } from "@angular/http";
import { InvoiceComponent } from "./invoice.component";
......

@NgModule ({
imports: [
HttpModule,
RouterModule.forChild([
{
path: "invoices",
component: InvoiceComponent
}])
],
exports: [
RouterModule
],
declarations: [
InvoiceComponent
]
})

export class InvoiceModule {}

We have defined the roots for the features modules, that must be configured forChild, according with the Angular 2 specifications.

To enable them in the application, we have to import them in the main module:

import { NgModule } from "@angular/core";
import { HttpModule } from "@angular/http";
import { RouterModule } from "@angular/router";
import { CustomerModule } from "./customer/customer.module";
import { InvoiceModule } from "./invoice/invoice.module";
......

@NgModule ({
imports: [
HttpModule,
CustomerModule,
InvoiceModule,
RouterModule.forRoot([
{
path: "",
redirectTo: "customers",
pathMatch: "full"
}
])],
exports: [
RouterModule
],
......
})

export class AppModule {}

We have finished the configuration and the if we start the application, it’ll work as expected; but we can do more.

At the moment, all the modules of the application will be loaded at the startup of the application; this is not a news, we are not surprized about that, the old angular applications worked in this way.

But with angular 2 we can do more, we are able to load the modules lazily, when required by the application.

The only thing to do is to change the configuration of the router.

Router configuration

The main change concerns the AppModule, where we have to change the configuration of the router:

import { NgModule } from "@angular/core";
import { HttpModule } from "@angular/http";
import { RouterModule } from "@angular/router";
......

@NgModule ({
imports: [
HttpModule,
RouterModule.forRoot([
{
path: "",
redirectTo: "customers",
pathMatch: "full"
},
{
path: "customers",
loadChildren: "app/customer/customer.module#CustomerModule"
},
{
path: "invoices",
loadChildren: "app/invoice/invoice.module#InvoiceModule"
},
])],
exports: [
RouterModule
]
......
})

export class AppModule {}

Instead of import the feature modules, we have defined the path of them as “Childrens”.

What we need to do now is update the paths in the customer and invoice modules:

@NgModule ({
imports: [
......
RouterModule.forChild([
{
path: "",
component: CustomerComponent
}])
],
......
})
@NgModule ({
 imports: [ 
 ......
 RouterModule.forChild([
 {
 path: "",
 component: InvoiceComponent
 }])
 ],
 ......
})

In short, we have changed the default path of the modules, because we declared these in the AppModule above.

The configuration is pretty simple but we can obtain a huge performance improvement.

Consider that in big applications, the modules wont be loaded eagerly and as a result the startup of the application will be very fast.

Here the full Angular2 project.

 

Lazy loading of modules in Angular 2

Feature modules in Angular 2

Angular 2 give us the chance to organize our application in modules.

Briefly, a module is a library where there may be components, directives and pipes and define a block of functionalities; Angular 2 libraries itself are modules, like FormsModule, HttpModule.

One cool behaviour of the modules is the ability to be loaded eagerly or lazyly through the router; this is a huge improvement, because we are able to load a module only when required and not at the startup of the application; we’ll see that the router help us in this phase.

What we’ll do in this example is define two commons modules, shared in the application; then we’ll define the feature modules, that are modules with a specific purphose; finally we’ll import them in the root module of the application.

Shared modules

In every application we have some stuff that we want to share, libraries such as translations, bootstrap, rxjs, and angular modules like FormsModule and even CommonsModule.

For these, the better choice is define a shared module that import these libraries:


import { NgModule, ModuleWithProviders, Optional, SkipSelf } from "@angular/core";
import { FormsModule } from "@angular/forms";
import { CommonModule } from "@angular/common";
import { TranslateModule } from "ng2-translate";
import { ModalModule, DatepickerModule } from 'ng2-bootstrap/ng2-bootstrap';

import "rxjs/add/observable/throw";
import "rxjs/add/observable/forkJoin";
import "rxjs/add/operator/catch";
import "rxjs/add/operator/debounceTime";
import "rxjs/add/operator/distinctUntilChanged";
import "rxjs/add/operator/map";
import "rxjs/add/operator/switchMap";
import "rxjs/add/operator/toPromise";

@NgModule ({
imports: [
FormsModule,
CommonModule,
ModalModule.forRoot(),
DatepickerModule.forRoot(),

],
exports: [
FormsModule,
CommonModule,
TranslateModule,
ModalModule,
DatepickerModule
]
})

export class SharedModule {}

What we done is import the bunch of modules that we want to include, then we defined the list of imported/exported modules.

If importing the modules is obvious, we can’t says the same thing about the export; we need to export the modules in order to make them shared; otherwise the others modules couldn’t access to these libraries.

Another behaviour that deserves a mention is the forRoot static method of the ModalModule and the DatepickerModule.

Angular 2 has a convention, so when a module has singleton services needs to implement a static method forRoot; otherwise we could provide these services, but thus the other modules could import these shared module and instantiate them.

The second module that we implement is the CoreModule, that contains the singleton services of the application:


import { NgModule, ModuleWithProviders, Optional, SkipSelf } from "@angular/core";
import { ToastModule } from "ng2-toastr/ng2-toastr";
import { SearchService } from "./search.service";
import { AlertService } from "./alert.service";

let options: any = {
autoDismiss: true,
positionClass: 'toast-bottom-right',
};

@NgModule ({
imports: [
ToastModule.forRoot(options)
]
})

export class CoreModule {
constructor (@Optional() @SkipSelf() parentModule: CoreModule) {
if (parentModule) {
throw new Error('CoreModule is already loaded. Import it in the AppModule only');
}
}

static forRoot(): ModuleWithProviders {
return {
ngModule: CoreModule,
providers: [
SearchService,
ToastModule,
AlertService
]
};
}
}

First of all, we import the ToastModule as singleton, by calling the forRoot method and passing the configuration as a parameter.

As explained above, because this module provide singleton services, have to implement a static forRoot method.

The constructor of the module class deserves an additional comment; this module must be loaded only from the root module, so to disallow the import from a feature module, we check this and eventually we throw an exception.

Feature modules

We can now implement our feature modules, that have everyone a specific purphose in the application.

For example, a CustomerModule will look like this:


import { NgModule } from "@angular/core";
import { Routes, RouterModule } from "@angular/router";
import { HttpModule } from "@angular/http";

import { SharedModule } from "../shared/shared.module";
import { CityModule } from "../city/city.module";
import { CustomerComponent } from "./customer.component";
import { CustomerDetailComponent } from "./customer.detail.component";
import { SearchCustomersPipe } from "./searchCustomers.pipe";
import { CustomerService } from "./customer.service";

@NgModule ({
imports: [
HttpModule,
SharedModule,
CityModule,
RouterModule.forChild([
{
path: "",
component: CustomerComponent
}])
],
exports: [
RouterModule
],
declarations: [
CustomerComponent,
CustomerDetailComponent,
SearchCustomersPipe
],
providers: [
CustomerService
]
})

export class CustomerModule {}

You can notice that the module import the shared module defined above.

The module deals to import the modules that needs and define with the static method forChild the root paths for the module.

Then declare the components and provide the services that belongs to the module.

App module

The last module is the root module, that is the startup module.

This module imports the shared modules of the application and define the feature modules:


import { NgModule } from "@angular/core";
import { HttpModule } from "@angular/http";
import { RouterModule } from "@angular/router";
import { BrowserModule } from "@angular/platform-browser";
import { TranslateModule } from "ng2-translate";

import { SharedModule } from "./shared/shared.module";
import { CoreModule } from "./core/core.module";
import { AppComponent } from "../app/app.component";
import { HeaderComponent } from "./header.component";

@NgModule ({
imports: [
BrowserModule,
HttpModule,
TranslateModule.forRoot(),
CoreModule.forRoot(),
SharedModule,
RouterModule.forRoot([
{
path: "",
redirectTo: "customers",
pathMatch: "full"
},
{
path: "customers",
loadChildren: "app/customer/customer.module#CustomerModule"
},
{
path: "invoices",
loadChildren: "app/invoice/invoice.module#InvoiceModule"
},
])],
exports: [
RouterModule
],
declarations: [
AppComponent,
HeaderComponent
],
bootstrap: [
AppComponent
]
})

export class AppModule {}

As you can see, the CoreModule is imported with the forRoot method, which means that is singleton as well as the RouterModule.

We defined the root paths of the router by loading the concerning modules as childrens; thus they will be loaded lazy.

So, these modules and the dependent modules will be loaded the first time only once the routing path will be fired; this is a awesome feature of Angular 2.

Here the full Angular2 project.

 

Feature modules in Angular 2

Pipes in Angular 2

Developing an Angular 1 application, filters were very useful to accomplish different needs such as filter values from an array, apply a display format to a value and so on.

In Angular 2 filters are non longer availables and the pipes take place.

Instead of filters, angular pipes are more flexible because they are able to accepts parameters as input and returning an output, and can be chained.

In this example we use a pipe to filter an array of objects.

Pipe component

We create a new pipe that will be able to filter a list of customers; in orther to do that, we need to import some stuff:


import { Pipe, PipeTransform } from "@angular/core";
import { Customer } from "./customer.model";

Now we define the Pipe component attributes:


@Pipe({
name: "searchCustomers",
pure: false
})

We defined the pipe selector and a pure attribute to false.

This because we have two types of pipes, pure and impure; a pure pipe is more efficient and fast but is able to detect changes only for primitive values (string, numbers..) or a changed object reference.

Instead, impure pipes can detect any component change; in our case we’ll have an array of customers as input, so we need an impure pipe.

Because the nature of impure pipes, you need to use these components with great care; if you notice a degrade of the performance of the application, it’s possibile to substitute the pipe with a custom component that will do the same work.

This is the code of the pipe:


export class SearchCustomersPipe implements PipeTransform {
transform(customers: Customer[], searchText: string): Customer[] {
let filteredCustomers: Customer[] = new Array<Customer>();

if (customers != undefined) {
filteredCustomers = customers.filter(c => (c.Name.indexOf(searchText) != -1) || (c.Address.indexOf(searchText) != -1));
}

return filteredCustomers;
}
}

The pipe implement the PipeTransform interface and the transform method, that accept two parameter, an array of customers and the text to search as well.

Obviously the method returns the filtered array.

The last step is register the pipe in the application module:


.....

import { SearchCustomersPipe } from "./searchCustomers.pipe";

.....

@NgModule ({

.....
declarations: [
SearchCustomersPipe
]

.....
})

Now the pipe is available to be used in the view.

Customer component

In the view we should have a list of the customers, and we want to filter that typing in a search text field.

The view will look like this:

<table class="table table-striped">
<thead>
<tr>
<th>
{{ "NAME" | translate }}</th>
<th>
{{ "ADDRESS" | translate }}</th>
<th>
{{ "CITY" | translate }}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let customer of (customers | searchCustomers: searchText)">
<td>
{{customer.Name}}</td>
<td>
{{customer.Address}}</td>
<td>
{{customer.City.Name}}</td>
</tr>
</tbody>
</table>

We apply the searchCustomers pipe to a customers array (first parameter accepted by the pipe) and after the colon we pass the second parameter searchText.

Here the full Angular2 project.

Pipes in Angular 2