Ionic Camera Photo Upload in Firebase

In Ionic, we can upload a file in Firebase as easily as CRUD operation on Firebase through Ionic.  Here our app we are going to see how to take pictures on a mobile device and upload them immediately to Firebase.

What are we Learning?

  1. Uploading taken a photo of the camera to Firebase storage service.
  2. AlertController to display an alert message when we successful upload photo to Firebase Storage.

Step 1: Setting up Apps 

ionic start cameraFirebase blank
$ ionic cordova plugin add cordova-plugin-camera
$ npm install --save @ionic-native/camera
npm install firebase --save

We have to create a project in firebase and click on Add Firebase to your web app. Copy the web app API from firebase project and add  in app.components.ts as highlight

import { Component } from '@angular/core';
...

import { HomePage } from '../pages/home/home';
import firebase from 'firebase';

@Component({
  templateUrl: 'app.html'
})
export class MyApp {
  rootPage:any = HomePage;

  constructor(platform: Platform, statusBar: StatusBar, splashScreen: SplashScreen) {    
    platform.ready().then(() => {
      statusBar.styleDefault();
      splashScreen.hide();

      const firebaseConfig = {
        apiKey: "AIUyQ",
        authDomain: "camm",
        databaseURL: "https",
        projectId: "camer",
        storageBucket: "camet.com",
        messagingSenderId: "939"
      };
      
      firebase.initializeApp(firebaseConfig);
    });
  }
}

 

Step 2: Adding user interface for Apps

Add the following code in home.ts file

<ion-header>
  <ion-navbar>
    <ion-title>Ionic Camera Firebase</ion-title>
  </ion-navbar>
</ion-header>
<ion-content padding>
  <div style="display:flex;justify-content:center">
    <button ion-button (click)="capture()">Lets take a picture!</button>
    <img [src]="captureDataUrl" *ngIf="captureDataUrl"/>
    <button ion-button (click)="upload()" *ngIf="captureDataUrl">Upload to Firebase!</button>
  </div>
</ion-content>

 

 

Step 3: Uploading pictures to Firebase

To upload photo, we will first need to get the root storage reference via firebase.storage().ref(). After that, we need to set file path in Firebase Storage. In this app, we have defined that all pictures must be inside a path ./images/filename-.jpg.

Once we have image path reference we will need to do the actual uploading using the
imageref.putString() function.
The function requires 2 parameters, which is the data of the actual picture, and the format of the data.
In the code below, since our picture was captured by the camera and stored inside the captureDataUrl, with the format of DATA_URL, we will need to tell Firebase that we will be uploading in that format:

imageRef.putString(this.captureDataUrl, firebase.storage.StringFormat.DATA_URL).then((snapshot)=> {
     // Do something here when the data is succesfully uploaded! 
});

Add the following code in home.ts file

import { Component } from '@angular/core';
import { NavController} from 'ionic-angular';
import { Camera, CameraOptions } from 'ionic-native';
import firebase from 'firebase';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {
  captureDataUrl: string;

  
  constructor(public navCtrl: NavController) {
  }

  capture() {
    const cameraOptions: CameraOptions = {
      quality: 50,
      destinationType: Camera.DestinationType.DATA_URL,
      encodingType: Camera.EncodingType.JPEG,
      mediaType: Camera.MediaType.PICTURE,
    };

    Camera.getPicture(cameraOptions)
      .then((imageData) => {
        // imageData is either a base64 encoded string or a file URI // If it's base64:
		
        this.captureDataUrl = 'data:image/jpeg;base64,' + imageData;
      }, (err) => {
      // Handle error
	  
    });
  } // End of capture camera


  upload() {
    let storageRef = firebase.storage().ref();
    // Create a timestamp as filename

    const filename = Math.floor(Date.now() / 1000);

    // Create a reference to 'images/todays-date.jpg'

    const imageRef = storageRef.child(`images/${filename}.jpg`);

    imageRef.putString(this.captureDataUrl, firebase.storage.StringFormat.DATA_URL)
      .then((snapshot)=> {
        // Do something here when the data is succesfully uploaded!


      });
  }
}

 

 

Note: As in this example, we don’t set any authentication. For access the Firebase storage we have to change to storage rule so that any one can read/write on storage. This means allowing the public to access. Click on storage rule and change the value for public access as

 

Step 4: Show prompt when upload succeeds

We are using AlertControlle component from ionic-angular package and to display alert box for successful uploading a photo to fire base.  To achieve this we have to import

import { NavController, AlertController } from ‘ionic-angular’;

add the function to show alert message
showSuccesfulUploadAlert() {
    let alert = this.alertCtrl.create({
      title: 'Uploaded!',
      subTitle: 'Picture is uploaded to Firebase',
      buttons: ['OK']
    });
    alert.present();

    // clear the previous photo data in the variable

    this.captureDataUrl = "";
  }

Adding complete code for home.ts

import { Component } from '@angular/core';
import { NavController, AlertController } from 'ionic-angular';
import { Camera, CameraOptions } from 'ionic-native';
import firebase from 'firebase';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {
  captureDataUrl: string;
  alertCtrl: AlertController;
  
  constructor(public navCtrl: NavController, alertCtrl: AlertController) {
    this.alertCtrl = alertCtrl;
  }

  capture() {
    const cameraOptions: CameraOptions = {
      quality: 50,
      destinationType: Camera.DestinationType.DATA_URL,
      encodingType: Camera.EncodingType.JPEG,
      mediaType: Camera.MediaType.PICTURE,
    };

    Camera.getPicture(cameraOptions)
      .then((imageData) => {
        // imageData is either a base64 encoded string or a file URI
        // If it's base64:
		
        this.captureDataUrl = 'data:image/jpeg;base64,' + imageData;
      }, (err) => {
      // Handle error
	  
    });
  } // End of capture camera


  upload() {
    let storageRef = firebase.storage().ref();
    // Create a timestamp as filename
	
    const filename = Math.floor(Date.now() / 1000);

    // Create a reference to 'images/todays-date.jpg'
	
    const imageRef = storageRef.child(`images/${filename}.jpg`);

    imageRef.putString(this.captureDataUrl, firebase.storage.StringFormat.DATA_URL)
      .then((snapshot)=> {
        // Do something here when the data is succesfully uploaded!
		
        this.showSuccesfulUploadAlert();
      });
  }

  showSuccesfulUploadAlert() {
    let alert = this.alertCtrl.create({
      title: 'Uploaded!',
      subTitle: 'Picture is uploaded to Firebase',
      buttons: ['OK']
    });
    alert.present();
    // clear the previous photo data in the variable
	
    this.captureDataUrl = "";
  }
}

 

 

 

Ionic CRUD operation on Firebase through AngularFirebase 2

W e can use Firebase to store data. With Firebase we can easily do CRUD and it provides login with the different option. We are creating Ionic apps with firebase and we will be doing following as

  1. Login and register user in our app through Firebase.
  2. Register user can create and view Shopping List through CRUD on Firebase database.

Step 1:  Creating our app from blank template

We can use SDK Firebase package, but angularfire2 are easier to code in our project.

ionic start firebaseAuth blank 
npm install angularfire2 firebase --save

 

Step 2: Creating / Configuring Firebase

We need to create a project, as we have three option to select as Android, Apple and web apps. As our Ionic project, we have to select the web. Add authentication to our project, click on Authentication and select option sign in options as Email and password as 

Firebase provides a lot of authentication option as login through social network authentication eg Facebook and Google etc.  We can control the access of Firebase database inside the firebase through setting rules. If we are not using any authentication then we set all user can read/write as in

{
  "rules": {
    ".read": "true",
    ".write": "true"
  }
}

 

Step 3: Configure our app.module.ts.

Before we configure, first create a file app.firebase.config.ts file containing the Firebase API. Add the following code in app/app.firebase.config.ts

 export const FIREBASE_CONFIG = {
    apiKey: "..",
    authDomain: "...",
    databaseURL: "...",
    projectId: "..",
    storageBucket: "",
    messagingSenderId: "7..."
  }; 

This information we will get from our Firebase project and add this code in app.module.ts file. Add the following code in app.module.ts file

import { BrowserModule } from '@angular/platform-browser';
....

import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { RegisterPage } from '../pages/register/register';
import { LoginPage } from '../pages/login/login';
import { HttpModule } from '@angular/http';
import { AngularFireModule} from 'angularfire2';
import { AngularFireDatabaseModule} from 'angularfire2/database';
import { AngularFireAuthModule } from 'angularfire2/auth';

import { FirebaseService} from '../providers/firebase-service';
import { FIREBASE_CONFIG } from "./app.firebase.config";

@NgModule({
  declarations: [
    MyApp,
    HomePage,
    LoginPage,
    RegisterPage
  ],
  imports: [
    BrowserModule,
    HttpModule,
    AngularFireDatabaseModule,
    AngularFireModule.initializeApp(FIREBASE_CONFIG),
    IonicModule.forRoot(MyApp),
    AngularFireAuthModule
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage,
    LoginPage,
    RegisterPage
  ],
  providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler},
    FirebaseService
  ]
})
export class AppModule {}

 

Step 4:  Create Firebase Service provider 

Create a Firebase service provider to access/ CRUD operation on Firebase database. First set the database rule to allow all user to read/write true. We will control the access later through creating Login/Register page

ionic g provider firebaseService

Add the following code in src/providers/firebase-service.ts

import { AngularFireDatabase } from 'angularfire2/database';
import { Injectable } from '@angular/core';

@Injectable()
export class FirebaseService {

  constructor(public afd: AngularFireDatabase) {}

  getShoppingItems(){
    return this.afd.list('/shoppingItems/');
  }

  addItems(name){
     return this.afd.list('/shoppingItems/').push(name);
  }

  removeItem(id){
     return this.afd.list('/shoppingItems/').remove(id);
  }

}

Here we are implementing CRUD operation on our Firebase database, we can easily understand the code of service.

 

Step 5: Create UI of our data on Home page

We will use the home page to display/ UI to allow the user to create/delete the shopping list. Add the following code in src/home/home.html

<ion-header>
  <ion-navbar color="primary">
    <ion-title>
      Shopping List
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  <ion-row> 
    <ion-col col-9>
      <ion-item>
        <ion-input type="text" [(ngModel)]="newItem" placeholder="New Shopping item"></ion-input>
      </ion-item>
    </ion-col>
    <ion-col>
      <button ion-button (click)="addItem()">Add</button>
    </ion-col>
  </ion-row>

  <ion-list>
    <ion-item-sliding *ngFor="let item of shoppingItems | async">
      <ion-item>
        {{ item.$value }}
      </ion-item>
      <ion-item-options side="right">
        <button ion-button color="danger" icon-only (click)="removeItem(item.$key)">
          <ion-icon name="trash"></ion-icon>
        </button>
      </ion-item-options>
    </ion-item-sliding>
  </ion-list>
</ion-content>

We are creating FirebaseListObservable in src/home/home.ts file as

import { Component } from '@angular/core';
import { NavController, ToastController } from 'ionic-angular';
import { FirebaseService } from '../../providers/firebase-service';
import { FirebaseListObservable } from 'angularfire2/database';


@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {
  shoppingItems: FirebaseListObservable<any []>;
  newItem = '';

  constructor( public navCtrl: NavController, public firebaseService: FirebaseService){
    this.shoppingItems = this.firebaseService.getShoppingItems();
  }

  addItem(){
    this.firebaseService.addItems(this.newItem);
  }

  removeItem(id){
    this.firebaseService.removeItem(id);
  }

}

 

Step 6: Adding Login /Register in Apps

Firebase supports login and authentication internally and also provides support for integrating with social login providers. Most applications need some kind of login mechanism internally; with Firebase it’s super easy to set this up. Whenever we login using Firebase a session is created and a unique ID uid is returned by Firebase, which is distinct across all providers and never change for a specific authenticated user.

We will create two-page Login and Register page as

ionic generate page Login
ionic generate page Register

Now set the login page to root and edit root page from home to login in app.component.ts file

...
import { LoginPage } from '../pages/login/login';
..

export class MyApp {
  rootPage:any = LoginPage;
  ....
}

 

Step 7: Creating User Model

Inside src folder create a new folder called models and add the new file for user model as a src/models/user.ts. Add the following code

export interface User{
    email: string;
    password : string;
}

 

Step 8: Create UI and typescript code for Login/Register page

Add the following code in src/pages/login/login.html file

<ion-header>
  <ion-navbar>
    <ion-title>Login</ion-title>
  </ion-navbar>
</ion-header>
<ion-content padding>
  <ion-item>
    <ion-label>Email Address</ion-label>
    <ion-input type="text" [(ngModel)]="user.email"></ion-input>
  </ion-item>
  <ion-item>
    <ion-label>Password</ion-label>
    <ion-input type="password" [(ngModel)]="user.password"></ion-input>
  </ion-item>

  <button ion-button (click)="login(user)">Login</button>
  <button ion-button color="light" (click)="register()">Register</button>
</ion-content>

Add the following code in src/pages/login/login.ts file, as for login we have to create service in real apps, as for this example we will put Firebase authentication in login.ts file only.

import { Component } from '@angular/core';
import {  NavController, NavParams } from 'ionic-angular';
import { User } from '../../models/user';
import { RegisterPage } from '../register/register';
import { AngularFireAuth } from 'angularfire2/auth';
import { HomePage } from '../home/home';


@Component({
  selector: 'page-login',
  templateUrl: 'login.html',
})
export class LoginPage {

  user = {} as User;

  constructor(private angFireAuth:AngularFireAuth, public navCtrl: NavController, public navParams: NavParams) {}

 async login(user: User){
   try{
    const result = this.angFireAuth.auth.signInWithEmailAndPassword(user.email, user.password);
    if(result){
      this.navCtrl.setRoot(HomePage);
    }
   }
   catch(e){
     console.error(e);
   }
 }

 register(){
  this.navCtrl.push(RegisterPage);
 }

}

Note : When the user passes the authentication, we will redirect the user to home page, where home page containing the shopping list. In the login page, we have two buttons one for login and one for register new user. The information of the new user will be stored in Firebase database. Clicking on Register button on login page will navigate to the regiseter page.

Add the following code in src/pages/register/register.html file 

<ion-header>
  <ion-navbar>
    <ion-title>Register</ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  <ion-item>
    <ion-label>Email Address</ion-label>
    <ion-input type="text" [(ngModel)]="user.email"></ion-input>
  </ion-item>
  <ion-item>
    <ion-label>Password</ion-label>
    <ion-input type="password" [(ngModel)]="user.password"></ion-input>
  </ion-item>

  <button ion-button (click)="register(user)">Register</button>
</ion-content>

Add the following code in src/pages/register/register.ts file 

import { Component } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
import { User } from '../../models/user';
import { AngularFireAuth } from 'angularfire2/auth';
import { LoginPage } from '../login/login';

@Component({
  selector: 'page-register',
  templateUrl: 'register.html',
})
export class RegisterPage {
  user = {} as User;

  constructor(private angFireAuth: AngularFireAuth, public navCtrl: NavController, public navParams: NavParams) {
  }

  async register(user: User){
    try {
      const result = await this.angFireAuth.auth.createUserWithEmailAndPassword(user.email, user.password)
      this.navCtrl.push(LoginPage);
      console.log(result);
    }
    catch(e) {
      console.error(e);
    }
    
  }
}

In real application we have to create separate Firebase service code in service folder, for now just for example we are keeping service code with register/login page.

Step 9: Modify the home page

Once valid user login, we will redirect to home page, here we add ToastController.

Add the following code modify the code in home.ts file, we have added ToastController to display the message below on above image.

import { Component } from '@angular/core';
import { NavController, ToastController } from 'ionic-angular';
import { FirebaseService } from '../../providers/firebase-service';
import { FirebaseListObservable } from 'angularfire2/database';
import { AngularFireAuth } from 'angularfire2/auth';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {
  shoppingItems: FirebaseListObservable<any []>;
  newItem = '';

  constructor(private angFireAuth: AngularFireAuth,  private toast: ToastController, public navCtrl: NavController, public firebaseService: FirebaseService){
    this.shoppingItems = this.firebaseService.getShoppingItems();
  }

  addItem(){
    this.firebaseService.addItems(this.newItem);
  }

  removeItem(id){
    this.firebaseService.removeItem(id);
  }

  ionViewWillLoad(){
    this.angFireAuth.authState.subscribe(data => {
      if(data && data.email && data.uid){
        this.toast.create({
          message: `Welcome to APP_NAME', ${data.email}`,
          duration: 3000
        }).present();
      }
      else{
        this.toast.create({
          message: `Couldn't find authentication, ${data.email}`,
          duration: 3000
        }).present();
      }
    });
  }

}

Weather – Chart Apps

Creating Weather – Chart Application, With help of API from https://api.forecast.io our apps we are forecasting the weather in different part of the world and displaying weather information in chart form.

What are we learning?

  • Use of forecast.io API to display current and forecast the weather.
  • Use the tab view
  • Forms, Form Fields, Form validations
  • Integrate angular modules angular2-highcharts to display chart in ionic

What We Will Build
“Weather App” that displays the current weather and a weather forecast in two tabs. The user can enter the location in a form, and the app will validate the location and retrieve weather for that location via the https://developer.forecast.io/ APIs (application programming interfaces). This app also fetches the forecast for the location and displays it in a chart format.

Prerequisite
To use the https://developer.forecast.io/ APIs you have to register with this site and get the API key. The API key is available from the Ionic Service Providers.

Important: Problem face while creating a chart in ionic.

  1.  Problem: charts are taken more width in mobile. Solution: add chart { display: block; } in .scss or in style sheet.
  2. Problem: chart type not define, solution on we have to define chart module in app.module differently as, define all the highlight in app.module.ts file
    import { BrowserModule } from '@angular/platform-browser';
    ....
    
    import { ChartModule } from 'angular2-highcharts';
    import * as highcharts from 'highcharts';
    ...
    
    declare var require:any;
    
    
    @NgModule({
    ..
      imports: [
        ....
        ChartModule.forRoot(highcharts, require('highcharts/modules/map')),
        IonicModule.forRoot(MyApp)
      ],
      bootstrap: [IonicApp],
      entryComponents: [
         .....
      ],
      providers: [
        .....
      ]
    })
    export class AppModule {}

Step 1: Create first the apps and add two tabs

ionic start weahteraAPI blank
ionic generate page weather-api
ionic generate tabs
How may tabs would you like? 2
Enter the first tab name: Weather
Enter the second tab name: Forecast

Set the weather-api as root page and put the both the tab inside the app/pages/weather-api.ts.

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { WeatherPage } from '../weather/weather';
import { ForcastPage} from '../forcast/forcast';

@Component({
  selector: 'page-weather-api',
  templateUrl: 'weather-api.html'
})
export class WeatherAPIPage {

  tab1Root: any = WeatherPage;
  tab2Root: any = ForcastPage;

  constructor(public navCtrl: NavController) {}

}

And add the app/pages/weather-api.html

<ion-tabs>
  <ion-tab [root]="tab1Root" tabTitle="Weather" tabIcon="home"></ion-tab>
  <ion-tab [root]="tab2Root" tabTitle="Forcast" tabIcon="text"></ion-tab>
</ion-tabs>

And add the following code in app/app.module.ts

import { ForcastPage} from '../pages/forcast/forcast';
import { WeatherPage } from '../pages/weather/weather';
import { WeatherAPIPage } from '../pages/weather-api/weather-api';

And add the following code in app/app.component.ts and set the rootPage to WeatherAPIPage.

import { Component } from '@angular/core';
...

import { WeatherAPIPage } from '../pages/weather-api/weather-api';
@Component({
  templateUrl: 'app.html'
})
export class MyApp {
  rootPage:any = WeatherAPIPage;
  ....
}

 

Step 2: Adding the service provider for data service

In the weather app, when you provide a location as input, the latitude and longitude of the location are required. You can retrieve this information using the google API. Once you have those details you can retrieve the weather details of the location using the forecast API. We will implement both these calls as services. Using ionic generate to create a provider for the constants and name it AppConstants.

ionic generate provider AppConstants

Add the following code in app/providers/app-constants.ts

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';

@Injectable()
export class AppConstants {
  googleAPIURL: string;
  forecastURL: string;

  constructor(private http: Http) {
    this.googleAPIURL = "https://maps.googleapis.com/maps/api/geocode/json?address=";
    this.forecastURL = "https://api.darksky.net/forecast/2406c34530d2a00c982c99c4a4da62ab/";
  }
  getGoogleAPIURL() {
    return this.googleAPIURL;
  }
  getForecastURL() {
    return this.forecastURL;
  }
}

 

Step 3: Add the REST Service Provider

Create the REST service provider with the name WeatherApi

$ ionic generate provider WeatherAPI

The REST provider class needs to use the app-constants provider for the HTTP URL requests. So import app-constants into the WeatherAPI provider file src/providers/weather-api.ts,

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';
import { AppConstants } from './app-constants';

@Injectable()
export class WeatherAPI {
  weatherURL: string;
  private constantVar: any;

  constructor(private http: Http, constantVar: AppConstants) {
    this.constantVar = constantVar;
    this.weatherURL = constantVar.getForecastURL();
  }

  getGeometry(googleAPIURL: any,location: any) {
    return this.http.get(googleAPIURL+"'"+location+"'")
    .map(res => res.json())
  }
  
  getCurrentWeather(longitude: any,latitude: any) {
    return this.http.get(this.weatherURL+latitude+","+longitude)
    .map(res => res.json())
  }

}

Notice the @Injectable() annotation in the provider classes. This marks the class to be available for the
component for instantiation

We have two methods in above code.

  1. getGeometry is the method to retrieve the latitude and longitude for the provided location from Google.
  2. getCurrentWeather is another method that retrieves the weather information for the provided longitude and latitude of the location.

Step 4:  Creating user interface for  Forcast and Weather Form

In the src/pages/forecast/forecast.html file include the code

<ion-header>
  <ion-navbar>
    <ion-title>Forcast</ion-title>
  </ion-navbar>
</ion-header>
<ion-content padding>
  <form [formGroup]="forecastForm" novalidate>
    <table width="100%">
      <tr>
        <td>
          <div class="list"> <br>
            <ion-label>Forecast Type</ion-label>
            <ion-list radio-group formControlName="forecastType" name="forecastType">
              <ion-item>
                <ion-label class="item item-radio">daily</ion-label>
                <ion-radio value="daily" checked></ion-radio>
              </ion-item>
              <ion-item>
                <ion-label class="item item-radio">hourly</ion-label>
                <ion-radio value="hourly"></ion-radio>
              </ion-item>
            </ion-list>
          </div>
        </td>
        <td>
          <ion-item>
            <ion-label floating>Location</ion-label>
            <ion-input type="text" formControlName="location" name="location"></ion-input>
          </ion-item>
          <p [hidden]="forecastForm.controls.location.valid" danger padding-left> Enter a valid location</p>
        </td>
      </tr>
      <tr> 
        <td colspan="2">
          <button ion-button block [disabled]="!forecastForm.controls.location.valid" (click)="getForecast(forecastForm)">
          <ion-icon name="custom-button"></ion-icon>Get Weather</button>
        </td> 
      </tr>
    </table>
  </form>
  <div class="container" *ngIf="weatherResult">
    <chart [options]="chartValue"></chart>
  </div>
</ion-content>

Here we are adding two radio button and we are formControlName=”forecastType” to control the forecasting daily and hourly. The highlight for displaying the chart in forecast page.

A form is a type of FormGroup. A Control is tied to the input field; it has a value and a validation state. In the code forecastForm is a FormGroup. This form has two fields: a location text field and a radio button group for the forecast type.

In the src/pages/weather/weather.html file include the code 

<ion-header>
  <ion-navbar>
    <ion-title>Weather</ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  <form [formGroup]="weatherForm" novalidate>
    <table width="100%">
      <tr>
        <td>
          <ion-item>
            <ion-label floating>Location</ion-label>
            <ion-input type="text" formControlName="location" name="location"></ion-input>
          </ion-item> <br>
          <p [hidden]="weatherForm.controls.location.valid" danger padding-left> Enter a valid location</p>
        </td>
      </tr>
      <tr> 
        <td> <br>
          <button ion-button block [disabled]="!weatherForm.controls.location.valid" (click)="getWeather(weatherForm)">
          <ion-icon name="custom-button"></ion-icon>Get Weather</button>
        </td> 
      </tr>
    </table>
  </form>

  <div *ngIf="weatherResult">
    <h3>Right Now</h3>
    <h2 class="current-temp">
      <ion-icon name="{{summaryIcon}}"></ion-icon>
      {{currentWeather.summary}}°
    </h2>
    <div>
      <div>
        <span>Feels like {{currentWeather.temperature}}</span><br>
        <span>Dew point: {{currentWeather.dewPoint}}</span><br>
        <span>Humidity: {{currentWeather.humidity}}%</span><br>
        <span>Visibility: {{currentWeather.visibility}} mi</span><br>
        <span>Pressure: {{currentWeather.pressure}} mb</span>
      </div>
    </div>
  </div>
</ion-content>

 

Step 5: Validation in the Weather Form

The Weather form uses four types of basic validation: required, minimum length, maximum length, and pattern. All four types of validation are applied on the location field. They are applied to the file src/pages/ weather/weather.ts via the constructor

import { Component } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import {AppConstants} from '../../providers/app-constants';
import {WeatherAPI} from '../../providers/weather-api';

@Component({
  selector: 'page-weather',
  templateUrl: 'weather.html',
})
export class WeatherPage {
  weatherForm: FormGroup;
  private appConstants: any;
  private Weather: any;
  private geometry: any;
  private currentWeather: any;
  weatherResult: boolean;
  summaryIcon: string;

  constructor(private navController: NavController, private fb: FormBuilder, appConstants: AppConstants, WeatherApi: WeatherAPI) {
    this.weatherForm = fb.group({
      'location': ['', Validators.compose([Validators.required,Validators.pattern
      ('[a-zA-Z, ]*'),Validators.minLength(3),Validators.maxLength(100)])]
    });

    this.appConstants = appConstants;
    this.Weather = WeatherApi;
    this.geometry = { "longitude":"", "latitude":""};
    this.currentWeather = {};
    this.weatherResult = false;
    this.summaryIcon ="";
  }

  getWeather(formData: any) {
    this.Weather.getGeometry(this.appConstants.getGoogleAPIURL(), formData.value.location).
      subscribe((data: any) => {
        this.geometry.longitude = data.results[0].geometry.location.lng;
        this.geometry.latitude = data.results[0].geometry.location.lat;
        this.Weather.getCurrentWeather(this.geometry.longitude,this.geometry.latitude).
        subscribe((weatherData: any) => {
          this.currentWeather=weatherData.currently;
          this.weatherResult = true;
          if(this.currentWeather.summary.toLowerCase().indexOf("cloudy") > 0)
            this.summaryIcon = "cloudy";
          else if(this.currentWeather.summary.toLowerCase().indexOf("rainy") > 0)
            this.summaryIcon = "rainy";
          else if(this.currentWeather.summary.toLowerCase().indexOf("sunny") > 0)
            this.summaryIcon = "sunny";
          else if(this.currentWeather.summary.toLowerCase().indexOf("thunderstorm") > 0)
            this.summaryIcon = "thunderstorm";
        });
      });
  }
}

Retrieve Weather Data for Display through getWeather(formData: any)  from weather-api service. We have getGeometry is called to retrieve the latitude and longitude. Once you have that information you call getCurrentWeather to retrieve the current weather. The current weather information is display in the step 4 weather.html page.

 

Step 6: Display the Forecast as Chart

Here we will learn how other modules of angular2 can be integrated with Ionic. To understand this integration, we will use the chart display for the forecast. To display the forecast as a chart, we use the “CHART_DIRECTIVES” from “angular2-highcharts” module.

npm install angular2-highcharts --save

Charts are used for the forecast, so let’s import the charts into the src/app/app.module.ts

import { ChartModule } from 'angular2-highcharts';
import * as highcharts from 'highcharts';

declare var require:any;

@NgModule({
  declarations: [
   ...
  ],
  imports: [
    BrowserModule,
    ChartModule.forRoot(highcharts, require('highcharts/modules/map')),
    HttpModule,
    IonicModule.forRoot(MyApp)
  ],

 

Include the chart value variable in the src/pages/forecast/forecast.ts. Define the variable as part of all the other variable definitions as – chartValue: {};

Filtering of Forecast JSON in src/pages/forecast/forecast.ts
Filtering the JSON retrieved from the forecast API is required for the chart display. JSON filtration retrieves the date/hour details and the temperature details. Add the following code in src/pages/forecast/forecast.ts

import { Component } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import {AppConstants} from '../../providers/app-constants';
import {WeatherAPI} from '../../providers/weather-api';

@Component({
  selector: 'page-forcast',
  templateUrl: 'forcast.html',
})
export class ForcastPage {
  forecastForm: FormGroup;
  private appConstants: any;
  private Weather: any;
  private geometry: any;
  private minWeather: number[][];
  private maxWeather: number[][];
  private weatherTime: any;
  weatherResult: boolean;
  summaryIcon: string;
  chartValue: {};

  constructor(private navController: NavController, private fb: FormBuilder, appConstants: AppConstants, WeatherApi: WeatherAPI) {
    this.forecastForm = fb.group({
      'location': ['', Validators.compose([Validators.required,Validators.pattern
      ('[a-zA-Z, ]*'),Validators.minLength(3),Validators.maxLength(100)])],
      'forecastType': 'daily'
    });

    this.appConstants = appConstants;
    this.Weather = WeatherApi;
    this.geometry = { "longitude":"", "latitude":""};
    this.minWeather = new Array();
    this.maxWeather = new Array();
    this.weatherTime = new Array();
    this.weatherResult = false;
    this.summaryIcon ="";
  }

  filterJson(json,forecastType){
    this.minWeather = new Array();
    this.maxWeather = new Array();
    this.weatherTime = new Array();
    for(var i=0;i<json.length;i++){
      var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
      var b: Date = new Date(json[i].time * 1000);

      if(forecastType == "daily"){
        this.weatherTime.push(b.getDate()+" "+months[b.getMonth()]+" "+b.getFullYear());
        this.maxWeather.push(json[i].temperatureMax);
        this.minWeather.push(json[i].temperatureMin);
      }
      else{
        this.weatherTime.push(b.getDate()+" "+months[b.getMonth()]+" "+b.getFullYear() +" - "+b.getHours() +" hours");
        this.minWeather.push(json[i].temperature);
      }
    }
  }

  getForecast(formData: any) {
    this.Weather.getGeometry(this.appConstants.getGoogleAPIURL(), formData.value.location).
    subscribe((data: any) => {
      this.geometry.longitude = data.results[0].geometry.location.lng;
      this.geometry.latitude = data.results[0].geometry.location.lat;
      this.Weather.getCurrentWeather(this.geometry.longitude,this.geometry.latitude).
      subscribe((weatherData: any) => {
      this.weatherResult = true;
      if(formData.value.forecastType == "daily"){
        this.filterJson(weatherData.daily.data,formData.value.forecastType);
        this.chartValue = {
          title : { text : 'Weather Forecast' },
          chart: { type: 'column' },
          xAxis: {
            categories: this.weatherTime
          },
          series: [
            { name : 'Min Temp', data: this.minWeather},
            { name : 'Max Temp', data: this.maxWeather}
          ]
        };
      }
      else{
        this.filterJson(weatherData.hourly.data,formData.value.forecastType);
        this.chartValue = {
          title : { text : 'Weather Forecast' },
          chart: { type: 'column' },
          xAxis: {
            categories: this.weatherTime
          },
          series: [ { name : 'Min Temp', data: this.minWeather},]
        };
      }
    });
  });
  }
}

 

Step 7: Include the required chart js file into the body of the src/index.html file 

<body>
....
  <ion-app></ion-app>
  <!-- Chart.js -->
  
  <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.3.0/Chart.min.js"></script>
  
    <!-- The polyfills js is generated during the build process -->
	
  <script src="build/polyfills.js"></script>

 

Step 8:  To display chart we have to add following code at the end of app/pages/forecast/forecast.html

  <div class="container" *ngIf="weatherResult">
    <chart [options]="chartValue"></chart>
  </div>

In app/app.module.ts add the following code

import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';
import { HttpModule } from '@angular/http';

import { ChartModule } from 'angular2-highcharts';
import * as highcharts from 'highcharts';

import { MyApp } from './app.component';
import { ForcastPage} from '../pages/forcast/forcast';
import { WeatherPage } from '../pages/weather/weather';
import { WeatherAPIPage } from '../pages/weather-api/weather-api';
import { AppConstants} from '../providers/app-constants';
import { WeatherAPI } from '../providers/weather-api';


declare var require:any;


@NgModule({
  declarations: [
    MyApp,
    ForcastPage,
    WeatherPage,
    WeatherAPIPage
  ],
  imports: [
    BrowserModule,
    ChartModule.forRoot(highcharts, require('highcharts/modules/map')),
    HttpModule,
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    ForcastPage,
    WeatherPage,
    WeatherAPIPage
  ],
  providers: [
    StatusBar,
    SplashScreen,
    AppConstants,
    WeatherAPI,
    {
      provide: ErrorHandler, 
      useClass: IonicErrorHandler
    }
  ]
})
export class AppModule {}

 

Ionic with Firebase for Hacker News Apps

To describe the app’s requirements, we list the main user stories as below.

  1. View top stories – Users can view a list of top stories on Hacker News and view the page of each story.
  2. View comments – For each story, users can view its comments. For each comment, users can also view replies to that comment.
  3. Add stories to favorites – Users can add a story to favorites and see a list of all favorite stories.
  4. Share stories – Users can share stories to the social network.
ionic start hacker_news_app sidemenu

$ ionic platform add ios --save
$ ionic platform add android --save

 

Step 1: Listing the Stories

We start implementing the first user story that lists Hacker News top stories. We are going to cover the following topics in this long chapter. Use the list component to show top stories and test with Jasmine and Karma. Services to load top stories.

  1. Firebase basics and Hacker News API.
  2. Infinite scrolling and pull-to-refresh.
  3. Loading indicators and error handling.

Define the Model
The app starts, the user is presented with a list of top stories on Hacker News. The user can see basic information on each story on the list, including title, URL, author, published time, and score. The information for each story should be declared in the model.

A model in programming is a generic concept, and you may have come across it with common patterns like Model-View-Controller (or MVC). Depending on the context, the exact definition of a model may vary, but in general, a model is used to store or represent data.
We declare the model as a TypeScript interface in. Here we use a more general model name Item instead of Story because comments are also items and can share the same model.

export interface Item {
	id: number;
	title: string;
	url: string;
	by: string;
	time: number;
	score: number;
}

The list of top stories can be represented as an array of items, that is, Item[]. However, we define a type Items in below to represent a list of items.

import { Item } from './Item';
export type Items = Item[];

In app we will create the folder called app/model/ and add two files as item.ts and items.ts file. Add the following code in item.ts file

export interface Item {
  id: number;
  title: string;
  url: string;
  by: string;
  time: number;
  score: number;
  text?: string;
  descendants?: number;
  kids?: number[];
  isFavorite?: boolean;
}

And add the following code in items.ts

import { Observable } from 'rxjs';
import { Item } from './Item';

export interface Items {
  refresh?: boolean;
  offset: number;
  limit: number;
  total?: number;
  results: Observable<Item>[];
  loading?: boolean;
}

 

Step 2: Display a List of Items through component

After defining the model Item and learning the component ion-list, we are now ready to display a list of items in the app. We need to create a component for the list of items and another component for each item in the list.

$ ionic g component Items
$ ionic g component Item

Ionic CLI creates the necessary files for a component, including TypeScript file, HTML template file, and Sass file. For the generated component, these files are created under the related subdirectory of the src/components directory.

Add or modify the app/components/item/item.ts file. 

import { Component, Input } from '@angular/core';
import { Item } from '../../model/item';

@Component({
  selector: 'item',
  templateUrl: 'item.html'
})
export class ItemComponent {
  @Input() item: Item;
}

Add or modify the app/components/item/item.html file. 

<div>
  <h2 class="title">{{ item.title }}</h2>
  <div> 
    <span><ion-icon name="bulb"></ion-icon>{{ item.score }}</span>
    <span>
      <ion-icon name="person"></ion-icon>
      {{ item.by }}
    </span>
    <span>
      <ion-icon name="time"></ion-icon>
      {{ item.time | timeAgo }}
    </span>
  </div>
  <div>
    <span>
      <ion-icon name="link"></ion-icon>
      {{ item.url }}
    </span>
  </div>
</div>

Important: The timeAgo used in {{ item.time | timeAgo }} is an Angular 2 pipe to transform the timestamp item.time into a human-readable text, like 1 minute ago or 5 hours ago. The implementation of TimeAgoPipe uses the taijinlee/humanize (https://github.com/taijinlee/humanize) library to generate a human-readable text. So we need to install the humanize library to our projects as

ionic generate pipe TimeAgoPipe
npm install humanize --save

Some example of humanize used, 

humanize.date('Y-m-d'); // 'yyyy-mm-dd'
humanize.filesize(1234567890); // '1.15 Gb'

Add/Modify the following code in src/app/pipes/time-ago-pipe.ts 

import { Pipe, PipeTransform } from '@angular/core';
import * as humanize from 'humanize';

@Pipe({
  name: 'timeAgo',
})
export class TimeAgoPipe implements PipeTransform {
  
  transform(time: number) {
    return humanize.relativeTime(time);
  }
}

 

Items Component

The ItemsComponent is used to render a list of items. Add the following code in src/components/items/items.ts, the selector for this component is items. It also has the property items that are bound to the value of the property items from its parent component.

import { Component, Input } from '@angular/core';
import { Items } from '../../model/Items';

@Component({
  selector: 'items',
  templateUrl: 'items.html',
})
export class ItemsComponent {
  @Input() items: Items;
}

 

Add or modify the app/components/item/items.html file. 

<ion-list *ngIf="items.length > 0">
  <ion-item *ngFor="let item of items">
    <item [item]="item"></item>
  </ion-item>
</ion-list>
<p *ngIf="items.length === 0">
  No items.
</p>

 

Step 3: Items Loading Service:

In Angular apps, code logic that deals with external resources should be encapsulated in services.
ItemService in below has a single method load(offset, limit) to load a list of items. Because there can be many items, the load() method only loads a subset of items. Parameters offset and limit are used for pagination: offset specifies the position of the first loaded item in the whole items list, limit specifies the number of loaded items. For example, load(0, 10) means loading the first 10 items. Add the following code in src/providers/ItemService.ts

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { Items } from '../model/Items';

@Injectable()
export class ItemService {
  load(offset: number, limit: number): Observable<Items> {
    return Observable.of({
      offset: 0,
      limit: 0,
      total: 0,
      results: [],
    });
  }
}

ItemService uses decorator factory Injectable, so it’ll be registered to Angular 2’s injector and available for dependency injection to other components

The return type of the load() method is Observable<Items>. Items are the model type we defined already and now we have to update the Items model to add more information related to pagination. The Items type now has both offset and limit properties that match the parameters in the load() method. It also has the property total that represents the total number of items. The property total is optional, because in some cases the total number may not be available. The property results represent the actual array of items.

Updated Item model 

import { Observable } from 'rxjs';
import { Item } from './Item';

export interface Items {
  refresh?: boolean;
  offset: number;
  limit: number;
  total?: number;
  results: Observable<Item>[];
  loading?: boolean;
}

Step 4: Adding the page – Top Stories Page 
Now we can create a new page in the Ionic app to show top stories using ItemsComponent and ItemService. TopStoriesPage class has property items of type Items. The value of this property is passed to the ItemsComponent for rendering.

ionic g page topStories

Add the following line of code in src/pages/top-stories/top-stories.ts file

import { Component, OnInit, OnDestroy } from '@angular/core';
import { NavController } from 'ionic-angular';
import { Subscription } from "rxjs";
import { Items } from '../../model/Items';
import { ItemService } from '../../providers/ItemService';

@Component({
selector: 'page-top-stories',
templateUrl: 'top-stories.html'
})
export class TopStoriesPage implements OnInit, OnDestroy {
  items: Items;
  subscription: Subscription;
  constructor(public navCtrl: NavController, private itemService: ItemService) {}
  
  ngOnInit(): void {
    this.subscription = this.itemService.load(0, 10).subscribe(items =>
      this.items = items
    );
  }

  ngOnDestroy(): void {
    if (this.subscription) {
      this.subscription.unsubscribe();
    }
  }
}

In the ngOnInit() method, the method load() of ItemService is invoked. When the loading is finished, loaded items is set as the value of the property items. TopStoriesPage class also implements the OnDestroy interface. In the method ngOnDestroy(), we use the method unsubscribe() of the Observable subscription to make sure resources are released.

 

Add the following line of code in src/pages/top-stories/top-stories.html file

<ion-header>
  <ion-navbar>
    <button ion-button menuToggle>
      <ion-icon name="menu"></ion-icon>
    </button>
    <ion-title>Top Stories</ion-title>
  </ion-navbar>
</ion-header>
<ion-content padding>
  <items *ngIf="items" [items]="items"></items>
</ion-content>

Important: In above highlight code <items *ngIf=”items” [items]=”items”></items> creates the ItemsComponent and binds the value of the property items to the items in TopStoriesPage class. Because loading items are asynchronous, the value of items is null until the loading is finished successfully. The usage of directive ngIf is to make sure the ItemsComponent is only created after items are loaded.

Step 5: Creating Database – Firebase

npm install angularfire2 --save