Showing posts with label ionic 2. Show all posts
Showing posts with label ionic 2. Show all posts

Friday, October 28, 2016

Simple starter kit for building realistic app with Ionic 2

At the point I'm writing this article, Ionic 2 released its RC1 and is going to official release. This article is a continue of my series: Learn Ionic 2 with Authentication by JWT, Ionic 2: FormBuilder and Validator, Build JWT authentication server with Node.js, Express and MySQL. It put all these articles into a Todos System with 2 parts:
  • Server: an enhancement of previous server in Build JWT authentication server with Node.js, Express and MySQL by adding new todos API allowing to get, create new and delete todos based on user role authentication. You can clone / download the source code of this server on my GitHub: https://github.com/vnheros/nodejs-mysql-jwt-auth
  • App: a todo app which allows user login or sign up new account. After login successfully, it will displays a list of public todos or todos of user. User can add new todo or delete an existing todo. You can clone / download the source code of this server on my GitHub (tested with Ionic 2 RC1): https://github.com/vnheros/ionic2-todos-example
I think this is a simple starter kit for you starting your real apps with Ionic 2. Below are important things in this starter kit which I didn't mention in my GitHub.

1. My SQL DB
There are 2 tables: users and todos having script as below:

CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(20) NOT NULL,
  `password` varchar(20) NOT NULL,
  `email` varchar(50) NOT NULL,
  `role` varchar(20) DEFAULT 'Regitered',
  PRIMARY KEY (`id`,`username`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;


CREATE TABLE `todos` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `task` varchar(150) NOT NULL,
  `deadline` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `public` tinyint(1) NOT NULL DEFAULT '0',
  `completed` tinyint(1) DEFAULT '0',
  `username` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;


2. Design of todos app
Below are screens of this app:




The Welcome page containing Ionic login will appear first, then it will check if user is logged in or not by checking id_token stored in local storage. If user logged in, it will move to Todos List page which presents public todos and todos of the user. If user is not logged in yet, it will navigate to Todos Login page for signing in or signing up. Todos List has 2 buttons, one on top for logging out and one in bottom for adding new todo.

3. Step to generate source code for the app
For Ionic 2 RC1, you should install its app-script to avoid some issues when running the app (read this post for more info).

Install @ionic/app-scripts and update your ionic 2:
#npm install @ionic/app-scripts@latest --save-dev
#npm install -g ionic


Generate providers then replace by the source code from my GitHub:
#ionic g provider Auth
#ionic g provider Todos

Generate pages then replace by the source code from my GitHub:
#ionic g page Welcome
#ionic g page Login
#ionic g page Signup
#ionic g page NewTodo

Add folder helpers then add file jwt-helper.ts (used to parse JWT in the app), add folder validators and add file password.ts (used to validate a password input when signing up). You can copy them from my GitHub.

From Ionic RCx, you will need to add pages and providers into app/app.module.ts to use them in your app. See my file on GitHub for example.

There are some another things in the source code which you can discover easily in the source code.
That's for to day and wish you have fun Halloween. Remember turn on the light.
Any comment is welcome.






Tuesday, July 12, 2016

Ionic 2: FormBuilder and Validator

In the article "Learn Ionic 2 with Authentication by JWT", you may know how to make a login page and navigate between pages after login.

In this article, we are going to improve the Signup form of Login page with FormBuilder and Validation which are strong weapons of Angular 2.

1. Add an URL to check existing user in the authentication server
Open the file user-routes.js in the authentication server, add new function to check a username if existing:
app.post('/check', function(req, res) {
  var userScheme = getUserScheme(req);
  if (!userScheme.username) {
    return res.status(400).send("You must send a username");
  }

  if (_.find(users, userScheme.userSearch)) {
   return res.status(400).send("A user with that username already exists");
  }

  res.status(201).send({username: "OK"});
});


Then start the server by using the command:
#node server.js

2. Mofidy file app/providers/auth-service/auth-service.ts
Open this file and add new member and method (function) to check a user as the following:
CHECK_URL = "http://localhost:3001/check";
check(user) {
        return new Promise((resolve, reject) => {
            this.http.post(this.CHECK_URL, JSON.stringify({username: user}), { headers: this.contentHeader })
                .map(res => res.json())
                .subscribe(
                    data => {
                        resolve(data)
                    },
                    err => {
                        reject(err)
                    }
                );
        });
}


3. Add new file app/validators/password.ts
It is used to validate the password input in Signup form. This file has content as below:
import {Control} from '@angular/common';
export class PasswordValidator {
    static checkPassword(control: Control) {
        // password must be between 4 and 10 characters and have at least 1 number
        if (control.value.match(/^(?=.*[0-9])[a-zA-Z0-9!@#$%^&*]{4,10}$/)) {
            return null;
        } else {
            return { 'invalidPassword': true };
        }
    }
}


4. Modify app/pages/login/login.ts
This file is modified as below:
import {Page, NavController} from 'ionic-angular';
import {FormBuilder, ControlGroup, Validators } from '@angular/common';
import {AuthService} from '../../providers/auth-service/auth-service';
import {WorkPage} from '../work/work';

import {PasswordValidator} from  '../../validators/password';
@Page({
  templateUrl: 'build/pages/login/login.html',
})
export class LoginPage {
    authType: string = "login";
    usernameIsValid: boolean = false;
   
   
constructor(private auth: AuthService, private nav: NavController, private formBuilder: FormBuilder) {
        this.signupCreds = formBuilder.group({
          'username': ['', Validators.compose([Validators.required, Validators.pattern('[a-zA-Z]*')])],
          'password': ['', PasswordValidator.checkPassword],
          'email': ['', Validators.required]
        });
    }


    checkUsername(username){
        if(username.length < 4) { //username must have at least 4 characters
          this.usernameIsValid = false;
          return;
        }
        this.auth.check(username.toLowerCase()).then(
          (success) => {
            this.usernameIsValid = true;
          },
          (err) => {
            this.usernameIsValid = false;
          }
        );
    }

   
    login(credentials) {
        this.auth.login(credentials).then(
          (success) => {
            this.nav.setRoot(WorkPage);
          },
          (err) => console.log(err)
        );
    }
   
    signup(credentials) {
        this.auth.signup(credentials).then(
          (success) => {
            this.nav.setRoot(WorkPage);
          },
          (err) => console.log(err)
        );
    }
}


The new BLUE codes are added / modified. Pay your attention on the constructor which I use FormBuilder to created signupCreds form and bind its controls to Validators. As you can see the password control use PasswordValidator.checkPassword to validate input password.

4. Modify app/pages/login/login.html
Let modify the signup form as the following:
<form *ngSwitchWhen="'signup'" [ngFormModel]="signupCreds" (ngSubmit)="signup(signupCreds.value)">
        <ion-item>
          <ion-label>Username</ion-label>
          <ion-input (change)="checkUsername(signupCreds.controls.username.value)" type="text" ngControl="username"></ion-input>
        </ion-item>
        <p *ngIf="signupCreds.controls.username.hasError('required') && signupCreds.controls.username.touched" danger>Username is required</p>
        <p *ngIf="signupCreds.controls.username.touched && !usernameIsValid" danger>User is invalid or taken</p>
        <p *ngIf="signupCreds.controls.username.touched && usernameIsValid" secondary> User is OK</p>
       
        <ion-item>
          <ion-label>Password</ion-label>
          <ion-input type="password" ngControl="password"></ion-input>
        </ion-item>
        <p *ngIf="!signupCreds.controls.password.valid && signupCreds.controls.password.touched" danger>Password is not meet standards</p>
        <p *ngIf="signupCreds.controls.password.valid && signupCreds.controls.username.touched" secondary> Password is good</p>


        <ion-item>
          <ion-label>Email</ion-label>
          <ion-input type="email" placeholder="Your email" ngControl="email"></ion-input>
        </ion-item>
        <p *ngIf="signupCreds.controls.email.hasError('required') && signupCreds.controls.email.touched" danger>Email is required</p>
       
        <div padding>
          <button block [disabled]="!usernameIsValid || !signupCreds.controls.password.valid || !signupCreds.controls.email.valid" type="submit">Signup</button>
        </div>
</form>


While email uses a standard Validator and password uses a customized Validator, username uses event change of the control to validate an input username. This will help to reduce number of calls to the authentication server to check the input username. If not, it will call the authentication server when keying every character in Username box.

Beside that, in this new form, you can not click Signup button if any control is invalid. Each control will have a message to inform you it is OK or not.

Now you can run ionic serve then try this new signup form. Here is my screen:

Hope this article can help you on developing your app on Ionic 2.
Happy coding! Cheers.


Thursday, May 19, 2016

Learn Ionic 2 with Authentication by JWT

Auth0 team has a great post about How to secure your mobile app with JWT (Json Web Tokens). In this article, I want to modify its AuthService service with full functions. So you can reuse this code in any your project. Addition, I also show you how to navigate between pages after authenticated.

Step 1: start your project
#ionic start ionic-auth-jwt-sample blank --v2 --ts

Step 2: add angular2-jwt. I use version @0.1.12 because the latest version of angular2-jwt has problem with latest version of ionic 2 + angular:
#cd ionic-auth-jwt-sample
#npm install angular2-jwt@0.1.12

Step 3: install authenticate server
+Copy the source code from here
+Go to the folder copied, run the server
#npm install
#node server.js

Step 4: generate AuthService and modify its code
#ionic g provider AuthService

Then edit file app/providers/auth-service/auth-service.ts as below:

import {Injectable} from 'angular2/core';
import {Http, Headers} from 'angular2/http';
import {Storage, LocalStorage} from 'ionic-angular';
import {JwtHelper, tokenNotExpired} from 'angular2-jwt';
import 'rxjs/add/operator/map';

@Injectable()
export class AuthService {
    LOGIN_URL: string = "http://localhost:3001/sessions/create";
    SIGNUP_URL: string = "http://localhost:3001/users";
    contentHeader: Headers = new Headers({"Content-Type": "application/json"});
    local: Storage = new Storage(LocalStorage);
    jwtHelper: JwtHelper = new JwtHelper();
    user: string;
    error: string;

    constructor(private http: Http) {
        let token = localStorage.getItem('id_token');
        if (token) {
            this.user = this.jwtHelper.decodeToken(token).username;
        }
    }

    public authenticated() {
        return tokenNotExpired();
    }

    login(credentials) {
        return new Promise((resolve, reject) => {
            this.http.post(this.LOGIN_URL, JSON.stringify(credentials), { headers: this.contentHeader })
                .map(res => res.json())
                .subscribe(
                    data => {
                        this.authSuccess(data.id_token);
                        resolve(data)
                    },
                    err => {
                        this.error = err;
                        reject(err)
                    }
                );
        });
    }

    signup(credentials) {
        return new Promise((resolve, reject) => {
            this.http.post(this.SIGNUP_URL, JSON.stringify(credentials), { headers: this.contentHeader })
              .map(res => res.json())
              .subscribe(
                  data => {
                    this.authSuccess(data.id_token);
                    resolve(data)
                  },
                  err => {
                    this.error = err;
                    reject(err)
                  }
              );
        });
    }

    logout() {
        this.local.remove('id_token');
        this.user = null;
    }

    authSuccess(token) {
        this.error = null;
        this.local.set('id_token', token);
        this.user = this.jwtHelper.decodeToken(token).username;
    }

}

Step 5: generate LoginPage and WorkPage
#ionic g page Login
#ionic g page Work

Then edit file app/pages/login/login.ts as below:

import {Page, NavController} from 'ionic-angular';
import {AuthService} from '../../providers/auth-service/auth-service';
import {WorkPage} from '../work/work';

@Page({
  templateUrl: 'build/pages/login/login.html',
})
export class LoginPage {
    authType: string = "login";
   
    constructor(private auth: AuthService, private nav: NavController) {
    }
   
    login(credentials) {
        this.auth.login(credentials).then(
          (success) => {
            this.nav.setRoot(WorkPage);
          },
          (err) => console.log(err)
        );
    }


    signup(credentials) {
        this.auth.signup(credentials).then(
          (success) => {
            this.nav.setRoot(WorkPage);
          },
          (err) => console.log(err)
        );
    }

}


Edit file app/pages/login/login.html as below:

<ion-navbar *navbar>
  <ion-title>Login</ion-title>
</ion-navbar>

<ion-content class="login" *ngIf="!auth.authenticated()">
 
    <div padding>
      <ion-segment [(ngModel)]="authType">
        <ion-segment-button value="login">
          Login
        </ion-segment-button>
        <ion-segment-button value="signup">
          Signup
        </ion-segment-button>
      </ion-segment>
    </div>
   
    <div [ngSwitch]="authType">
      <form *ngSwitchWhen="'login'" #loginCreds="ngForm" (ngSubmit)="login(loginCreds.value)">
        <ion-item>
          <ion-label>Username</ion-label>
          <ion-input type="text" ngControl="username"></ion-input>
        </ion-item>
       
        <ion-item>
          <ion-label>Password</ion-label>
          <ion-input type="password" ngControl="password"></ion-input>
        </ion-item>
       
        <div padding>
          <button block type="submit">Login</button>       
        </div>
       
      </form>
      <form *ngSwitchWhen="'signup'" #signupCreds="ngForm" (ngSubmit)="signup(signupCreds.value)">
        <ion-item>
          <ion-label>Username</ion-label>
          <ion-input type="text" ngControl="username"></ion-input>
        </ion-item>
       
        <ion-item>
          <ion-label>Password</ion-label>
          <ion-input type="password" ngControl="password"></ion-input>
        </ion-item>
       
        <div padding>
          <button block type="submit">Signup</button>
        </div>
       
      </form>
    </div>
   
    <div padding>
      <p *ngIf="error" class="error">{{ error._body }}</p> 
    </div>
 
</ion-content>



Edit file app/pages/work/work.ts as below:

import {Page, NavController} from 'ionic-angular';
import {AuthService} from '../../providers/auth-service/auth-service';
import {LoginPage} from '../login/login';

@Page({
  templateUrl: 'build/pages/work/work.html',
})

export class WorkPage {
  constructor(private auth: AuthService, private nav: NavController) {}
 
  logout() {
    this.auth.logout();
    this.nav.setRoot(LoginPage);
  }
}


Edit file app/pages/work/work.html as below:

<ion-navbar *navbar>
  <ion-title>Work</ion-title>
</ion-navbar>

<ion-content padding class="tools">
  <div *ngIf="auth.authenticated()">
    <div padding>
      <h1>Welcome {{ auth.user }}! Let do your job then Logout.</h1>
      <button block (click)="logout()">Logout</button>
    </div> 
  </div>
</ion-content>


Step 6: edit file app/app.ts

import {App, Platform} from 'ionic-angular';
import {StatusBar} from 'ionic-native';
import {LoginPage} from './pages/login/login';
import {Http} from 'angular2/http';
import {AuthHttp, AuthConfig} from 'angular2-jwt';
import {provide} from 'angular2/core';
import {AuthService} from './providers/auth-service/auth-service';

@App({
  template: '<ion-nav [root]="rootPage"></ion-nav>',
  providers: [
      provide(AuthHttp, {
        useFactory: (http) => {
          return new AuthHttp(new AuthConfig, http);
        },
        deps: [Http]
      }),
      AuthService
    ],
  config: {}
})
export class MyApp {
  rootPage: any = LoginPage;

  constructor(platform: Platform) {
    platform.ready().then(() => {
      StatusBar.styleDefault();
    });
  }
}


Step 7: start the app and check.
#ionic serve

Below are my captured images:



Update: if you care, here is new article for improving signup form with FormBuilder & Validator.

Happy coding! Any comment are welcome.

Friday, April 15, 2016

The first journey to learn Ionic 2


1. Introduce about Ionic 2
At the time I'm writing this article, Ionic 1 already had a big success while Ionic 2 and Angular 2 is coming GA (general available).
If you just start with Ionic, this is the good time for learning Ionic 2 and Angular 2.

2. Why Ionic 2 is better
Compare with Ionic 1, Ionic 2 has some things better as the following:

2.1 Ionic 2 app is organized in a better structure vs. Ionic 1

Below is the structure of files and folders in a Ionic project:
appThis folder will contain all the code you're going to write for the app, ie. the pages and services.
hooksThis folder contains scripts that can be executed as part of the Cordova build process. This is useful if you need to customize anything when you're building the app packages.
node_modulesIonic projects use npm to import modules, you'll find all imported modules here.
resourcesThis folder contains the icon and splash images for the different mobile platforms.
typingsThis folder contains type definition files for external modules that are not written in TypeScript.
wwwThis folder contains the index.html, remember all the app code should be in the app folder, not in the www folder.
config.xmlThis file contains the configuration for Cordova to use when creating the app packages.
ionic.config.jsConfiguration used by the Ionic CLI when excuting commands.
package.jsonThis file contains the list of all npm packages that have been installed for this project.
tsconfig.jsonConfiguration for the TypeScript compiler.
webpack.config.jsThis file contains the configuration for WebPack, which will create a bundle from the modules in the app.

2.2 Ionic 2 supports to create pages just by a simple command:
#ionic generate page YourPage
or
#ionic g page YourPage

2.3 Ionic 2 has cleaner syntax than Ionic 1, here are some examples:
Ionic 1: <img ng-src="{{photo.image}}" />
Ionic 2: <img [src]="photo.image" />

Ionic 1: <button ng-click="doSomething()">
Ionic 2: <button (click)="doSomething()">

You can see the code of Ionic 2 is easier to read than Ionic 1.

2.4 Ionic 2 has navigation mechanism better than Ionic 1, any time you want to navigate to a page, just push it into the navigation stack:
this.nav.push(YourPage);

When you want to go back the previous page, jus pop the current page out of the navigation stack:
this.nav.pop(YourPage);

2.5 Ionic 2 supports TypeScript and ES6, this allows you code take advantages of TypeScript and ES6 which will be supported natively in browsers in near future. However Ionic 2 will help translate TypeScript to JavaScript, so you don't worry.

2.6 Ionic 2 supports Components feature of Angular 2. Components allow you to quickly construct an interface for your app. You can build your specific Component or use existing Components of Ionic 2 such as modals, popups, and cards.
When you need  to use a Component, just go to http://ionicframework.com/docs/v2/components/

3. Decorators in Ionic 2

Decorators is main concept in Ionic 2. It contains configurations for a class such as template, providers, etc. Here is the general format of a class:

@Decorator({
    /*configurations*/
})
export class MyClass {
    /*functions*/
}


A decorator can be: @App, @Page, @Directive, @Component, @Pipe, @Injectable

@App: the root component where starts the other components in your app. It often is bound with a root page. Below is an example:
@App({
  template: `<ion-nav [root]="root"></ion-nav>`
  config: {
    backButtonText: 'Go Back',
    iconMode: 'ios',
    modalEnter: 'modal-slide-in',
    modalLeave: 'modal-slide-out',
    tabbarPlacement: 'bottom',
    pageTransition: 'ios',
  }
})


@Page: a page (view) component which is often specified by a template. Your app is a set of pages. Below is an example:
@Page({
    templateUrl: 'build/pages/mypage/mypage.html'  
})
export class MyPage {

}

@Directive: a custom directive which can be used in a Page or Component. Here is an example:
import {Directive} from 'angular2/core';
@Directive({
  selector: 'foo',
  template: './foo.html'
)}
export class FooDirective { }


And it is used in a Page:
import {FooDirective} from './foo'
@Page({
  templateUrl: '../page.html',
  directives: [FooDirective],
})


@Component: a built-in or custom component (module) has an interface and functionalities. Ionic 2 has many built-in Components, but you can make a custom component whatever you want as long as it fits your app. The following is a sample of a custom Component:
@Component({
  selector: 'custom-component',
  template: `
    <h1>My awesome custom component</h1>
    <ion-list>
      <ion-item>
        I am an awesome ionic component.
      </ion-item>
    </ion-list>
  `,
  directives: [IONIC_DIRECTIVES]
})
class MyComponent {

}

@Pipe: a data filter. Below is an example:
import {Pipe} from 'angular2/core'
@Pipe({
 name: 'myPipe'
})
export class MyPipeClass{
 transform(value, args) {
  return value;
 }
}


Use this pipe in HTML:
value | myPipe:args[0]:args[1]

@Injectable: a service which can be injected into another module. Here is a sample:

@Injectable()
export class DataService {
}


Use it in a page:
@Page({
  templateUrl: 'build/pages/my-page/my-page.html',
  providers: [DataService],
  directives: [MyCoolDirective, MyCoolerDirective],
  pipes: [MyPipe]
})


Hope you can start your journey with Ionic 2 after reading this article.

Happy coding!
Subscribe to RSS Feed Follow me on Twitter!