Getting Started

For developing a Keyplet you need to create a Node project. To do that first install Node.js After having Node installed, create a Node project and install the Keyp SDK:

  1. Download the Keyplet SDK

  2. Extract the ZIP file

  3. Change directory into the extracted folder

  4. Build the package npm run build

  5. Make it available globally npm link

The keyp package offers CLI functionalities. To be able to use them directly from the command line you need to install the keyp package globally.
npm link

This installs the npm package in your current Node project.

The keyp package provides an express application and some helpers.

Additionally it provides types so it can be used with a Typescript project as well.

To import the package in your Typescript project place the following in your imports.

import { App, logger, MongoDB, RestService, Utils} from "keyp";
OR
import * as keyp from "keyp";

To import the keyp package in javascript place the following in your code

const {App, logger, MongoDB, RestService, Utils} = require("keyp");

The App import is an express application class and it can be initialized the with the new keyword. It provides the express application, the express router and an instance of the itself. The constructor of the App class requires a function as an argument. This function will be used as a middleware for the /auth endpoint. The developer can implement the business logic for their keyplet authentication here.

Example 1. Example
const {app, router, instance} = new App(middleware, certificateInfo);

The certificate info requires the following information to create a certificate request.

{
    countryName: string
    stateOrProvinceName: string
    localityName: string
    organizationName: string
    organizationalUnitName: string
    commonName: string
    emailAddress?: string
}
function middleware(){
    return (req: Request, res: Response, next: Function) => {
           // keyplet logic for validating data
           req.body.expiresIn = `Factor token expiration date`
           next();
        }
}
The next function must be called if the data validation process is successful. This function calls the next function in the /auth endpoint where the Factor token is created and forwarded to the Wallet app.

The data of the request body coming in the middleware has the following structure:

{
 attributes: {},
 attr: req.body.attributeToken,
 validationData: req.body.validationData,
 userData: <user data object set on the widget>,
 expiresIn: 120,
 walletPubKey: JWK
}

By default the factor token will expire in 120 seconds. This can be changed by changing the value of the expiresIn property. Any additional data to be added can be added in the attributes property.

The router is used for adding new paths to the express application. The app is used for starting the express server, also to add additional middleware to the express server.

Example 2. Example
const server = app.listen(app.get("port"));
server.on("listening", () => {
    logger.info(`Express server listening on port ${app.get("port")}`);
});

Before starting the application the developer needs to create the developer key pair and developer csr. To do that the developer can run the following command:

keyp dev-keys <path/to/save/keys>

This command will generate the developer key pair as well as the developer CSR. This CSR the developer can upload it to the Identity platform and receive the developer certificate. After receiving the developer certificate from the identity platform rename it to developer_key.pem and place it in the certs folder inside the project directory.

Furthermore the developer manifest token needs to be generated. To generate the token file run the following command at the root directory of the project:

keyp prepare <path/to/developer/private/key> <host> <keyplet_name>

This will create a file in the public directory named dev_manifest.txt which holds the token signed with the developers private key. The host and keyplet options are required for the certificate x5u header.

For security reasons the developer key pair must be stored in a safe machine and is stored only in the machine where it was generated.
keyp zip <directory/to/zip> [configfile]

This command will create a zip file from the directory provided. The zip file created will comfort the required directory format by the Wallet app. Additionally, the hash of the zip file will be added to the manifest.json file.

The configfile is an optional argument for the command. If the configfile is provided then this command will create the developer token of the manifest. The config file is required to have the following fields.

{
  "devKey": "the full path of the developer key",
  "host": "the host where the keyplet will be deployed",
  "keyplet": "the name of the keyplet as set on the `KEYPLET` environmental variable"
}

Setting environmental variables

For setting the environmental variables used in the keyp package the developer can create a .env file in the project root directory and set the following variables:

PORT=
NODE_ENV=
*HOST=
*KEYPLET=
LOG_LEVEL=
MONGO_URL=
The * variables must be set

The PORT defines the port where the server will run.

NODE_ENV defines the environment node will start with ( development, production )

HOST defines the base url where the keyplet is going to be deployed. e.g. https://keyplet.keyp.io

KEYPLET specifies the url path where all the files in the public directory will be served. E.g. basePath/<KEYPLET>/manifest.json

LOG_LEVEL defines the level the logger will print output to the console and file system. E.g. debug, error, info, silly, verbose, warn

MONGO_URL is required for the server to know where to look for the MongoDB to set up a connection.

Keyplet Widget

For using the cordova plugins provided by the Wallet mobile application with ionic you need to install the wallet-api package.

npm install @types/wallet-api

This library provides the abstract interface of the functions available in the Wallet.

Example 3. Example

Keyplet.setUserData(object)

There is no implementation required for these functions.

Keyplet SDK

Keyplet SDK is a Node.js server written with Typescript. The server provides common endpoints and functionalities required for developing a basic Keyplet.

API

The Node server has a core endpoint which provides a JSON Web Token (JWT) for the verifying the communication between the Keyplet Widget and the Keyplet SDK (Node.js server). Additionally all files within the public directory are served as static files.

Description Of Usual Server Responses:

200 OK - the request was successful (some API calls may return 201 instead).

401 Unauthorized - authentication failed or user doesn’t have permissions for requested operation.

404 Not Found - resource was not found.

Auth

Returns the Identity Factor Token.

  • URL

/auth

  • Method:

POST

  • Data Params

JSON content type

  • Success Response:

    • Code: 200

    • Content: "eyJ0eXAiOiJhdHRyIiwiandrIjp7Imt0eSI6I…​"

  • Error Response:

    • Code: 401 UNAUTHORIZED

    • Content: { error : "Authentication failed!" }

  • Sample Call

{
  "attributeToken":"eyJ0eXAiOiJhdHRyIiwiYWxnIjoiRVMyNTYifQ.eyJpYXQiOjE1MzM2MzM1NDUsInVzZXJEYXRhIjp7InBob25lTnVtYmVyIjoiNzQwNTc0ODM2NyJ9fQ.Asv4Cp6f-qH7-teGCjAjzHMj8rGg1lzjvJNJqyjUs_n1pVzVB08E7rZ3WXAwNwx5lQZiK0FpnTSPTh1POz15EQ",
	"validationData":{
    "code":"747484"
  },
	"deviceToken":"eyJkZXZpY2VJZCI6Ijc1ZWQ3NmQxLWExNzAtNDJkMS04OGY2LWM2NzE5OTliNmYxMCIsInR5cCI6ImRldmkiLCJhbGciOiJFUzI1NiJ9.eyJrdHkiOiJFQyIsIngiOiI1XzF6WFd1QzJUY2l4c0dCWmtVak14NkFQOUlTWVoxUE5veVJrVDJhSy04IiwiY3J2IjoiUC0yNTYiLCJ5IjoiZTRyakk4UUpMRUY3OEV2V1M4SXpNMGJiS2FWVW1mS1VBdHM3UHloSG9tWSJ9.YgncC0Eeo8IoUaX5FyfrGBkwvMihPakSF-4DSkKUutDbrmbplVtl8zECPXLOQUYQqXVQ2LHF-nLPKiP03F9bSg"
}

Project Structure

.
├── Dockerfile
├── Dockerfile.production
├── Jenkinsfile
├── README.md
├── docker-compose.yml
├── index.ts
├── keyp-cli
│   ├── cli.ts
│   ├── generators.ts
│   └── prompts.ts
├── package-lock.json
├── package.json
├── src
│   ├── app.ts
│   ├── config.ts
│   ├── constants.ts
│   ├── controllers
│   │   ├── auth.ts
│   │   └── certs.ts
│   ├── helpers
│   │   ├── addressStandartization.ts
│   │   ├── database.ts
│   │   ├── generateECKeys.ts
│   │   ├── generateRSAKeys.ts
│   │   ├── index.ts
│   │   ├── logger.ts
│   │   ├── phoneStandartization.ts
│   │   ├── restService.ts
│   │   └── utils.ts
│   └── middleware
│       └── validateData.ts
├── swagger.json
├── tests
│   └── tests.ts
├── tsconfig.json
├── tslint.json
└── variables.scss

Environmental Variables

The Node.js server uses for development environmental variables defined in a .env file. Below is an example of a .env file.

Example 4. Example .env file

PORT=8001

NODE_ENV=development

In this example we start the Node server in development mode, listening at Port 8001.

Environmental variables that are used in the SDK are:


PORT=

NODE_ENV=

LOG_LEVEL=

VERSION=

*KEYPLET=

MONGO_URL=

*HOST=

The * variables are required.

One way to set them is by creating a .env file in you project directory and define the variables there.

Classes

ValidateData Class

The class ValidateData is where the validation of the Attribute Token is handled. Also the signing of the Developer Manifest Token is done. The class is located under src/middleware/validateData.ts.

RSA Class

The class RSA is where the asymetric RSA keys are generated. By default RSA uses a 2048 bit for generating the keys. This class exports an instance of itself, so it can be used as a singleton across the entire Keyplet SDK. This way the keys generated will be the same. The class implements three functions:

RSA functions
generateRSAKeys

This functions is where the RSA keys are generated. In the function a check is made whether the keys already exists (This way the keys will be the same between server restarts). If the keys already exist, they are used for the signing.

this.keys[property] = {publicKey: results[0], privateKey: results[1]};

For the key generation the node-forge npm package is used.

In case the keys have not been created before then they are generated. For the key creation the default key size of 2048 bit is used. The keyplet-manifest-validation-node Keyp package is used to handle the key generation.

generateRsaKeys
.then((keys: KeyPair) => {

})

After generating the key pair, they are exported into PEM files files with the following functions.


rsa.exportKey(directory.publicDirectory, keys.publicKey, type);
rsa.exportKey(directory.privateDirectory, keys.privateKey, type);
exportKey

This function retrieves the key generated in the RSA class and exports it into a PEM file. The public key is placed under the folder public located under the project root directory. The public folder contains all the project sources that are accessible from API. The private key is placed under the private directory located in the root directory of the project.

Additionally a certificate signing request is generated together with the keys.


rsa.createCSR(directory.csrDirectory, type, certificateInfo);

This create the Keyplet CSR and is placed under the csr directory located in the root directory of the project.

Generate Own RSA Keys

If you want to generate your own RSA keys using libraries such as OpenSSL you have to create them in PEM format. Also make sure to follow the naming expected by the application for the keys. Name the public key keyplet_key.pem and name the private key keyplet_key.pem. Additionally the keys must be place in the public and private folder respectively.

Utils

The Utils class provides helper functions that are used throughout the application. The class contains static functions so no instance of the class needs to be created to use its functions.

currentFile

This function takes as an argument the module-level argument __filename and returns only the name of the file. This function is mostly used for logging purposes.

resolvePath

This function requires a directory as an argument relative to the root directory of the applicatio and it returns the absolute path of this directory.

movePropertyALevelUp

This function requires an object and a property as arguments. As the name shows, it moves the provided property one level up in the provided object.

Example 5. Example
let example = {
  data: "some data",
  props: {
    min: 1,
    max: 5
  }
}

Utils.movePropertyALevelUp(example, "props");

/* example structure after modification
{
  data: "some data",
  min: 1,
  max: 5
}
*/
readPrivateKey

This function is used for reading the value of the keys. On successfully reading the file it returns the key in string format.

App

The App class is where the express application is created. The constructor of the class requires two arguments.

  • The middleware function, where the the keyplet developer can validate the data of the keyplet before the Identity Factor token is generated.

  • The certificate signing request info for generating the Keyplet CSR.

    This requires the following object format
{
    countryName: string;
    stateOrProvinceName: string;
    localityName: string;
    organizationName: string;
    organizationalUnitName: string;
    commonName: string;
    emailAddress?: string;
}

The email address is optional.

In the constructor of this class the configuration of the express server is made. After that the express routes are loaded into the application. Finally, the RSA keys are generated/imported into the application.

config

This function adds to the express application the following middleware:

  • Body-Parser

    The body-parser parses incoming request bodies of json/x-www-form-urlencoded type.
  • Helmet

    An npm package that sets various HTTP headers to the express app.
  • Morgan

    This middleware logs HTTP requests for the express application.
  • Express.static

    Using express.static we serve static files from the public directory of the application.
  • Custom Middleware

    We add our own custom middleware into the express application.
    This middleware handles the data send to the /auth endpoint.
    Also the result from the validate function of the ValidateData class.
routes

Here the express routes are injected into the express server.

Logger

The Node server uses winston package for logging information. The configuration is located in the src/helpers/logger.ts file. The logger has 5 priority logging levels.

ERROR : 0
WARN : 1
INFO : 2
VERBOSE : 3
DEBUG : 4
SILLY : 5

Additionally the logger is configured to output the logs in files as well. There are two files storing server logs.

  • info.log - Stores the logs of all logging levels with priority info level.

  • error.log - Stores the logs of all logging levels with priority error level.

The log files are stored in the logs/ directory in the root directory of the application. The logger level is specified as functions calls from the logger instance.

Example 6. Example
logger.debug("Logging at debug level", {example: "DEBUG"});

The logger levels take two arguments.

  1. A string (required) as a first argument

  2. An object (optional) as a second argument

The second argument is printed in JSON format.

RestService

The restService.ts is where the encapsulation for making REST request is located. The class has a constructor, which requires the base path and possible headers as argument. It initializes an axios service. The class provide a function for making requests, called request. This function requires two arguments:

  • the type of the request (e.g. POST, GET, PUT, PATCH, DELETE)

  • the endpoint to make this request to

Additionally it has two optional arguments:

  • The payload, which is the content of the body of the request (Typically used for POST requests)

  • The headers, which contains the headers that are going to be used for the request.

The request function returns a promise as a response.

MongoDB

The Keyp SDK offers an connection option with MongoDB. The database.ts file holds the class with basic functionalities for MongoDB connection. The class MongoDB exports an instance of itself so the same connection can be used between imports of the class. To connect to the database the connect function requires the Mongo URI string. The class provides some basic fuctionalities for accessing the MongoDB database:

  • createCollection - creates an new collection in the database. Requires the name of the collection as an argument and the options of the collection as an optional argument.

  • find - Search for specific data in the database. Requires the collection name and the data you are looking for as arguments. Optionally you can specify the data you want the search to return.

  • findOne - Search for a specific data in the database and return the first match. Requires the collection name and the data you are looking for as arguments. Optionally you can specify the data you want the search to return.

  • insertOne - Inserts one entry in the database. Requires the name of the collection to enter the data into and the data to be inserted into the database as arguments.

  • insertMany - Inserts multiple entries in the database. Requires the name of the collection to enter the data into and the data to be inserted into the database.

  • updateOne - Updates an entry in the database. Requires the collection name, the updated data and the query to search for the data to be updated as arguments. Optionally you can specify the data you want the search to return.

  • updateMany - Updates an entry in the database. Requires the collection name, the updated data and the query to search for the data to be updated as arguments. Optionally you can specify the data you want the search to return.

  • deleteOne - Deletes one entry from the database collection. Requires the collection name, the updated data and the query to search for the data to be deleted as arguments.

  • deleteMany - Deletes multiple entry from the database collection. Requires the collection name, the updated data and the query to search for the data to be deleted as arguments.

  • getCollection - Retrieves the collection from the database. With this collection the developer can directly invoke methods of MongoDB that are not provided by the class.

Authentication

The file src/controllers/auth.ts is where the final step of the /auth endpoint is implemented. The function auth handles the data received from the ValidateData and creates a JWT. The JWT is signed with the keyplet private key generated. The token created is the Identity Factor Token of the Keyp protocol.

This token is then encrypted using a shared key generated with the Wallet Public key and the private key of a Elliptic Curve Key pair generated. The public key of the generated key pair is added to the header of the JWE generated.

Config

The config.ts is where the different environment configuration are located. Based on the initialization of the NODE_ENV environmental variable, different configurations are loaded. There are configuration for development, production and testing.

Index

The index.ts is the file where all the exports are.

Keyp CLI

The keyp CLI is an wrapper for Ionic CLI. It provides some of the Ionic that help the Keyplet developer in case the use Ionic for implementing a Widget. Additionally the keyp CLI provides functionalities that can help the developer publish a Keyplet.

To use the keyp cli in a shell you have to install the keyp package globally.

  1. Download the Keyplet SDK

  2. Extract the ZIP file

  3. Change directory into the extracted folder

  4. Build the package npm run build

  5. Make it available globally npm link

Creating a manifest.json

1. Run a CLI questionnaire

To create a manifest.json with values that you supply, run:

keyp init

This will initiate a command line questionnaire that will conclude with the creation of a manifest.json in the directory in which the command was initiated.

Manually editing your manifest.json

You can manually edit your manifest.json. You’ll need to create an attribute in the package object called dependencies that points to an object. This object will hold attributes that name the packages you’d like to use. It will point to a semver expression that specifies the versions of that project that are compatible with your project.

If you have dependencies you only need to use during local development, follow the same instructions as above but use the attribute called devDependencies.

Creating Developer Keys/CSR

keyp dev-keys <path/to/save/keys>

This command will generate the developer public and private keys to the specified directory as well as the certificate signing request (CSR) required for the identity platform to create a developer’s certificate. After running the command a prompt will ask the user to enter the CSR details.

Sign the Manifest with the Developer Key

keyp prepare <path/to/developer/private/key> <host> <keyplet_name>

This command will generate a file with and signed token of the manifest.json file. The token is signed with the developer’s private key. Later when the Keyplet is deployed this signed token is signed again with the Keyplet key. The host and keyplet name are corensponding to the values of the environmental variables HOST and KEYPLET.

Creating an Ionic template

To create an Ionic template for the Widget, run:

keyp start [<name_of_widget>] [<template_name>]

The templates provided for generating projects are a selected few from the default templates that Ionic provides.

Template

Description

tabs

A starting project with a simple tabbed interface

sidemenu

A starting project with a side menu with navigation in the content area

super

A starting project complete with pre-built pages, providers and best practices for Ionic development

blank

A blank starter project

The template generates the Ionic-Angular project in the directory in which the command is initiated.

Generating additional Ionic components

For generating Ionic components you have to navigate to the directory of the Ionic project created by the command described in Creating an Ionic template

When in the Ionic project directory, run:

keyp generate [<type>] [<name>]

Automatically create components for your Ionic app.

The type of the component corresponds to one of the following:

component, directive, page, pipe, provider, tabs

The name will be the name of the component created. The name is normalized into an appropriate naming convention e.g. keyp generate page example creates a page by the name of ExamplePage.

Additionally the command can accept options.

Option

Description

--no-module

Do not generate an NgModule for the component

--constants

Generate a page constant file for lazy-loaded pages

Building the Widget

For building the Ionic project, run the following command in the Ionic project directory:

keyp build

The command has additional option:

Option

Description

--prod

Build the application for production

--aot

Perform ahead-of-time compilation for this build

--minifyjs

Minify JS for this build

--minifycss

Minify CSS for this build

--optimizejs

Perform JS optimizations for this build

Creating ZIP file for Widget

For creating a ZIP file out of the Ionic Widget, run:

keyp zip [<path>] [configfile]

The path is the path to the build ionic Widget. The ZIP file created will be placed under the public directory of your project.

In case Ionic is not used for creating the Widget then the path should point to the index.html file of your Widget.

The configfile is an optional argument for the command. If the configfile is provided then this command will create the developer token of the manifest. The config file is required to have the following fields.

{
  "devKey": "the full path of the developer key",
  "host": "the host where the keyplet will be deployed",
  "keyplet": "the name of the keyplet as set on the `KEYPLET` environmental variable"
}

Preview the Ionic project

To view the Ionic project in the web browser, run the following command in the Ionic project directory:

keyp serve

This will open the simulated mobile view of the application. You can select between android, apple and windows phone view.

UI Framework with Ionic

When you want to make a Keyplet you don’t have to start from the scratch. We built a small Framework based on Ionic for you that helps you building your own Widget within a very short time.

First Step:

You need to install the Keyp npm package to your own node project. Once this is done you can start writing your Business logic for your Backend.

npm install keyp

Second Step:

We need to generate a Manifest for your Keyplet. You need this to publish your Keyplet to the Identity Platform. To do this, type the following command and follow the steps.

keyp init

Third Step:

To start a Widget with our framework you can use the following command. It will generate an Ionic Project with a first Page. Additionally we Ovverride some Variables that your Widget has already in a Keyp-ish appearance.

keyp start

Fourth Step:

If you want to add additional pages to your widget, just type the following command and follow the steps.

keyp genrate

Colors

primary:    #000
secondary:  #5AAA69
danger:     #AA5A5A
light:      #fff
dark:       #000
keyp_light: #fff
keyp_dark:  #000

Basic

For properly locating your items, every Page should be based on the following code. The container with the class "keyp-content" will center vertically.

HTML
<ion-content padding>
  <div class="keyp-content">
   <!-- Your Content here -->
  </div>
</ion-content>

Icon

Available Icons here: Ionicons

HTML
  <ion-icon name="mail" class="keyp-icon-primary"></ion-icon>
Screenshot

Text

Text

font-family is Overpass

HTML
    <p class="keyp-text-primary">We sent you an email</p>
    <p class="keyp-text-secondary">We sent you an email</p>
Screenshot

Text

Buttons

HTML
    <button ion-button class="keyp-button-primary" (click)="presentLoading()">Dark</button>
    <button ion-button class="keyp-button-secondary" color="keyp_dark" outline>dark outline</button>
Screenshot

Buttons

Checkboxes

HTML
  <ion-item>
    <ion-label>Daenerys Targaryen</ion-label>
    <ion-checkbox color="keyp_dark" checked="true"></ion-checkbox>
  </ion-item>

  <ion-item>
    <ion-label>Arya Stark</ion-label>
    <ion-checkbox color= "keyp-dark" disabled="true"></ion-checkbox>
  </ion-item>
Screenshot

Checkboxes

Input Forms

Input Forms are required to have a Form validation. Because of this, you need to setup the rules in your .ts file, and add Error Messages to your .html code.

HTML
 <ion-list>
    <form novalidate (ngSubmit)="signup()" [formGroup]="signupform">
      <ion-item>

        <ion-input type="text"  placeholder="Name" [(ngModel)]="userData.name" formControlName="name" [class.error1]="!signupform.controls.name.valid && signupform.controls.name.dirty"></ion-input>
      </ion-item>


      <ion-item no-lines *ngIf="( signupform.get('name').hasError('minlength') || signupform.get('name').hasError('maxlength') ||signupform.get('name').hasError('pattern') ||signupform.get('name').hasError('required') ) && signupform.get('name').touched">
        <div class="error" *ngIf="signupform.get('name').hasError('required') && signupform.get('name').touched">
          Please input your name
        </div>
        <div class="error" *ngIf="signupform.get('name').hasError('minlength') && signupform.get('name').touched">
          Minimum 4 characters
        </div>
        <div class="error" *ngIf="signupform.get('name').hasError('maxlength') && signupform.get('name').touched">
          Maximum 30 characters
        </div>
        <div class="error" *ngIf="signupform.get('name').hasError('pattern') && signupform.get('name').touched">
          Just use alphabet character
        </div>
      </ion-item>

      <ion-item>
        <ion-input type="text"  placeholder="Email" [(ngModel)]="userData.email" formControlName="email" [class.error1]="!signupform.controls.email.valid && signupform.controls.email.dirty"></ion-input>
      </ion-item>
      <ion-item no-lines *ngIf="( signupform.get('email').hasError('minlength') || signupform.get('email').hasError('pattern') ||signupform.get('email').hasError('required') ) && signupform.get('email').touched">
        <div class="error" *ngIf="signupform.get('email').hasError('required') && signupform.get('email').touched">
          Please input your email
        </div>
        <div class="error" *ngIf="signupform.get('email').hasError('pattern') && signupform.get('email').touched">
          Email address invalid
        </div>
      </ion-item>
      <ion-item>
        <ion-input type="text"  placeholder="Username"[(ngModel)]="userData.username" formControlName="username" [class.error1]="!signupform.controls.username.valid && signupform.controls.username.dirty"></ion-input>
      </ion-item>

      <ion-item no-lines *ngIf="( signupform.get('username').hasError('minlength') || signupform.get('username').hasError('maxlength') ||signupform.get('username').hasError('pattern') ||signupform.get('username').hasError('required') ) && signupform.get('username').touched">
        <div class="error" *ngIf="signupform.get('username').hasError('required') && signupform.get('username').touched">
          Please input your username
        </div>
        <div class="error" *ngIf="signupform.get('username').hasError('minlength') && signupform.get('username').touched">
          Minimum 4 characters
        </div>
        <div class="error" *ngIf="signupform.get('username').hasError('maxlength') && signupform.get('username').touched">
          Maximum 10 characters
        </div>
        <div class="error" *ngIf="signupform.get('username').hasError('pattern') && signupform.get('username').touched">
          Just use alphabet character
        </div>
      </ion-item>
      <ion-item>
        <ion-input type="password" placeholder="Password" [(ngModel)]="userData.password" formControlName="password" [class.error1]="!signupform.controls.password.valid && signupform.controls.password.dirty"></ion-input>
      </ion-item>

      <ion-item no-lines *ngIf="( signupform.get('password').hasError('minlength') || signupform.get('password').hasError('maxlength') ||signupform.get('password').hasError('required') ) && signupform.get('password').touched">
        <div class="error" *ngIf="signupform.get('password').hasError('required') && signupform.get('password').touched">
          Please input your password
        </div>
        <div class="error" *ngIf="signupform.get('password').hasError('minlength') && signupform.get('password').touched">
          Minimum 6 characters
        </div>
        <div class="error" *ngIf="signupform.get('password').hasError('maxlength') && signupform.get('password').touched">
          Maximum 12 characters
        </div>
      </ion-item>
      <button type="submit" ion-button block color="primary" [disabled]="signupform.invalid">SIGNUP</button>
    </form>
  </ion-list>
JavaScript
 import { Validators, FormBuilder, FormGroup, FormControl } from '@angular/forms';

 export class HomePage {
  signupform: FormGroup;
  userData = { "username": "", "password": "", "email": "", "name": "" };
  constructor(public navCtrl: NavController) {
  }

  ngOnInit() {
    let EMAILPATTERN = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;
    this.signupform = new FormGroup({
      username: new FormControl('', [Validators.required, Validators.pattern('[a-zA-Z ]*'), Validators.minLength(4), Validators.maxLength(10)]),
      password: new FormControl('', [Validators.required, Validators.minLength(6), Validators.maxLength(12)]),
      name: new FormControl('', [Validators.required, Validators.pattern('[a-zA-Z ]*'), Validators.minLength(4), Validators.maxLength(30)]),
      email: new FormControl('', [Validators.required, Validators.pattern(EMAILPATTERN)]),
    });
  }
}
Screenshot

Forms

Select

HTML
<ion-list>
<ion-item>
  <ion-label>Gaming</ion-label>
  <ion-select [(ngModel)]="gaming" interface="action-sheet">
    <ion-option value="nes">NES</ion-option>
    <ion-option value="n64">Nintendo64</ion-option>
    <ion-option value="ps">PlayStation</ion-option>
    <ion-option value="genesis">Sega Genesis</ion-option>
    <ion-option value="saturn">Sega Saturn</ion-option>
    <ion-option value="snes">SNES</ion-option>
  </ion-select>
</ion-item>
</ion-list>
Screenshot

Text

Date and Time Picker

HTML
<ion-item>
  //Documentation for Date Formatting:
  //https://ionicframework.com/docs/api/components/datetime/DateTime/
  <ion-label>Start Time</ion-label>
  <ion-datetime displayFormat="h:mm A" pickerFormat="h mm A" [(ngModel)]="event.timeStarts"></ion-datetime>
</ion-item>
Screenshot

Text

Range

HTML
<ion-item>
    <ion-range min="0" max="100" pin="true"[(ngModel)]="brightness">
      <ion-icon range-left small name="sunny"></ion-icon>
      <ion-icon range-right name="sunny"></ion-icon>
    </ion-range>
  </ion-item>
  <ion-item>{{brightness}}</ion-item>
Screenshot

Text

Toggle

HTML
<ion-item>
  <ion-label>Frodo</ion-label>
  <ion-toggle  checked="true"></ion-toggle>
</ion-item>  <ion-item>
  <ion-label>Sam</ion-label>
  <ion-toggle disabled checked="false"></ion-toggle>
</ion-item>
Screenshot

Text

Radio Group

HTML
<ion-list radio-group>
  <ion-list-header>
    Language
  </ion-list-header>

  <ion-item>
    <ion-label>Go</ion-label>
    <ion-radio checked="true" value="go"></ion-radio>
  </ion-item>

  <ion-item>
    <ion-label>Rust</ion-label>
    <ion-radio value="rust"></ion-radio>
  </ion-item>

  <ion-item>
    <ion-label>Python</ion-label>
    <ion-radio value="python" disabled="true"></ion-radio>
  </ion-item>
</ion-list>
Screenshot

Text

Radio Group

JavaScript
import { LoadingController } from 'ionic-angular';

export class MyPage {

  constructor(public loadingCtrl: LoadingController) { }

  presentLoading() {
    const loader = this.loadingCtrl.create({
      content: "Please wait...",
      duration: 3000
    });
    loader.present();
  }
}
Screenshot

Text