Tuesday, 4 December 2018

Angular Material Table with Server Side Data

In this post we will build an Angular Material Table with paging and sorting from server side api. The codes are using Angular Material 6.

We will need to create a service and data source that can be consumed by Angular component to populate and refresh the Material table. We will start with basic codes without paging and sorting initially then add the features once the basic is already working.

1. Basic table with server side data
1.1. First, we create a service:
import { Injectable }   from '@angular/core';
import { HttpClient }   from '@angular/common/http';
import { Observable } from 'rxjs';
import { User } from './models/user.model';

@Injectable()
export class UserService {
  private serviceUrl = 'http://myserviceurl';
  
  constructor(private http: HttpClient) { }
  
  getUser(): Observable<User[]> {
    return this.http.get<User[]>(this.serviceUrl);
  }  
}

1.2. Then a data source that inherits from Angular DataSource class:
import { CollectionViewer, DataSource } from "@angular/cdk/collections";
import { Observable } from 'rxjs';
import { UserService } from "./user.service";
import { User } from './models/user.model';

export class UserDataSource extends DataSource<any> {
  constructor(private userService: UserService) {
    super();
  }
  connect(): Observable<User[]> {
    return this.userService.getUser();
  }
  disconnect() { }
}

1.3. Then the Angular component that will consume the data source:
import { Component, ViewChild, AfterViewInit, OnInit} from '@angular/core';
import { MatPaginator, MatTableDataSource } from '@angular/material';
import { MatSort } from '@angular/material';

import { UserService } from './user.service';
import { UserDataSource } from './user.datasource';
import { User } from './models/user.model';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements AfterViewInit, OnInit{
  displayedColumns: string[] = ['studentId', 'studentNumber', 'firstName', 'lastName'];
  user: User;
  dataSource: UserDataSource;
   

  constructor(private userService:UserService) {
  }

  ngAfterViewInit() {
  }

  ngOnInit() {
    this.dataSource = new UserDataSource(this.userService);
  }
}

1.4. Finally the HTML template:
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
   <ng-container matColumnDef="studentId">
     <th mat-header-cell *matHeaderCellDef> Student Id </th>
     <td mat-cell *matCellDef="let user"> {{user.studentId}} </td>
   </ng-container>
   <ng-container matColumnDef="studentNumber">
     <th mat-header-cell *matHeaderCellDef> Student Number </th>
     <td mat-cell *matCellDef="let user"> {{user.studentNumber}} </td>
   </ng-container>
   <ng-container matColumnDef="firstName"> 
     <th mat-header-cell *matHeaderCellDef> First Name </th>
     <td mat-cell *matCellDef="let user"> {{user.firstName}} </td>
   </ng-container>
     <ng-container matColumnDef="lastName">
     <th mat-header-cell *matHeaderCellDef> Last Name </th>
     <td mat-cell *matCellDef="let user"> {{user.lastName}} </td>
   </ng-container>

   <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
   <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>

Once this is working, we can add the paging feature.

2. Add paging feature
2.1. Import paging module to the app module
import { MatPaginatorModule } from '@angular/material/paginator';
@NgModule({
  . . .,
  imports: [
    . . .
    MatPaginatorModule
  ],
 . . .
})

2.2. Add paginator element on HTML template:
<mat-paginator [pageSizeOptions]="[100, 500]"></mat-paginator> 

2.3. Modify getUser() method in the service class:
getUser(pageIndex : number =1, pageSize : number): Observable<User[]> {

   return this.http.get<User[]>(this.serviceUrl, {
      params: new HttpParams()
        .set('pageIndex', pageIndex.toString())
        .set('pageSize', pageSize.toString())
   });
}  

2.3. Modify the data source class:
export class UserDataSource implements DataSource<User> {
   // add variables to hold the data and number of total records retrieved asynchronously
   // BehaviourSubject type is used for this purpose
   private usersSubject = new BehaviorSubject<User[]>([]);

   // to show the total number of records
   private countSubject = new BehaviorSubject<number>(0);
   public counter$ = this.countSubject.asObservable();

   constructor(private userService: UserService) {
  
   }
  
   loadUsers(pageIndex: number, pageSize: number) {
  
      // use pipe operator to chain functions with Observable type
      this.userService.getUser(pageIndex, pageSize)
      .pipe(
         catchError(() => of([])),
         finalize()
      )
      // subscribe method to receive Observable type data when it is ready
      .subscribe((result : any) => {
         this.usersSubject.next(result.data);
         this.countSubject.next(result.total);
        }
      );
   }
  
   connect(collectionViewer: CollectionViewer): Observable<User[]> {
      console.log("Connecting data source");
      return this.usersSubject.asObservable();
   }

   disconnect(collectionViewer: CollectionViewer): void {
      this.usersSubject.complete();
      this.countSubject.complete();
   }
}

2.4. Modify component class:
// import ViewChild, MatPaginator and MatTableDataSource
import { ViewChild } from '@angular/core';
import { MatPaginator, MatTableDataSource } from '@angular/material';

export class AppComponent implements AfterViewInit, OnInit{

   . . .

   @ViewChild(MatPaginator) paginator: MatPaginator;

   ngAfterViewInit() {

      this.dataSource.counter$
      .pipe(
         tap((count) => {
            this.paginator.length = count;
         })
      )
      .subscribe();

      // when paginator event is invoked, retrieve the related data
      this.paginator.page
      .pipe(
         tap(() => this.dataSource.loadUsers(this.paginator.pageIndex, this.paginator.pageSize))
      )
      .subscribe();
   }  

   ngOnInit() { 
      // set paginator page size
      this.paginator.pageSize = 100;

      this.dataSource = new UserDataSource(this.userService);
      this.dataSource.loadUsers(this.paginator.pageIndex, this.paginator.pageSize);  
   }
}

For the server side api, we need to use a class that can hold data and records count like:
public class PagingResult<T>
{
   public IEnumerable<T> Data { get; set; }

   public int Total { get; set; }
}

Then the codes to retrieve data will look something like:
return new PagingResult<Student>()
{
   Data = query.Skip(pageIndex * pageSize).Take(pageSize).ToList(),
   Total = query.Count()
};

Once the paging works, we can move to the sorting functionality.

3. Add sorting functionality
3.1. Import sorting module to the app module
import { MatSortModule } from '@angular/material/sort;
@NgModule({
   . . .,
   imports: [
      . . .
      MatSortModule
   ],
   . . .
})

3.2. Add sorting element to the table and headers on HTML template:
<table mat-table [dataSource]="dataSource" matSort matSortDisableClear>

. . .

<th mat-header-cell *matHeaderCellDef mat-sort-header> Student Id </th>

<th mat-header-cell *matHeaderCellDef mat-sort-header> First Name </th>

3.3 Modify the component class:
import { MatSort } from '@angular/material';

. . .

export class AppComponent implements AfterViewInit, OnInit{
   . . .
  
   @ViewChild(MatSort) sort: MatSort;
   ngAfterViewInit() {
      . . .

      merge(this.paginator.page, this.sort.sortChange)
      .pipe(
         tap(() => this.dataSource.loadUsers(this.paginator.pageIndex, this.paginator.pageSize, this.sort.active, this.sort.direction))
      )
      .subscribe();
   }

   . . .

   ngOnInit() {
      this.paginator.pageSize = 100;
      this.sort.active = 'firstName';
      this.sort.direction = 'asc';

      this.dataSource = new UserDataSource(this.userService);
      this.dataSource.loadUsers(this.paginator.pageIndex, this.paginator.pageSize, 'firstName', 'asc');
   }
}

Reference:
Angular Material Data Table: A Complete Example (Server Pagination, Filtering, Sorting)

Friday, 16 November 2018

Quick Steps Creating Web API in ASP.NET Core 2.1 with Entity Framework

These are steps of how to quickly create Web API service in ASP.NET Core 2.1 with Entity Framework for an existing database (you will need to design in different projects and layers for a proper solution):

1. Create a new ASP.NET Core Web Application project

2. Select Web API for template

3. Install the following packages from Nuget or Package Manager console:
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Tools
Microsoft.VisualStudio.Web.CodeGeneration.Design

4. On Package Manager console, run:
Scaffold-DbContext “Server=your_server_name;Database=your_db_name;Trusted_Connection=True;” Microsoft.EntityFrameworkCore.SqlServer -OutputDir Model
This will create DbContext instance and all the model classes for tables in database.

5. Open Startup.cs and comment out the whole OnConfiguring(DbContextOptionsBuilder optionsBuilder) method

6. On ConfigureServices(IServiceCollection services) method, add:
public void ConfigureServices(IServiceCollection services)
{
   services.AddMvc();
   var connection = @"Server=server_name;Database=db_name;Trusted_Connection=True;ConnectRetryCount=0";
   services.AddDbContext<MyDBContext>(options => options.UseSqlServer(connection));
}
7. Create a controller by right clicking Controller folder

8. Choose ‘API Controller with actions, using Entity Framework’

9. Select a model class which you would like to use


For allowing CORS, put these codes on Startup.cs:
services.AddCors(options => options.AddPolicy("AllowAll", p => p.AllowAnyOrigin()
                                                                      .AllowAnyMethod()
                                                                      .AllowAnyHeader()));

Wednesday, 31 October 2018

Adding Sorting Functionality to Angular Material Table

Below are the steps of how to add sorting feature of pre-fetched data to Angular Material 6 Table:

- on html page, add matSort to the table and mat-sort-header to each column that we would like to enable:
<table mat-table . . . matSort>
    <ng-container matColumnDef="name">
      <th mat-header-cell *matHeaderCellDef mat-sort-header> Name </th>
      <td mat-cell *matCellDef="let element"> {{element.name}} </td>
    </ng-container>
    <ng-container matColumnDef="weight">
      <th mat-header-cell *matHeaderCellDef mat-sort-header> Weight </th>
      <td mat-cell *matCellDef="let element"> {{element.weight}} </td>
    </ng-container>
    <ng-container matColumnDef="symbol">
      <th mat-header-cell *matHeaderCellDef mat-sort-header> Symbol </th>
      <td mat-cell *matCellDef="let element"> {{element.symbol}} </td>
    </ng-container>
. . .
</table>

- on module page (e.g., module.ts):
import { MatSortModule } from '@angular/material/sort;
@NgModule({
  . . .,
  imports: [
    . . .
    MatSortModule
  ],
. . .
})

- on component page (e.g., component.ts):
import { MatSort } from '@angular/material';
export class AppComponent implements AfterViewInit, OnInit{
  . . .
 
  @ViewChild(MatSort) sort: MatSort;
 
 
  ngAfterViewInit() {
    . . .
    this.dataSource.sort = this.sort;
  }
 
  . . .
}

I will create a post of how to do paging and sorting with server-side data interaction soon.

Friday, 5 October 2018

Attempt to use Visual Studio 2017 for Older Apache Cordova App

I have an Ionic 1 with Apache Cordova built using Visual Studio 2015. As time goes by, I couldn't upload package to Apple Store and Google Play anymore as they were asking more recent iOS and Android versions supported in the package. Then thinking to target more recent mobile phone OS, I decided to try Visual Studio 2017 on the same machine. After installing and trying to build, I got some errors.

First error I got is:
Could not resolve com.android.tools.build:gradle:2.1.0. 
Could not get resource 'https://repo1.maven.org/maven2/com/android/tools/build/gradle/2.1.0/gradle-2.1.0.pom'.
Could not HEAD 'https://repo1.maven.org/maven2/com/android/tools/build/gradle/2.1.0/gradle-2.1.0.pom'.
Could not get resource 'https://jcenter.bintray.com/com/android/tools/build/gradle/2.1.0/gradle-2.1.0.pom'.
Could not HEAD 'https://jcenter.bintray.com/com/android/tools/build/gradle/2.1.0/gradle-2.1.0.pom'.

After some googling, it seemed that I had issue with contacting the target servers using HTTPS. I changed these lines on platforms\adroid\build.gradle file:
buildscript {
    repositories {  
        //mavenCentral()
        //jcenter()
        // change to use HTTP explicitly
        jcenter {
   url "http://jcenter.bintray.com/"
  }
    }
    . . .
}

. . .

allprojects {
    repositories {
        //mavenCentral()
        //jcenter()
        // change to use HTTP explicitly
        jcenter {
   url "http://jcenter.bintray.com/"
  }  
    }
}
Also on platforms\adroid\CordovaLib\build.gradle:
buildscript {
    repositories {
        //mavenCentral()
        // change to use HTTP explicitly 
        maven { url 'http://repo1.maven.org/maven2' }
        jcenter {
   url "http://jcenter.bintray.com/"
  }
    }
    . . .
}

Then I found another issue:
cordova-build error : java.lang.UnsupportedClassVersionError: com/android/dx/command/Main : Unsupported major.minor version 52.0

This was fixed by updating the project to use the latest Java installed. Go to Tools -> Options -> Tools for Apache Cordova -> Environment Variable Overrides, then change the JAVA_HOME folder.

Then I tried to run Google Emulator and it was still using the old AVD that had been installed previously. And when I checked config.xml file, VS2017 only supports Cordova 6.3.1 and Global Cordova 7.0.1 by default. I was expecting it supports a more recent version of Cordova that supports the recent versions of Android and iOS. This is the main reason I tried to upgrade to catch up with recent version of Android and iOS in the market. Seeing so many hassles and no update from Visual Studio Tool for Apache Cordova team for almost two years, I think I will try to upgrade my app using Cordova CLI itself.

Friday, 21 September 2018

Adding Paginator to Angular Material Table

To add paging feature of pre-fetched data to Angular Material 6 Table:
- on html page add mat-paginator to the bottom (or top) of the table:
<mat-paginator [pageSizeOptions]="[2, 5, 10]"></mat-paginator>

- on module page (e.g., module.ts):
//import MatPaginatorModule
import { MatPaginatorModule } from '@angular/material/paginator';
@NgModule({
  . . .,
  imports: [
    . . .
    MatPaginatorModule
  ],
. . .
})

- on component page (e.g., component.ts):
// import ViewChild, MatPaginator and MatTableDataSource
import { ViewChild } from '@angular/core';
import { MatPaginator, MatTableDataSource } from '@angular/material';

export class AppComponent implements AfterViewInit, OnInit{

  . . .

  dataSource : MatTableDataSource<MyDataModel>;

  @ViewChild(MatPaginator) paginator: MatPaginator;

  ngAfterViewInit() {
    // set datasource paginator
    this.dataSource.paginator = this.paginator;
  }

  ngOnInit() {
    // initialise the datasource
    this.dataSource = new MatTableDataSource<MyDataModel>(MY_DATA);
  }
}

I will create a post of how to do paging and sorting with server-side data interaction soon.


Tuesday, 4 September 2018

How to Add Angular Material Table

Quick reference of how to add Angular Material Table (Angular Material 6):
- on module page (e.g., module.ts):
// import the module
import { MatTableModule } from '@angular/material';
@NgModule({
  . . .,
  imports: [
    . . .
    MatTableModule
  ],
 . . .
})

- on component page (e.g., component.ts):
export class AppComponent implements AfterViewInit, OnInit{
  . . .
// specify columns to be displayed
  displayedColumns : string[] = ['columnone', 'columntwo'];

// specify data to be displayed
  dataSource = [{columnone: 1, columntwo: ‘A’},{columnone: 2, columntwo: ‘B’},…]
  . . .
}

- on html page:
<table mat-table [dataSource]="dataSource">
  <ng-container matColumnDef="columnone">
    <th mat-header-cell *matHeaderCellDef> ColumnOne </th>
    <td mat-cell *matCellDef="let elem"> {{elem.columnone}} </td>
  </ng-container>

  <ng-container matColumnDef="columntwo">
    <th mat-header-cell *matHeaderCellDef> ColumnTwo </th>
    <td mat-cell *matCellDef="let elem"> {{elem.columntwo}} </td>
  </ng-container>

  <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
  <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>

We will see how to add Angular Material Paginator on the next post.

Tuesday, 28 August 2018

Installing Angular Material 6

At the time of writing, Angular Material version is 6.4.6. It needs Angular version 6, so we need to make sure we have the recent version. Below are NPM command lines to install latest version of Angular, Angular Material and packages that it requires:
// make sure to remove older version of Angular first
npm uninstall -g @angular/cli
npm uninstall -g angular-cli

// clean the cache and verify
npm cache clean
npm cache verify

// installing the packages
npm install -g @angular/cli
// Yarn package manager is required by Angular CLI
npm install -g yarn

// check the version
ng –version

// create a new Angular application
ng new angularmaterial

cd angularmaterial

// serve the application and show in browser 
ng serve

// install Angular Material and required Angular CDK
npm install --save @angular/material @angular/cdk

The application shown on the browser should automatically refresh every time you make changes to the codes. If it is not working try to put some polling interval, like:
ng serve --poll=2000

Wednesday, 18 July 2018

Node.js and Proxy Server Issue

Tried to build a new web project in Visual Studio 2017. However it was unsuccessful and I saw the required packages were missing. I tried to do ‘Restore Packages’ again many times but still not good.


Then I tried to check whether Node.js has been installed properly. Found out, nothing wrong with it. I suspected this is because of something wrong with the proxy setting as this happened in a corporate environment.

To see proxy server setting in Node.js, I ran:
npm config get proxy
Turned out that the password used is my old password that was used when I installed Visual Studio. So I updated the setting with:
npm config set proxy http://[DOMAIN]%5C[USERNAME]:[PASSWORD]@[SERVER-ADDRESS]:[PORT-NUMBER]
Then I tested the connectivity to Node.js registry server with:
npm ping
The result displayed was:
Ping error: Error: self signed certificate in certificate chain
npm ERR! code SELF_SIGNED_CERT_IN_CHAIN
npm ERR! self signed certificate in certificate chain
The error indicates there is something wrong with SSL connection. When I checked the ‘strict-ssl’ and ‘registry’ values with ‘npm config list -l’ command, they were ‘true’ and ‘https://registry.npmjs.org/’. So I ran these commands to change the connection to use non secured connection:
npm config set strict-ssl false
npm config set registry http://registry.npmjs.org/
Tried ‘npm ping’ again and the result was ‘Ping success: {}’.

Now, I could restore the missing packages in Visual Studio.

Tuesday, 2 January 2018

Single Launch Screen for iOS

When building a project for iOS in Visual Studio Tools for Apache Cordova, I noticed that the launch or splash screen was always shown in iOS devices. This is because iOS always needs a launch/splash screen and this is cannot be removed. So I read some documentation and found out that we can use a single image to be applied to all kind of iOS devices.
All we need to do is add this setting in the project's config.xml file:
<splash src="res/screen/ios/Default@2x~universal~anyany.png" />
I used the same name suggested. It may work if we use other file name but I haven't tested it. For the resolution and layout, I tried to follow the default splash.png file in the resources folder. I made my main image in the center and leave plenty amount of spaces around it, knowing that the image will be cropped significantly in some smaller resolution iOS devices. The image size I used is the same as the default image size, which is 2208 x 2208 pixels.

Reference:
https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-splashscreen/