Thursday, 8 August 2019

Commands to Check Ionic, Cordova and Plugin Version

Cordova
see installed version
cordova -v
or
cordova --version
see the latest version available
npm info cordova
to upgrade to latest version
npm update -g cordova
-g means install globally

otherwise need to uninstall and reinstall
npm uninstall -g cordova
npm install -g cordova

Ionic CLI

installed version
ionic -v
or
ionic --version
latest version available
npm info ionic
to upgrade to latest version
npm update -g ionic
-g means install globally

otherwise need to uninstall and reinstall
npm uninstall -g ionic
npm install -g ionic

Plugin of an Cordova app
installed version
npm list thePluginName
latest version available
npm info thePluginName
to upgrade to latest version
npm update thePluginName

otherwise need to uninstall and reinstall

or you can try to use cordova-check-plugins plugin
to add to project
cordova plugin add thePluginName
or
ionic cordova plugin add thePluginName
to remove from project
cordova plugin remove thePluginName
or
ionic cordova plugin remove thePluginName

You can also use
ionic info
command in an App directory to check Ionic CLI, Ionic Framework, Cordova CLI, Platforms, Plugins installed and other information.

Monday, 29 July 2019

ASP.NET Core Client Server JWT Authentication

Recently, I was trying to play around with ASP.NET Core JWT Authentication with Web API as backend server and Angular as front end client. I used ASP.NET Core 2.1 version.

Web API server setup
1. Add these codes inside ConfigureService method on Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
 services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

 services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
  .AddJwtBearer(options =>
  {
   options.TokenValidationParameters = new TokenValidationParameters
   {
    ValidateIssuer = true,
    ValidateAudience = true,
    ValidateLifetime = true,
    ValidateIssuerSigningKey = true,

    ValidIssuer = "ABC",
    ValidAudience = "DEF",
    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("mySuperSecretKey"))
   };
  });

 services.AddCors(options =>
 {
  options.AddPolicy("EnableCORS", builder =>
  {
   builder.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod().AllowCredentials().Build();
  });
 });
}
Notice that we also need to enable Cross Origin Resource Sharing (CORS) as our Angular client will sit on different domain.

2. Then inside Configure method, add:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
 if (env.IsDevelopment())
 {
  app.UseDeveloperExceptionPage();
 }
 else
 {
  app.UseHsts();
 }

 app.UseAuthentication();

 app.UseCors("EnableCORS");

 app.UseHttpsRedirection();
 app.UseMvc();
}
Make sure it is before app.UseMvc(); line otherwise you will keep getting ‘401 Unauthorized’ message with no details. Also we need to add CORS setting that we have done.

3. On the Web API login method on controller:
[EnableCors("EnableCORS")]
[HttpPost, Route("login")]
public IActionResult Login([FromBody]LoginModel user)
{
 if (user == null)
 {
  return BadRequest("Invalid client request");
 }

 if (user.UserName == "user" && user.Password == "password")
 {
  var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("mySuperSecretKey "));
  var signinCredentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256);

  var tokeOptions = new JwtSecurityToken(
   issuer: "ABC",
   audience: "DEF",
   claims: new List<Claim>(),
   expires: DateTime.Now.AddMinutes(5),
   signingCredentials: signinCredentials
  );

  var tokenString = new JwtSecurityTokenHandler().WriteToken(tokeOptions);
  return Ok(new { Token = tokenString });
 }
 else
 {
  return Unauthorized();
 }
}
Make sure [EnableCors("EnableCORS")] is added either to the method or at controller level.

4. On our Angular client we will need to call login:
login(form: NgForm) {
    let credentials = JSON.stringify(form.value);
    this.http.post("http://localhost:5000/api/auth/login", credentials, {
      headers: new HttpHeaders({
        "Content-Type": "application/json"
      })
    }).subscribe(response => {
      let token = (<any>response).token;
      localStorage.setItem("jwt", token);
      this.invalidLogin = false;
      this.router.navigate(["/"]);
    }, err => {
      this.invalidLogin = true;
    });
}
Once authenticated, we use local storage to store the token with ‘jwt’ key.

5. Then on our secured resource controller:
[EnableCors("EnableCORS")]
[HttpGet, Authorize]
public IEnumerable<string> Get()
{
 return new string[] { "confidential one", " confidential two" };
}

6. To request that API, we simply use this on client side:
let token = localStorage.getItem("jwt");
this.http.get("http://localhost:5000/api/customer", {
  headers: new HttpHeaders({
 "Authorization": "Bearer " + token
  })
}).subscribe(response => console.log(response)); 

Note that we need to have "Authorization": "Bearer " + token in the request header so that the server can authorize it.  

Friday, 21 June 2019

Should I Change My App's WebSQL Database to Other?

Recently I have been doing app upgrade due to requirements by Google Play and Apple App Store. I have been doing some research about the latest technologies for the app. I tried to find out more about latest database technology and was trying to figure out whether I should change my WebSQL database.

These are the findings that I had in bullet points:
  • It is mentioned that WebSQL has been deprecated but is still supported by Android and iOS. They do not have any plan to remove it anytime in the future.
  • The SQLite alternative is seemed to be supported by individuals/small group as well as those who developed WebSQL. There is no clarity about the support and future as well.
  • IndexedDB seems good but is not supported by iOS
  • Some opinions said that WebSQL is deprecated simply because it does not fulfill a standard for client side storage but the standard does not really exist and not accepted by all parties.
  • SQLite also does not fullfil the standard
Since there is no alternative and clarity about this and major operating systems have no plan to remove WebSQL in the future, I think it is better to keep using WebSQL in my upgraded app.


References:
https://softwareengineering.stackexchange.com/questions/220254/why-is-web-sql-database-deprecated
https://www.reddit.com/r/SQL/comments/8woehg/sqlite_being_deprecatedreplaced_as_database/
https://cordova.apache.org/docs/en/latest/cordova/storage/storage.html

Friday, 26 April 2019

Angular Directive Example to Format Value to Currency

The following is an example of an Angular 6 directive that format input value to currency when losing focus. It uses NgModel for value binding and CurrencyPipe for the formatter.
import { Directive } from '@angular/core';
import { NgModel } from '@angular/forms';
import { CurrencyPipe } from '@angular/common';

@Directive({
  selector: '[ngModel][my-directive]',
  providers: [NgModel, CurrencyPipe],
  host: {
    '(blur)': 'onInputChange($event)'
  } 
})
export class MyDirective {

  constructor(private model: NgModel, private currencyPipe: CurrencyPipe) { }

  onInputChange($event) {
    var value = $event.target.value;
    if (!value) return;

    var plainNumber: number;
    var formattedValue: string;


    var decimalSeparatorIndex = value.lastIndexOf('.'); 
    if (decimalSeparatorIndex > 0) {
      // if input has decimal part
      var wholeNumberPart = value.substring(0, decimalSeparatorIndex);
      var decimalPart = value.substr(decimalSeparatorIndex + 1);
      plainNumber = parseFloat(wholeNumberPart.replace(/[^\d]/g, '') + '.' + decimalPart)
    } else {
      // input does not have decimal part
      plainNumber = parseFloat(value.replace(/[^\d]/g, ''));
    }

    if (!plainNumber) {
      formattedValue = '';
    }
    else {
      formattedValue = this.currencyPipe.transform(plainNumber.toFixed(2), "USD", "symbol-narrow");
    }

    this.model.valueAccessor.writeValue(formattedValue);
  }
}

Then to use it on HTML part:
<input name="productPrice" [(ngModel)]="price" my-directive />

Friday, 5 April 2019

Angular Directive Example to Allow Certain Values

Below is an example of an Angular 6 Directive. This directive detects changes in NgModel as user enters in input value and only allows specific value to be entered while rejecting the others.
import { Directive } from '@angular/core';
import { NgModel } from '@angular/forms';

@Directive({
  selector: '[ngModel][my-directive]',
  providers: [NgModel],
  host: {
    '(ngModelChange)': 'onInputChange($event)'
  } 
})
export class MyDirective {

  constructor(private model: NgModel) { }

  onInputChange(value) {
    console.log(value);
    
    this.model.valueAccessor.writeValue(value.replace(/[^\d\.\,\s]+/g, ''));
  }
}

Then to use it on HTML part:
<input name="productPrice" [(ngModel)]="price" my-directive />

Friday, 22 March 2019

Angular Form, ngModel and Detecting Changes

Angular (at the moment of writing is version 8) has two flavours when coming down to forms. We can use Template Driven or Reactive Form. Template Driven form is useful for simple form that does not need much customisation. It is similar to the form in AngularJS. Angular will create form model representation and data binding in the background with some default structure and names. That is why it is called Template Driven.

Reactive Form, on the other hand is much more flexible however requires more effort in setting up. This form uses more code in component and less in HTML. Reactive form is best to handle complex scenarios and allow easier unit testing.

We are going to see an example of Template Driven form with ngModel binding.
<form name="personNameForm" novalidate="" ng-submit="submitForm()">
 <div>
  <input type="text" name="firstName" class="textinput"
                 [(ngModel)]="person.firstName"
                 (ngModelChange)="combineNames()"
                 required />
  <input type="text" name="lastName" class="textinput"
                 [(ngModel)]="person.lastName"
                 (ngModelChange)="combineNames()"
                 required />
 </div>
</form>
Note that we need to put form's name attribute and for each ngModel field a name attribute is also required.
We use ngModelChange directive to detect changes for NgModel value. $watch and $observe are not required anymore.

Monday, 14 January 2019

My Notes of Switching from Visual Studio to Ionic CLI

Recently, I tried to update my app in Google Play and Apple Store but failed to do so because they mentioned that I need to use later version of Android or iOS. I have been using Visual Studio 2013 then Visual Studio 2016 with Tools for Apache Cordova however when checking the latest supported Android and iOS versions, they have not progressed much in the last two years. After some research, I decided to use Apache Cordova directly, together with Ionic, Angular and Node.js. Apache Cordova and Ionic are still progressing and very much alive. I still use Visual Studio 2017 though for code editor only. All project configurations, simulator settings and others will be done at lower level, using Apache Cordova directly. As my app was written in JavaScript and HTML using Ionic 1 so I think it is a good choice.

The notes below are my journey to move from Visual Studio Tool for Apache Cordova to the CLI option. I use same machine to install the required new components. Hopefully it can help anyone who chooses a similar path as mine.


SETTING UP FRAMEWORKS
First I had Node.js, recent Cordova and Ionic installed. Once they were installed I tried to create a new test app by running:
ionic start myApp sidemenu
then it will show:
+ ionic@4.2.1
added 242 packages from 151 contributors in 43.756s
? Integrate your new app with Cordova to target native iOS and Android?
Chose yes and continued with the installation.
After installation:
cd myApp
ionic serve
I made sure the app could be compiled successfully and showed in a browser.

Then I added Android platform:
ionic cordova platform add android
To check the installed version run:
cordova platform version android
Then it showed:
7.1.1 is the latest version


SETTING UP EMULATOR
Next, I tried to set up the emulator with latest version of Android. But first I wanted to make sure that I could run an existing virtual device from AVD Manager.

When trying to run one, I got this error:
“Could not find an installed version of Gradle either in Android Studio,
or on your system to install the gradle wrapper. Please include gradle
in your path, or install Android Studio
[ERROR] An error occurred while running subprocess cordova.”
I installed Gradle from the website and set an environment variable for it.

The second error I got after trying to run the emulator:
“Could not unzip C:\Users\rical\.gradle\wrapper\dists\gradle-4.1-all\bzyivzo6n839fup2jbap0tjew\gradle-4.1-all.zip to C:\Users\rical\.gradle\wrapper\dists\gradle-4.1-all\bzyivzo6n839fup2jbap0tjew.
Reason: error in opening zip file
Exception in thread "main" java.util.zip.ZipException: error in opening zip file”
I deleted the zip folder and run the command again.

Then I got another error:
“Error occurred during initialization of VM
Could not reserve enough space for 2097152KB object heap”
I went to Start -> Control Panel -> System -> Advanced(tab) -> Environment Variables -> System Variables and add new variable:
Variable name: _JAVA_OPTIONS
Variable value: -Xmx512M

After trying to run the emulator again, another error shown up:
“A problem occurred configuring project ':CordovaLib'.
> You have not accepted the license agreements of the following SDK components:
[Android SDK Platform 27, Android SDK Build-Tools 26.0.2].
Before building your project, you need to accept the license agreements and complete the installation of the missing components using the Android Studio SDK Manager.”
Went to SDK Manager and install SDK Platform 27 and Android SDK Build-Tools 26.0.2.

The emulator is working now, but I received a warning:
“Running an x86 based Android Virtual Device (AVD) is 10x faster. We strongly recommend creating a new AVD.”
Solved this by installing Intel x86 Atom_64 or Intel x86 Atom and setting the emulator (AVD) to use one of them.
I received another error “PANIC: Cannot find AVD system path. Please define ANDROID_SDK_ROOT”. I created a new environment variable called ANDROID_SDK_ROOT that has my SDK path, something like "C:\Program Files (x86)\Android\android-sdk". Then restarted the machine.

I wanted to use recent version of Android which is version 8 or 9 but after installing the system images, I got this error:
“This AVD's configuration is missing a kernel file! Please ensure the file "kernel-qemu" is in the same location as your system image”
I found a great article to solve the issue https://www.andreszsogon.com/using-android-8-and-9-emulators-without-android-studio
I followed the instructions:
- download emulator-windows-4848055.zip
- uninstall my current Android 8 and newer system images
- close my Android SDK Manager and AVD Manager tools if open
- extract the contents from the ZIP file into my android-sdk/tools
- download the desired emulator’s System Images from the SDK Manager
- create a new emulator from the AVD Manager
- start a virtual device

Received another error:
“emulator: ERROR: x86 emulation currently requires hardware acceleration!
Please ensure Windows Hypervisor Platform (WHPX) is properly installed and usable.
CPU acceleration status: HAXM is not installed on this machine”
Went to Turn Windows features on and off, checked Windows Hypervisor Platform.

Finally, I could run a virtual device from the AVD Manager.

When I tried to run from command prompt:
ionic cordova emulate --target=My_Android_9_Virtual_Device  android
I got another error:
“A problem occurred configuring project ':CordovaLib'.
> Failed to find Platform SDK with path: platforms;android-27”
I found out that cordova-android 7.1.1 only supports up to Android 8.1 (SDK 27). So I needed to download the SDK Platform and a choice of system image of the newer version.

Finally, the emulator from command prompt is working!

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/

Tuesday, 31 October 2017

Visual Studio Tools for Apache Cordova Remote Build Node.js Version Issue

Recently when I tried to do remote build of my Visual Studio Tools for Apache Cordova application from my Visual Studio 2015 to Mac machine, I had this error message "TACO0204: Error installing taco-remote-multiplexer via npm" on my Visual Studio and "Cannot find module 'internal/fs'" on my Mac machine.

After googling, it seemed that the Node.js on the Mac machine had been upgraded for some reason. I checked the version with these commands:
node -v
I found out that the version is 8.4.0 now.

I needed to remove it and install the compatible version for my Visual Studio. So I deleted the 'npm' folder in '/usr/local/lib/node_modules/' folder by running this:
rm -rf /usr/local/lib/node_modules/npm
Then went to node.js website, downloaded and installed a version that is compatible (i.e.; version 0.12.x is the most compatible).

Thursday, 5 October 2017

Some Visual Studio 2015 Apache Cordova Project iOS Deployment Issues

Recently, I tried to deploy my Visual Studio Tools for Apache Cordova project into iOS mobile device. The project is built in Visual Studio 2015 using Cordova CLI 6.0.0. The Visual Studio is on a Windows machine. I also have a Mac laptop with XCode 8.3. After following the steps on this page https://taco.visualstudio.com/en-us/docs/ios-guide, I ran into some issues.

The first error message I received is "Remotebuild requires your projects to use cordova-ios 4.3.0 or greater with XCode 8.3. Please update your cordova-ios version".
It seemed that the iOS version of the project is less than 4.3.0 and XCode expected the version to be at least 4.3.0.
The solution:
1. on my Windows machine, went to command prompt and installed Cordova; npm install -g cordova
2. changed package.json file in the project to have the later version of iOS:
{
  "android": "5.1.1",
  "ios": "4.3.0"
}
3. then on the command prompt, went to the 'platforms' folder of the project and deleted and recreated the iOS version of the project. I ran the command; cordova platform add ios@4.3.0

After passing that, I got another error, "Severity Code Description Project File Line Suppression State Error Warning developmentTeam is missing from your build.json.".
The solution:
- added some configuration settings on my build.json file based on the information on this link https://cordova.apache.org/docs/en/latest/guide/platforms/ios/#using-flags.
So my build.json had something like this:
{
  "android": {
    . . .
  },
  "ios": {
    "debug": {
      "codeSignIdentity": "iPhone Developer",
      "provisioningProfile": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
      "developmentTeam": "XXXXXXXXXX",
      "packageType": "development"
    },
    "release": { }
  }
}
According to the article, codeSignIdentity should use "iPhone Developer" value for both debug and release mode. provisioningProfile is the profile that I set on Apple Developer website. The filename of the downloaded profile has the same key value. developmentTeam is the team name that is set on Apple Developer website. packageType values are either 'development', 'enterprise', 'ad-hoc', and 'app-store'.

Once this is solved, I got another error:
Severity Code Description Project File Line Suppression State
Error Error: Remote build error from the build server Build failed with error ios-deploy was not found. Please download, build and install version 1.9.0 or greater from https://github.com/phonegap/ios-deploy into your path, or do 'npm install -g ios-deploy' - 1

Solution:
- on my Windows machine I ran; npm install -g ios-deploy.

Then another error:
No certificate matching 'iPhone Development' for team 'XXXXXXXXXX': Select a different signing certificate for CODE_SIGN_IDENTITY, a team that matches your selected certificate, or switch to automatic provisioning.
Code signing is required for product type 'Application' in SDK 'iOS 10.3'

Solution:
- on the Mac computer, I went to Applications > Utilities > KeyChain Access folder and found there were more than one certificates related to the profile downloaded. This was due to a mistake I did earlier when generating provisioning profile. So I deleted the incorrect one.

Finally I got this error:
Failed to launch iOS remote for build C:\myProjectDirectory\bld\ios\Debug\buildInfo.json to http://192.168.1.118:3000/cordova :
Http 404: Error mounting developer disk image
------ Cordova tools 6.0.0 already installed.
Requesting debug on remote iOS device for buildNumber 23139 on server http://192.168.1.118:3000/cordova...
Failed to Debug iOS remote for build C:\myProjectDirectory\bld\ios\Debug\buildInfo.json to http://192.168.1.118:3000/cordova :
Http 500: No devices found to debug. Please ensure that a device is connected and awake and retry.

Solution:
- on my Mac machine I ran; brew upgrade libimobiledevice --HEAD


References and further details:
https://github.com/Microsoft/remotebuild/issues/5
https://stackoverflow.com/questions/43944273/apache-cordova-visual-studio-2015-xcode-8-3-cannot-remotebuild

Thursday, 15 June 2017

Open Link on Browser in Ionic Framework

To be able to open a link in an Ionic Framework based app, we need to install InAppBrowser plugin. If you use Visual Studio Tools for Apache Cordova, you can open config.xml file and find in Plugins section.

After installing the plugin, we don’t need to pass any new module in the code function constructor. All we need to do is just to call the functions directly like:
cordova.InAppBrowser.open('http://www.google.com', '_system');
// or we can use
window.open('http://www.google.com', '_system');
_system target is used so that the link will be opened on system's web browser.

In HTML code, we can call like this:
<a href="#" onclick="window.open('https://www.google.com', '_system');">my link</a>
Don’t forget to include the ‘http://’ otherwise you will get an error like ‘Cannot display PDF (… cannot be opened).

Thursday, 9 February 2017

Ionic Modal with this Controller

Below is a simple example of using Ionic Modal with this controller (Controller As):
var vm = this;
    . . .
    . . .
    . . .

    /* modal */
    vm.showModal = function () {
        $ionicModal.show();
    };

    $ionicModal.fromTemplateUrl('my-modal.html', {
        scope: $scope,
        animation: 'slide-in-up'
    }).then(function (modal) {
        vm.modal = modal;
    });

    vm.openModal = function () {
        vm.modal.show();
    };

    vm.closeModal = function () {
        vm.modal.hide();
    };

    // Clean up the modal
    $scope.$on('$destroy', function () {
        vm.modal.remove();
    });

    // Execute action on hide modal
    $scope.$on('modal.hidden', function () {
        . . .
    });

    // Execute action on remove modal
    $scope.$on('modal.removed', function () {
        . . .
    });
Note that we still need to use $scope for particular function.

The modal template:
<ion-modal-view>
    <ion-header-bar>
        <h1 class="title">My Modal title</h1>
    </ion-header-bar>
    <ion-content>
        Hello!
        <button ng-click="vm.closeModal()">Close</button>
    </ion-content>
</ion-modal-view>