Quantcast
Channel: Devdactic – Ionic Tutorials
Viewing all 183 articles
Browse latest View live

Google Sign-In with Ionic and Firebase

$
0
0

Google Sign-In has become one of the most used authentication methods out there, and this is in part because of how easy it is to the users to sign-in across multiple devices without managing passwords.

This is a guest post from Jorge Vergara. You can check out more about Jorge on his blog Javebratt!

In today’s guide, we’re going to set up Google Sign-In with the native picker, meaning that we won’t show our users a browser to log into their Google accounts, instead, they’ll be shown the native Google account selector so they can pick which account to use for your app.

By the way, at the end of this post, I’m going to link a Starter Template that already has Google & Facebook authentication ready to go, all you’d need to do is add your credentials and run npm install.

This app is going to be built using Firebase Authentication for the back-end, and we’re going to break down the process into three steps:

Step #1: Get your credentials from Google.

We need to set-up Google Credentials manager to get API keys so we can communicate with our Firebase app, that way Firebase can handle all the authentication flow.

Step #2: Install the Google native plugin.

We need to install a Cordova plugin to call the native account chooser, in this step we’ll also install the Ionic Native package for Google+, that way we can handle it in a more “Ionic” way.

Step #3: Log In using Google and Firebase.

In this final stage we’ll work on coding our sign-in process, so users can get to our app and use their Google account to sign-in.

To follow along with this tutorial, I’m going to assume you already know how to create a brand new Ionic app and initialize it with Firebase. If you don’t know yet, you can read this post first.

Step #1: Get your credentials from Google.

The first thing we need to do is to set up everything we’re going to need from Google to get our app working. We need to get:

  • A REVERSED_CLIENT_ID from the Google developers console.
  • Enable Google sign-in inside the Firebase console.
  • Take note of the credential so we can install the plugin.

To start generating our credentials, we need to create both iOS and Android apps in the Google Developers Console, let’s start with the iOS app, for that, go to this link.

Log in with the Google account you use for Firebase (if it asks you to log in), and follow the instructions to create the iOS app.

The first thing it’s going to ask you is the app name, as soon as you start typing it’s going to search for your Firebase apps, pick the one you linked to this project.

Then, it’s going to ask you for an iOS Bundle ID, to get this, go into your app’s config.xml file and copy the package id of the app.

It’s the one that looks like io.ionic.starter or something similar (by the way, please, change this to be something custom for your project).

After you add the iOS Bundle ID, click on the button that says Continue to choose and configure services.

Once you move to the next view, it’s going to let you enable services to use for your app. It has Google Sign-In, Analytics, and Cloud Messaging, we’re going to choose Google Sign-In and click on the button that says ENABLE GOOGLE SIGN-IN.

Once enabled it will let you download the GoogleService-Info.plist configuration file, you’ll store this file in your project’s root, right next to the package.json file.

Now it’s time to follow the same process with Android, and it’s mostly the same, you’ll go to this link, and start creating your Android app.

The one difference is that you need to add an Android Signing Certificate SHA-1 to be able to enable Google Sign-In for Android.

Getting the SHA-1 sign-in certificate isn’t difficult, but it does require a bit of command line work.

There are two certificates you’ll need, one for production and one for development, in this tutorial we’ll get the development certificate sign-in, and in the end, I’ll show you where to get the production one.

To get the development certificate, we’re going to open our terminal and type:

$ keytool -exportcert -list -v -alias androiddebugkey -keystore ~/.android/debug.keystore

Where ~/.android/debug.keystore is where 99,99% of the time you’ll find it, if not, check your Android SDK install to see where it is.

It’s going to ask you for a Keystore password. The default one is android** (hopefully you haven’t changed it 😛)

The output from that command is going to give you 3 or 4 certificate fingerprints, copy the SHA1 and paste it in the form where you’re creating the Android app in the developers’ console.

Then click ENABLE GOOGLE SIGN-IN, and it will let you download your Android configuration file, go ahead and move it to the project’s root folder.

And now we’re ready to install the Cordova plugins to get our application working.

Step #2: Install the Google native plugin.

Now that we finished with all the setup, we’re going to open the GoogleService-Info.plist file. Inside that file, locate this bit of code:

<key>REVERSED_CLIENT_ID</key>
<string>com.googleusercontent.apps.LONG_STRING_HERE</string>

That right there is your REVERSE_CLIENT_ID, take note of it, we’ll use it to install the Cordova plugin (NOTE: This is only required for iOS.)

Now open your terminal and install both the plugin and the Ionic native wrapper (do not forget to replace ‘myrecerseclientid‘ with the reversed client id you got from the configuration file).

$ cordova plugin add cordova-plugin-googleplus --save --variable \
 REVERSED_CLIENT_ID=myreversedclientid

$ npm install --save @ionic-native/google-plus

After the CLI finishes installing, go ahead and type

$ cordova prepare

And then, make sure to add the GooglePlus package as a provider in the app.module.ts file:

import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';
import { GooglePlus } from '@ionic-native/google-plus';

@NgModule({
  ...,
  providers: [
    {provide: ErrorHandler, useClass: IonicErrorHandler},
    SplashScreen,
    StatusBar,
    GooglePlus
  ]
})
export class AppModule {}

Now grab a drink, and get ready for the next step where we’ll create the authentication flow.

Step #3: Create the Sign-In Process

Go ahead and open your home.html page (or the page you want to use for sign-in, home is just an example) and change the HTML to look like this:

<ion-header>
  <ion-navbar>
    <ion-title>
      Ionic Blank
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>

  <button ion-button block color="danger" (click)="loginUser()" *ngIf="!userProfile">
    <ion-icon name="logo-googleplus"></ion-icon>
    Login with Google
  </button>

  <ion-item *ngIf="userProfile">
    <ion-avatar item-left>
      <img [src]="userProfile.photoURL">
    </ion-avatar>
    <h2>{{ userProfile.displayName }}</h2>
    <h3>{{ userProfile.email }}</h3>
  </ion-item>
</ion-content>

We’re using

<button ion-button block color="danger" (click)="loginUser()" *ngIf="!userProfile">
  <ion-icon name="logo-googleplus"></ion-icon>
  Login with Google
</button>

To show the login button when there’s no user, and we’re using:

<ion-item *ngIf="userProfile">
  <ion-avatar item-left>
    <img [src]="userProfile.photoURL">
  </ion-avatar>
  <h2>{{ userProfile.displayName }}</h2>
  <h3>{{ userProfile.email }}</h3>
</ion-item>

To display some info if the user is already logged-in.

And now let’s move to the home.ts file to connect everything and get our app working, the first thing we’ll do is to import Firebase and Google plus

import { GooglePlus } from '@ionic-native/google-plus';
import firebase from 'firebase';

Then, we’re going to declare the userProfile variable we’re using in the HTML, and also inject Google Plus into the constructor

userProfile: any = null;
constructor(public navCtrl: NavController, private googlePlus: GooglePlus) {}

Once that’s done, we’re going to create an authentication observable that listens to any auth changes in real-time and respond to them

constructor(public navCtrl: NavController, private googlePlus: GooglePlus) {
  firebase.auth().onAuthStateChanged( user => {
    if (user){
      this.userProfile = user;
    } else { 
        this.userProfile = null;
    }
  });
}

onAuthStateChanged() is listening to Firebase authentication, and every time there’s change it will trigger, if there’s a logged-in user it will assign that user to the userProfile variable, and if there isn’t any user it will mark the userProfile as null.

Now go ahead and create the login function:

loginUser(): void {
  this.googlePlus.login({
    'webClientId': '<Your web client ID>',
    'offline': true
  }).then( res => console.log(res))
    .catch(err => console.error(err));
}

Here’s what’s going on:

  • We’re calling googlePlus.login() to have the cordova plugin call the native account chooser.
  • We’re passing 'offline': true and our webClientId to have the app return an idToken we can later send to Firebase.
  • Our webClientId is the same as our REVERSE_CLIENT_ID but backward, meaning it would look like LONG_STRING_HERE.apps.googleusercontent.com.

Right there we managed to authorize our application with Google Sign-In (or we have a big red error message and don’t know what to do).

Now we’re going to send the idToken to Firebase so we can create a user account using Google Sign-In

loginUser(): void {
  this.googlePlus.login({
    'webClientId': '<Your web client ID>',
    'offline': true
  }).then( res => {
          const googleCredential = firebase.auth.GoogleAuthProvider
              .credential(res.idToken);

          firebase.auth().signInWithCredential(googleCredential)
        .then( response => {
            console.log("Firebase success: " + JSON.stringify(response));)
        });
  }, err => {
      console.error("Error: ", err)
  });
}

Let’s break it down to understand it better

const googleCredential = firebase.auth.GoogleAuthProvider.credential(res.idToken);

This is creating a credential object for the Google Provider with the idToken we got from the function.

And:

firebase.auth().signInWithCredential(googleCredential)
  .then( response => {
    console.log("Firebase success: " + JSON.stringify(response));)
});

This is taking the credential object and signing in our user with Firebase signInWithCredential().

Once the user is successfully signed in, the onAuthStateChanged() will trigger and it will assign everything to the userProfile object.

Now, remember that if you want to use this process in production, you’ll need to go back to the SHA-1 generation step and instead of using the debug key you’ll need to use the production Keystore you created to upload your apps to Google Play.

$ keytool -exportcert -list -v \
-alias androiddebugkey -keystore <path to keystore here>

Next Steps

First of all thank you for giving me your time and attention, if you’re experiencing any issues don’t hesitate to send me a message.

Second, I created a starter template ready to use for both Google and Facebook Sign-In, all you need to do is add your credentials and run npm install.

My starters usually go for $10 bucks, but since you took the time to read this whole post I want you to have it for free, just use the code devdactic at checkout.

Download the Social Authentication Starter.

The post Google Sign-In with Ionic and Firebase appeared first on Devdactic.


How to Combine Ionic Side Menu and Tabs Navigation

$
0
0

The navigation inside Ionic projects is in general quite simple, but once you try to combine different navigation patterns things can get really tricky. Especially one case happens a lot in reality, which is using a side menu and a tab bar navigation together.

Inside this tutorial we will see how we can put together our different view elements in a way that makes sense and works super easily. Some of the code is inspired by the official Ionic conference app, so if you want an even bigger example you can check that repo out.

We will keep things a bit simpler, so in the end we will be able to transition from a dummy login page to our “inside” navigation which combines side menu and tabs.

ionic-side-menu-tabs

Starting our App

We start with a blank template just so we can see what pages and components are needed to put together this kind of navigation. Of course we could also start with the given Ionic templates like side menu or tabs, but I felt starting at zero helps us learn the concepts even more.

We need to create quite a few pages which we will fill with live later on, so go ahead and create everything we need right in the beginning:

ionic start fullNavigation blank
cd fullNavigation
ionic g page login
ionic g page menu
ionic g page tabs
ionic g page tab1
ionic g page tab2
ionic g page special

You can delete the default home/ folder as he doesn’t contain a modules file and we want to use lazy loading. You could also create a file for that inside the folder, but we have generated already everything new so we’re already behind that point now 😉

Next we need to change the entry point of our app to be our new created LoginPage, so open your src/app/app.component.ts and change it to:

import { Component } from '@angular/core';
import { Platform } from 'ionic-angular';
import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';

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

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

Now the app will start with the blank login page. Also if you remove the HomePage folder, make sure to remove the reference to it from the app.module.ts!

The Login Screen

Our login screen is actually just a placeholder for your login, we won’t implement anything like this now. You can check out the login example for more ideas how to build your login!

We only need a button so we can move on from that screen, so open your src/pages/login/login.html and change it to:

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

<ion-content padding>
  <button ion-button full (click)="doLogin()">Login</button>
</ion-content>

The function inside the class will now change our navigation by calling setRoot() which basically resets the navigation instead of pushing a new page onto the stack. Otherwise we would have a back arrow to the login page which we don’t really want.

Go ahead and add this to your src/pages/login/login.ts:

import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';

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

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

  doLogin() {
    this.navCtrl.setRoot('MenuPage');
  }

}

So now after we hit login, we will be on the template for our menu. But there’s no menu yet, so let’s change that.

Building the Menu Navigation

The menu pattern is always the same, we need to define the menu area and also the content area inside a view. In our case we construct the menu items from an array of pages which we will create in the next step.

Each button will automatically close the menu using menuToggle and we also dynamically set the icon and color of the button. Setting the color helps to reflect the changes of the tab bar also inside the menu!

Go ahead and add this menu code to your src/pages/menu/menu.html:

<ion-menu [content]="content">
  <ion-header>
    <ion-toolbar>
      <ion-title>Menu</ion-title>
    </ion-toolbar>
  </ion-header>

  <ion-content>
    <ion-list>
      <button ion-item menuClose *ngFor="let p of pages" (click)="openPage(p)">
          <ion-icon item-start [name]="p.icon" [color]="isActive(p)"></ion-icon>
          {{ p.title }}
        </button>
    </ion-list>
  </ion-content>
</ion-menu>

<!-- main navigation -->
<ion-nav [root]="rootPage" #content swipeBackEnabled="false"></ion-nav>

Now we get to the actually most difficult part of this tutorial, because once we select a menu item we need to change our content view and also make sure our tab bar also marks the correct item.

As said before we will keep an array of pages, and those are of the type PageInterface which we defined above the class.

You will see why each element needs the attributes of that interface once we go through the functions.

Also, we grab a reference to the root navigation by using @ViewChild so we can directly work on that element!

Inside the array we keep 3 pages; two of them will be also tabs and 1 is just a special page which should have no tabs visible.

The important part here are now the 2 functions that are called once we open a page from the side menu and the one to check if the current element should be marked as active. For now add the code to your src/pages/menu/menu.ts:

import { Tab2Page } from './../tab2/tab2';
import { Tab1Page } from './../tab1/tab1';
import { TabsPage } from './../tabs/tabs';
import { Component, ViewChild } from '@angular/core';
import { IonicPage, NavController, Nav } from 'ionic-angular';

export interface PageInterface {
  title: string;
  pageName: string;
  tabComponent?: any;
  index?: number;
  icon: string;
}

@IonicPage()
@Component({
  selector: 'page-menu',
  templateUrl: 'menu.html',
})
export class MenuPage {
  // Basic root for our content view
  rootPage = 'TabsPage';

  // Reference to the app's root nav
  @ViewChild(Nav) nav: Nav;

  pages: PageInterface[] = [
    { title: 'Tab 1', pageName: 'TabsPage', tabComponent: 'Tab1Page', index: 0, icon: 'home' },
    { title: 'Tab 2', pageName: 'TabsPage', tabComponent: 'Tab2Page', index: 1, icon: 'contacts' },
    { title: 'Special', pageName: 'SpecialPage', icon: 'shuffle' },
  ];

  constructor(public navCtrl: NavController) { }

  openPage(page: PageInterface) {
    let params = {};

    // The index is equal to the order of our tabs inside tabs.ts
    if (page.index) {
      params = { tabIndex: page.index };
    }

    // The active child nav is our Tabs Navigation
    if (this.nav.getActiveChildNav() && page.index != undefined) {
      this.nav.getActiveChildNav().select(page.index);
    } else {
      // Tabs are not active, so reset the root page 
      // In this case: moving to or from SpecialPage
      this.nav.setRoot(page.pageName, params);
    }
  }

  isActive(page: PageInterface) {
    // Again the Tabs Navigation
    let childNav = this.nav.getActiveChildNav();

    if (childNav) {
      if (childNav.getSelected() && childNav.getSelected().root === page.tabComponent) {
        return 'primary';
      }
      return;
    }

    // Fallback needed when there is no active childnav (tabs not active)
    if (this.nav.getActive() && this.nav.getActive().name === page.pageName) {
      return 'primary';
    }
    return;
  }

}

First of all when we want to transition to a page, we grab the optional index for that page. Also, we try to get the active child nav, which would mean a reference to the tabs navigation.

If we have both of these information, we can simply use the select() function of the tab bar to select the tab with the according index. Of course the index in our array should reflect the position of the element in the tab bar (we will create the tab bar in the next step).

When the check fails, we more or less reset our root navigation of the content to the new page and pass any additional params to it. This will be the case when we select our special page which has no tab bar, or when we navigate back from that page because then also no tab bar is active (at the moment of the transition).

Any questions so far? Just let me know below, I know this is not super easy.

To mark the active element we again try to grab a reference of the tab bar and check if the selected root is equal to the component specified for the page.

As a fallback for our special page we also make a check on the root navigation and see if the names match, by doing this we can also mark our special page as active correctly!

That was the hard part of this tutorial. If you are still with me, things will get easier from here. Take a moment to go through these 2 functions, initially it also took me a moment (or 2, 3…) to completely understand what’s going on here.

Adding the Tab bar

As we approach the end, we haven’t actually added our tabs, so change this by opening your src/pages/tabs/tabs.html and change it to:

<ion-tabs [selectedIndex]="myIndex">
  <ion-tab [root]="tab1Root" tabTitle="Tab 1" tabIcon="home"></ion-tab>
  <ion-tab [root]="tab2Root" tabTitle="Tab 2" tabIcon="contacts"></ion-tab>
</ion-tabs>

A very classic tab bar implementation with 2 tabs. The only really important thing here is that we are able to dynamically change the selectedIndex of our tab bar!

We will make use of this inside the constructor of this class. If you remember, we create new pages and also pass the params object through, so in case our tab bar is created newly we can access the tabIndex which was passed through from the menu.ts!

By doing this our tabs will be always initialised back with the correct tab to be selected. Go ahead and change your src/pages/tabs/tabs.ts to:

import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';

@IonicPage()
@Component({
  selector: 'page-tabs',
  templateUrl: 'tabs.html',
})
export class TabsPage {

  tab1Root: any = 'Tab1Page';
  tab2Root: any = 'Tab2Page';
  myIndex: number;

  constructor(navParams: NavParams) {
    // Set the active tab based on the passed index from menu.ts
    this.myIndex = navParams.data.tabIndex || 0;
  }
}

Now we only need to add a menu button to all of our pages so we can toggle the menu from everywhere. Go through these 3 files and change all of them like in the example below, perhaps change the content and title so you can actually see that a different page is displayed!

Files to change:

  • src/pages/tab1/tab1.html
  • src/pages/tab2/tab2.html
  • src/pages/special/special.html

The general HTML template for these files:

<ion-header>
  <ion-navbar>
    <ion-buttons start>
      <button ion-button menuToggle>
      <ion-icon name="menu"></ion-icon>
    </button>
    </ion-buttons>
    <ion-title>Special</ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  This page has no tabs!
</ion-content>

Now all you need to do is run the app and enjoy your full side menu with tabs navigation!

Conclusion

It’s a bit tricky to get all the features working when combining different navigation patterns. But the important thing here is it is definitely possible, and the result is exactly what the behaviour should look like!

Leave a comment below if you enjoyed this a bit different tutorial from what I normally do on some of the more “core” aspects of Ionic!

You can also find a video version of this article below with even more explanation!

Happy Coding,
Simon

The post How to Combine Ionic Side Menu and Tabs Navigation appeared first on Devdactic.

Case Study: Realtime Image Sharing App with Ionic and Firebase

$
0
0

We often go through various code examples for different use cases with Ionic here, but we seldom talk about building real world apps, where we most certainly encounter different problems then inside our little sandbox tutorials.

Inside this post I’ll give you an overview about a recent Ionic app I developed. There are always different phases when you take a new project, and each of these stages is important (besides the blocker phase, nobody needs that one).

If you want you can also check out the app below and leave me a good rating 🙂

iOS: https://itunes.apple.com/de/app/picu/id1247256765
Android: https://play.google.com/store/apps/details?id=com.devdactic.picu

The Idea Phase

This year my fiancé and I are getting married, and we wanted an easy way for all guests of our wedding to share the pictures they have taken with us.

We don’t want to collect each image from everyone afterwards or ask what they have taken at the party, we just wanted one place where everyone could upload their images on their own so other people can see these images and download them.

Sounds pretty easy, right?

It isn’t that hard, but I also wanted to make this a bit more “white-label” or a general solution and not only specific for weddings.

We all have some big events, from work, birthday parties or whatever happens where we take images. And we don’t need another spammy WhatsApp group with people we actually don’t know.

Therefore, this idea needed to become a solution to this problem.

The Feature Outline

As you don’t want random people to see your images, you need some sort of authentication.

Once people are inside the app, they should be able to join groups and, what I initially left out, also create groups.

Inside a group they should be somehow able to share from their library or take an image and add an optional description if they want to.

The image below was the actual guideline I wrote myself inside my notes app.
wedding-app-outline

All the clear?

Sounds like a funny little app to work on!

BTW: Before you ask, my current note taking app is Bear. I left Evernote because Bear felt a bit more lightweight and so far I love it.

The Technology Stack

You might have already seen it inside the last image, my technology selection was Firebase (+ Ionic of course).

I am not so skilled that I can come up with a fast solution for this idea on my own, and Firebase is offering exactly what we need:

  • Authentication: Easily create users or use a social login
  • Database: Store information to these groups, users and images
  • Storage: Upload the images taken by users to the Firebase Storage

So all we need is out there, we just need to use it! And although Jorge is the Firebase expert, I somehow managed to get everything working in the end!

Firebase Data Structure

Structuring your data in the right way is a critical point when using Firebase. I’m not suer if this way is right, but it felt good at most stages so I’m quite comfortable with it.

Inside the Database we have 2 major groups: groups and userProfile.

After registration, the profile for each user is created with it’s firebase UID and his name and email is saved. Also, whenever a user joins an image group this group is added to his own groups.
wedding-app-profile

Inside that object the key and the clear name of that group is stored. This helps us to show a list to the user with all of his groups without making a big query over the other object inside the database!

But that node was kinda easy, the hard thing was implementing the groups. The groups need some information about themselves and they need to have multiple images.

Each image has again some specific information, at the time writing this I think perhaps the images could be completely separate from group so the groups object is not getting to fat.. Any thoughts on that?
wedding-app-groups

This was my final solution which works until now also very good. Until now we have been very theoretical, so let’s see some code highlights!

Implementation Highlights

The overall structure of the app is not really hard. From the image below you can see most of my files, all the logic concerning firebase is inside one provider. I used a progress bar inspired by Josh and a validator for email validation from Jorge. Follow those blogs if you are not doing it by now!

wedding-app-structure

Image Thumbnails & Upload

One of the most challenging tasks was the creation of a thumbnail I haven’t found a good solution so I hacked together something using the canvas. If you are interested in my solution, let me know below and I’ll make a new tutorial from that because there is quite some code involved..

Also, uploading the images and updating the data needed to be chained somehow to cover everything in one big async action. The code to upload the image data looked for example like this:

uploadImageData(imageData, meta) {
    let imgName = this.firebaseProvider.user.uid + '_' + new Date().getTime();

    this.createThumbnail(imageData, _data => {
      let cleanedData = imageData.replace('data:image/jpeg;base64,', '');
      let cleanedDataPrev = _data.replace('data:image/jpeg;base64,', '');
      this.uploadActive = true;

      let uploadTask = firebase.storage().ref(this.groupKey).child(imgName + '.png').putString(cleanedData, 'base64', { contentType: 'image/png' });

      uploadTask.then(data => {
        let fullUrl = data.downloadURL;
        let uploadThumbTask = firebase.storage().ref(this.groupKey).child(imgName + '_prev.png').putString(cleanedDataPrev, 'base64', { contentType: 'image/png' });

        uploadThumbTask.then(res => {
          let thumbUrl = res.downloadURL;
          let imgInf = {
            'imageURL': fullUrl,
            'thumb': thumbUrl,
            'owner': this.firebaseProvider.user.uid,
            'createdAt': new Date().getTime(),
            'username': this.userName,
            'desc': meta['desc'],
            'place': meta['coords']
          }
          this.firebaseProvider.storeImageData(this.groupKey, imgInf);
        });
      });

      uploadTask.on(firebase.storage.TaskEvent.STATE_CHANGED, snapshot => {
        var percent = snapshot.bytesTransferred / snapshot.totalBytes * 100;
        this.loadProgress = Math.floor(percent);
        if (this.loadProgress === 100) {
          this.uploadActive = false; +
            this.presentToast('Neues Bild hochgeladen!');
        }
      });
    });
  }

There is a solution for everything! It took a few hours but if you implement one step after each other it just works.

Also I hooked into the STATE_CHANGED to get the upload progress of the task to upload our progress bar!

Of course you can’t see all the other functions now, if you want to see more code of this or specific parts just let me know, I could come up with like 5 new tutorials just around the learnings of this app.

This is also a reason why I added learning projects and challenges to the Ionic Academy: Once you actually put stuff into action and hit problems, you learn a lot faster!

Joining Groups

To join groups we also needed some functions and some of them were Observables, some Promises, and some a special Firebase Observable which could not be converted to a Promise easily. For that problem I found a great solution to convert a Thenable to Observable here.

addUserToGroup(groupName, groupKey): Observable < any > {
  let groupData = {
    [groupKey]: groupName
  }

    return this.afd.object('/userProfile/' + this.user.uid + '/groups/' + groupKey)
    .take(1)
    .flatMap(obj => {
      if (obj.$exists()) {
        return Observable.create(observer => {
          observer.next({ success: false, msg: 'Du bist bereits Mitglied dieser Gruppe!' })
          observer.complete();
        })
      } else {
        let obs = Observable.fromThenable(this.afd.object('/userProfile/' + this.user.uid + '/groups/' + groupKey).set(groupName));
        return obs.flatMap(res => {
          let data = {
            [this.user.uid]: true
          }
          return this.afd.object('/groups/' + groupKey).update(data).then(res => {
            return { success: true, msg: 'Du bist erfoglreich der Gruppe beigetreten' };
          });
        })
      }
    });
}

Although you won’t understand the German messages, you can see from the success field if the result is ok. If you want some kind of response to a user like showing a toast you need to handle all this async stuff and still pass the right result in the end back, so it’s here a combination of take() and flatMap() and other stuff to achieve the final result (hell this took me long..).

You can really do all kind of freaky stuff with Rxjs so getting a good understanding of all the functions is definitely recommended!

Custom Styling

Of course we don’t want the out of the box styling when we build a real world app, so using Ionic Theming makes it really easy to change every part of your app.

One piece that was not working so super easy was overriding the header bar color with a gradient. In my case, the solution looks like this:

.header-ios .toolbar-background-ios {
  background: linear-gradient(to right, #6541e8 10%,#cf48c6 100%);
}

.header-md .toolbar-background-md {
  background: linear-gradient(to right, #6541e8 10%,#cf48c6 100%);
}

The problem is that we cannot set this gradient function to the Sass value Ionic offers for styling the header. Meh!

Besides that, the rest of thy styling was easy going. Especially making a modal with transparent background can give you really great results!

Development Blockers

All of what I described was solved in some way and took a reasonable amount of time (hello Rxjs!), but 2 super specific errors followed me until the end through the development.

Uncaught (in promise): removeView was not found

For some reasons, pages were created and removed to fast and I always tried to show a loading indicator when asynchronous operation was going on and dismissed it when it was finished.

But sometimes this error came up every now and then:
weddin-app-remove.view

The solution here was two folded. First, make sure that you are showing and dismissing the loading correctly and not too often. But what’s more important, is this piece of code:

presentLoading() {
  this.loader = this.loadingCtrl.create({
    dismissOnPageChange: true
  });
  this.loader.present();
}

By adding dismissOnPageChange to the creation of each loading Ionic takes care of dismissing it when the page leaves. You don’t always have to do everything on your own!

Cannot read property ‘push’ of null

The second one was more related to Firebase and active subscriptions. Also, it just came up like every second time when loading but it was still nasty.
wedding-app-subscription-error

The problem is that you are sometimes still subscribed to an Observable when the page leaves, leaving something behind that should not be there.

The solution was to store our subscription and to manually take care of unsubscribing once the ionViewWillUnload event is called like this:

this.subscription = this.groups.take(1).subscribe(data => {
          // Some action...
});

ionViewWillUnload() {
  if (this.subscription) {
    this.subscription.unsubscribe();
  }
}

Perhaps not the most obvious solution, but it’s ok to sometimes take care of your own garbage, right?

Submission Process

I wont cover the full submission process here, there is just one thing that helps me all the time when it comes to creating the store entries:

Sketch to App Store template FTW!

With this Sketch template, you can take a few screenshots, select a background image and add some texts to a few fields and the template will create all of your screenshots for both Android and iOS!wedding-app-sketch

I don’t know how many hours this has saved me, so give it a try next time you rage about all the app screenshot sizes. But of course you need the paid app Sketch for this.

The Result

I’m really happy with the result and hope everything works on our wedding day. Perhaps you can give the app a try and report if you find any mature bugs! The links are again below.

iOS: https://itunes.apple.com/de/app/picu/id1247256765
Android: https://play.google.com/store/apps/details?id=com.devdactic.picu

I hope this post gave you a good idea of a “real world” app besides all code examples and dummy todo lists!

Building your own stuff helps you to learn everything so much faster. Of course it takes time, but you have to enjoy the process!

You hit problems, you find solutions – and that’s what we really like about coding, isn’t it?

Happy Coding,
Simon

The post Case Study: Realtime Image Sharing App with Ionic and Firebase appeared first on Devdactic.

How to Make iOS In-App Purchases Inside Ionic

$
0
0

Many apps offer them, and the most successful apps live from it: In-app purchases. Who has never bought some coins, crystals or anything in his favorite game?

In this article we will learn how to integrate iOS in-app purchases into our Ionic app. Perhaps I’ll do a second part on Android as well, the process is very similar so you might even already draw your ideas from the concepts and code inside this article.

We will create 3 dummy items which we can buy, and we will reflect those changes inside our app just like in the image below!

ionic-in-app-purchase

But before we achieve all of this we have to go through a few steps to set up everything accordingly.

Create the In App Purchases on iTunes Connect

Tu offer in-app purchases inside your app, you first of all need to create the purchasable item on iTunes Connect. This also means that you need to have an app on iTunes connect!

But if you want you can create one now, for the example I just created the items on one of my apps but never released the items, so you can test all of this without any fear of showing these random purchase options to the users of your app.

Anyway, once you are inside ITC navigate to the Features tab of your app and on the left menu you will now see In-App Purchases.

Inside that screen create a new in-app purchase and you will get asked what type of item you want to create:

  • Consumable: Something you add to the user account like coins
  • Non-Consumable: Something that only needs to be used once, like unlocking a level
  • Auto-Renewable Subscription:< Monthly (or other period) subscription for a service/li>
  • Non-Renewing Subscription: Access to something for a specific duration

Pick whatever you like, for the app we will see later I created each one of the first three. On the following screen you will have to insert all kind of information about your purchasable item:

ios-iap-example

There is nothing really you need to worry about on that screen, just fill in all the fields so that it looks right to you.

After adding my 3 items, the overview of my in-app purchase items looked like in the image below.
ios-iap-overview

Now we are ready to actually call and purchase these items from our app!

Setup our App

Although I added the items to an app which is already live, I started a blank new Ionic app and later just used the bundle ID from the official app.

For now you can do the same, so start your new app and add the Cordova plugin for in-app purchases:

ionic start devdacticPurchase blank
cd devdacticPurchase
ionic cordova plugin add cordova-plugin-inapppurchase
npm install --save @ionic-native/in-app-purchase

To make use of it we also have to include it inside our module in src/app/app.module.ts:

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 { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { InAppPurchase } from '@ionic-native/in-app-purchase';

@NgModule({
  declarations: [
    MyApp,
    HomePage
  ],
  imports: [
    BrowserModule,
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage
  ],
  providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler},
    InAppPurchase
  ]
})
export class AppModule {}

Now as said before, I created all the purchase items on an app that is already live. You also have to set the right bundle ID inside your config.xml so your app knows where it has to look for your items.

<widget id="com.devdactic.crossingnumbers" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">

Once your ID looks fine you might need to remove and add the iOS platform, but now we are finally good to go to purchase our own stuff.

Implement In App Purchases

The flow of an in-app purchase is a bit different from what you might think. Actually, we start the process by loading information about our products!

But we still need the Product ID of our in-app purchases, not sure why this can’t work without them. You can find it on the overview of your items, it’s the one key you added when creating your product.

So we use these IDs inside an array to query the information about our items. Once this is done, we assign the information to an array so our view can later display all of that information!

When we buy a product, we simply call the buy() function of the plugin and afterwards make sure to handle the successful purchase in our app. This means, enable whatever the user just bought!

Also Apple insist on a “Restore” button somewhere inside your app to give users the chance to restore their historic purchases, for example if they already unlocked those level before you need to give him access to this feature again but this time without payment.

All of this can be found inside our class below, so open your src/pages/home/home.ts and change it to:

import { Component } from '@angular/core';
import { NavController, Platform } from 'ionic-angular';
import { InAppPurchase } from '@ionic-native/in-app-purchase';

const MONTHLYLVL_KEY = 'com.devdactic.crossingnumbers.monthlylevels';
const CRYSTALS_KEY = 'com.devdactic.crossingnumbers.100crystal';
const GAMEMODE_KEY = 'com.devdactic.crossingnumbers.specialgamemode';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {
  products = [];
  previousPurchases = [];
  crystalCount = 0;
  specialGame = false;
  monthlySub = false;

  constructor(private iap: InAppPurchase, private plt: Platform) {
    this.plt.ready().then(() => {
      this.iap.getProducts([MONTHLYLVL_KEY, CRYSTALS_KEY, GAMEMODE_KEY])
        .then((products) => {
          this.products = products;
        })
        .catch((err) => {
          console.log(err);
        });
    })
  }

  buy(product) {
    this.iap.buy(product).then(data => {
      this.enableItems(product);
    })
  }

  restore() {
    this.iap.restorePurchases().then(purchases => {
      this.previousPurchases = purchases;
      // Unlock the features of the purchases!
      for (let prev of this.previousPurchases) {
          this.enableItems(prev.productId)
      }
    });
  }

  enableItems(id) {
      // Normally store these settings/purchases inside your app or server!
      if (id === CRYSTALS_KEY) {
        this.crystalCount += 100;
      } else if (id === GAMEMODE_KEY) {
        this.specialGame = true;
      } else if (id === MONTHLYLVL_KEY) {
        this.monthlySub = true;
      }
  }
}

Of course you don’t want to use my IDs for the items, they shouldn’t work for you so make sure to insert your own Product IDs here!

The last missing piece is the view, which is kinda simple given that our class already holds all the information and functions.

We display each of our in-app purchase items as a card with the title and description and the buy button. Here we use the price we get from Apple, so don’t rely on your internal numbers and take the information we receive from the plugin!

The list of previous purchases is just for our own information and some debugging, and at the top I added a few “resources” that we can purchase/activate like in a classic game.

Go ahead and change your src/pages/home/home.html to:

<ion-header>
  <ion-navbar>
    <ion-title>
      Ionic In App
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  <ion-row text-center>
    <ion-col>Crystals:<br> <b>{{ crystalCount }}</b></ion-col>
    <ion-col>Special Mode: <b>{{ specialGame }}</b></ion-col>
    <ion-col>Monthly Subscription: <b>{{ monthlySub }}</b></ion-col>
  </ion-row>

  <ion-card *ngFor="let product of products">
    <ion-card-header>{{ product.title }}</ion-card-header>
    <ion-card-content>
      {{ product.description }}
    </ion-card-content>
    <ion-row>
      <ion-col>
        <button ion-button block (click)="buy(product.productId)">
          Buy now - {{ product.price }}
        </button>
      </ion-col>
    </ion-row>
  </ion-card>

  <button ion-button full icon-left color="secondary" (click)="restore()">
  <ion-icon name="refresh"></ion-icon>Restore
</button>

  <ion-card *ngFor="let prev of previousPurchases">
    <ion-card-header>Purchased {{ prev.date | date:'short' }}</ion-card-header>
    <ion-card-content>
      {{ prev.productId }}
    </ion-card-content>
  </ion-card>

</ion-content>

That’s all for the code side! If you run your app on an iOS device now and everything is connected accordingly you will see your in-app purchases right inside your app.

Now we only need a way to test it…

Testing In App Purchases

Of course you don’t want to spend your real money on test purchases, but we can create a Sandbox user for this!

Right inside your iTunes Connect account you can navigate to Users and Roles and at the top select the tab Sandbox Testers.

On this screen you can add new users which are Sandbox users, you can also follow the official guide from Apple.

Once your are done you should have at least one user on this screen.

ios-iap-sandbox-tester

If you now start your app, you are perhaps still logged in as yourself, so got to iPhone Settings -> iTunes & App Stores and sign out from your account there.

Now you can log in from your app once you purchase something with the new Sandbox user!

Conclusion

There are quite a few steps involved to add in-app purchases, but it’s definitely not as hard as you might think.

These purchases are a super efficient way to generate money from your free apps and work better than apps almost ever. So if you have some feature you want to unlock, add it as an in-app purchase to your Ionic app now!

You can find a video version of this article below.

Happy Coding,
Simon

The post How to Make iOS In-App Purchases Inside Ionic appeared first on Devdactic.

Learning Ionic: The State of the Ionic Academy

$
0
0

Ionic is growing every day, more and more companies bet on cross-platform apps over native development and the need for developers skilled with Angular is continuously rising.

Early in 2017 I set out to give people interested in learning Ionic a better way to achieve their goals faster with more support. The idea for the Ionic Academy was born.

But how has the Ionic Academy evolved until now?

How have the first few months worked out for the members?

What can people expect in the future?

This article will give you a more detailed picture of whats going on behind the scenes and if you should consider to join the Ionic Academy.

The Original Idea

The first announcement was made in January 2017, and I first spoken open about in the this post about the idea and the technical details of running a membership site.

From the technical point of view the Ionic Academy is exactly like I wanted it to be. All building blocks were included since the early days, which means the learning system with courses, the paywall and even the private community are working flawless.

The videos are hosted on Vimeo and the code for courses and the projects is hosted on AWS.

So far the overall learning approach has not really changed. Members can take different courses, and most courses have an additional project attached at the end to take action with your just learned skills.

If problems arise, the community and Slack channel offer quick help, so people feel never alone or lost while learning.

The Library

academy-courses-prev
The Academy launched with about 9 ready courses, while we currently already have 20 courses!

The initial library was way to small so I worked off all the open hot topics like Firebase or Ionic Cloud Services as fast as possible for the members who already joined in the launch week (I’m super thankful for each of you!) and currently release new courses about every 2 weeks.

Of course this might slow down especially if there are critical changes that needs to be fixed inside the existing content. Currently this process works very smooth as members mostly report if something is not working so it can be fixed soon for the next person going through the material.

There is no final number of courses, the library will grow and grow because there are infinite interesting topics that are worth to be covered.

The Community

The community has over 700 posts by now and while the initial exchange between people has slowed down a bit, it’s still the best place to ask questions while learning.

Besides the community on the side there is also a Slack channel for all members of the Ionic Academy. It is not used heavily but offers a way of getting a quick response to a question or just an exchange of ideas and links.

Overall this part has still a lot of room for improvement, but you can’t force people to be active inside a community/channel. This is one of the areas that still needs to grow a bit to become more mature.

Future Development

academy-challenge
It feels like there’s always something to do. In the future of course more course material and projects will be released to cover even more interesting topics that people are interested in.

But the work is not only on the material, it’s also on the learning form.

In June we started experimenting with monthly challenges which are not yet working as expected. There are a few more ideas on my list like monthly hangouts or live webinar sessions. There has been the idea to have an app (with Ionic) for the whole membership so people can download all the material.

We will continue with trying out different ideas to see what works best for the members.

How much work is a Membership site?

More than you wish and less than you might think.

At the beginning of the Academy I quit my job to work full-time on this blog and other projects I have. The Ionic Academy takes at least 50% of that time.

It’s not only the creation of courses, it’s all the other hats you have to wear: Marketing, Outsourcing tasks, pleasing German taxes (this is especially hard), planning new features, writing content, keeping up with the problems of members, answering questions…

If I still had a full-time job this wouldn’t be possible I’m sure.

But besides all those tasks that always come up, sometimes you can just enjoy a round of your favorite game as nothing urgent is open.

So it’s not “passive income” like a book where all the work is upfront, but currently I enjoy all the tasks (all but the taxes stuff) and wouldn’t trade it for any other job.

Members

Initially I thought the target group would be young male people after college or inside their job and web developers getting into mobile.

Well, that’s not true. Actually, many people inside the Academy are 50+ years, some even 60+!

I have an unbelievable amount of respect for these people. Some just want to get back into some “doing”, some enjoy technologies, others have been learning all their life new software development stuff.

Regarding countries I have no statistics but drawing from the introduction section of the community people from all over the world have introduced them. Some living in cities next to me in Germany while others are from Canada or Singapore.

It’s amazing to see people from everywhere come together in learn in one platform, so hopefully I’ll get the chance to meet at least some of them in person if I make it to their countries some day.

Why should I join?


Perhaps you came across the page before and were not sure if it’s for you. And you are right, the Ionic Academy is not for everyone.

You have to be willing to learn and interested in Ionic, and you can’t only consume but never work on your skills.

How the Ionic Academy can help you?

If you want to get started with Ionic but feel you are lost after the quick start guide, the Ionic Academy offers a roadmap which takes you through different courses ranging from beginner to expert.

You can expect more support for your problems and questions as well as a friendly place for just some talking.

And of course you can participate and talk about the courses and material that you would like to see in the future.

The Ionic Academy is growing, evolving and I’m super happy for everyone who supports me on this journey!

I’d love to meet you inside.

Happy Coding,
Simon

The post Learning Ionic: The State of the Ionic Academy appeared first on Devdactic.

Building an Ionic Fitness App with iOS HealthKit Integration

$
0
0

If you want to create the next killer lifestyle fitness app, chances are high you want to log your measurements into the official Health app on iOS.

With the help of a Cordova plugin we can easily access the health data of a user to read and write the information our app needs.

In this tutorial we will read and set the height of a user, check his 24 hour step count (I know it’s embarrassing sometimes) and also log a specific type of workout to the iOS Health app.

ionic-health-app

The hardest part is actually figuring out which permissions you need and what parameters we need to pass to the health app, but we will go through all of it step by step.

Setting up our HealthKit App

First of all we need the Cordova plugin and the according Ionic native plugin to access the iOS Health app. There is another package which tries to cover both iOS and Android behaviour which I haven’t tested so far, perhaps you have some experience with it?

Anyway, we start our app and setup the plugin so go ahead and run:

ionic start devdacticHealth blank
cd devdacticHealth
ionic cordova plugin add com.telerik.plugins.healthkit
npm install --save @ionic-native/health-kit

Like always we need to import it to our module as well, so open your src/app/app.moudle.ts and insert:

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 { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';

import { HealthKit } from '@ionic-native/health-kit';

@NgModule({
  declarations: [
    MyApp,
    HomePage
  ],
  imports: [
    BrowserModule,
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage
  ],
  providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler},
    HealthKit
  ]
})
export class AppModule {}

Also, when you build your app through Xcode you have to make sure that the Capabilities for HealthKit are enabled like in the image below!

ionic-healthkit-permission

Reading and Writing HealthKit Data

Now we got the basics and can start to code the actual app. Our view is quite unspectacular, we need an input to set our height and 2 more fields that will be readonly to show some data.

We will also craft a list of cards for all the workouts we get back from the Health app below, but this list might be empty in the beginning for your so don’t worry.

For now open your src/pages/home/home.html and change it to:

<ion-header>
  <ion-navbar>
    <ion-title>
      Devdactic Health
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content>
  <ion-item>
    <ion-label stacked>Set Height</ion-label>
    <ion-input type="text" [(ngModel)]="height" placeholder="My Height today (in cm)"></ion-input>
  </ion-item>
  <ion-item>
    <ion-label stacked>Current Height</ion-label>
    <ion-input type="text" [(ngModel)]="currentHeight" readonly></ion-input>
  </ion-item>
  <ion-item>
    <ion-label stacked>Steps last 24h</ion-label>
    <ion-input type="text" [(ngModel)]="stepcount" readonly></ion-input>
  </ion-item>

  <button ion-button full (click)="saveHeight()">Set Height</button>
  <button ion-button full (click)="saveWorkout()">Set a Workout</button>

  <ion-list>
    <ion-card *ngFor="let workout of workouts">
      <ion-card-header>{{ workout.calories }}</ion-card-header>
      <ion-card-content>
        <p>Activity: {{ workout.activityType }}</p>
        <p>Duration: {{ workout.duration / 100 }} min</p>
        <p>Date: {{ workout.startDate | date:'short' }}</p>
        <p>Distance: {{ workout.miles }} miles</p>
      </ion-card-content>
    </ion-card>
  </ion-list>
</ion-content>

Now we get to the meat of the HealthKit integration.

In our example we will ask for all the permissions we need upfront. You don’t have to do this and you can wait until you use a call to HealthKit, but if you request different things the pop-up might come up multiple times which can be quite annoying.

In our case we will only see this pop-up once asking for all the needed permissions

ionic-healthkit-access

You can look through the official Apple documentation for some more general information and the names of the permissions which you might need.

I did not find everything so easy there, so you can see all available identifiers on the header file of this repo, which is way easier than going through all the Apple documentation!

Ok enough on permissions, actually setting our height is just one simple call with the new value as amount and also the unit (in our case “cm”).

Setting a workout requires a bit bigger object which we construct upfront. Of course you need to log some information for the workout, but once you got this object the call is again just one simple line!

For now open your src/pages/home/home.ts and insert:

import { HealthKit, HealthKitOptions } from '@ionic-native/health-kit';
import { Component } from '@angular/core';
import { NavController, Platform } from 'ionic-angular';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {
  height: number;
  currentHeight = 'No Data';
  stepcount = 'No Data';
  workouts = [];

  constructor(private healthKit: HealthKit, private plt: Platform) {
    this.plt.ready().then(() => {
      this.healthKit.available().then(available => {
        if (available) {
          // Request all permissions up front if you like to
          var options: HealthKitOptions = {
            readTypes: ['HKQuantityTypeIdentifierHeight', 'HKQuantityTypeIdentifierStepCount', 'HKWorkoutTypeIdentifier', 'HKQuantityTypeIdentifierActiveEnergyBurned', 'HKQuantityTypeIdentifierDistanceCycling'],
            writeTypes: ['HKQuantityTypeIdentifierHeight', 'HKWorkoutTypeIdentifier', 'HKQuantityTypeIdentifierActiveEnergyBurned', 'HKQuantityTypeIdentifierDistanceCycling']
          }
          this.healthKit.requestAuthorization(options).then(_ => {
            this.loadHealthData();
          })
        }
      });
    });
  }

  // Save a new height
  saveHeight() {
    this.healthKit.saveHeight({ unit: 'cm', amount: this.height }).then(_ => {
      this.height = null;
      this.loadHealthData();
    })
  }

  // Save a new dummy workout
  saveWorkout() {
    let workout = {
      'activityType': 'HKWorkoutActivityTypeCycling',
      'quantityType': 'HKQuantityTypeIdentifierDistanceCycling',
      'startDate': new Date(), // now
      'endDate': null, // not needed when using duration
      'duration': 6000, //in seconds
      'energy': 400, //
      'energyUnit': 'kcal', // J|cal|kcal
      'distance': 5, // optional
      'distanceUnit': 'km'
    }
    this.healthKit.saveWorkout(workout).then(res => {
      this.loadHealthData();
    })
  }

  // Reload all our data
  loadHealthData() {
    this.healthKit.readHeight({ unit: 'cm' }).then(val => {
      this.currentHeight = val.value;
    }, err => {
      console.log('No height: ', err);
    });

    var stepOptions = {
      startDate: new Date(new Date().getTime() - 24 * 60 * 60 * 1000),
      endDate: new Date(),
      sampleType: 'HKQuantityTypeIdentifierStepCount',
      unit: 'count'
    }

    this.healthKit.querySampleType(stepOptions).then(data => {
      let stepSum = data.reduce((a, b) => a + b.quantity, 0);
      this.stepcount = stepSum;
    }, err => {
      console.log('No steps: ', err);
    });

    this.healthKit.findWorkouts().then(data => {
      this.workouts = data;
    }, err => {
      console.log('no workouts: ', err);
      // Sometimes the result comes in here, very strange.
      this.workouts = err;
    });

  }
}

The final function we haven’t touched yet is the one that reloads our data whenever we update something or inside the constructor.

Again, some data can be received quite simple like the height here. For the step count we need to create a SampleType query with a timeframe, the units and the actual sample type which is HKQuantityTypeIdentifierStepCount for the user step count.

Also, we get back an array of step entries so we need to sum up this array to get to our final step count!

Finally loading the workouts is just one call, but in my example I got the actual array data inside the error block back. I haven’t figured out why this happens, perhaps just a problem on my side but that’s why I also set the workouts array inside the error completion block.

All of data lives of course not only inside our app, you can open your Health app on the device and check the metrics we have changed like the health or the workouts of a user. You will find that there is a new workout added which will look somehow like this:
ionic-healthkit-app

So now our data is persisted inside the official Health app on the device of a user!

Conclusion

A “super native” functionality like HealthKit integration can be accessed quite easily through Ionic and allows us to build fitness apps that talk directly with the core metrics of a users device!

To make the approach even more cross-platform we need to evaluate the plugin which covers Android as well, but perhaps anyone has already given that one a try?

You can also find a video version of this article below.

Happy Coding,
Simon

The post Building an Ionic Fitness App with iOS HealthKit Integration appeared first on Devdactic.

Ionic Realtime Chat with Socket.io

$
0
0

There are many ways to build a chat application with Ionic. You could use Firebase as a realtime database, or you can use your own Node server with some Socket.io, and that’s what we gonna do today.

In this tutorial we will craft a super simple Node.js server and implement Socket.io on the server-side to open realtime connections to the server so we can chat with other participants of a chatroom.

This is a super lightweight example as we don’t store any of the messages on the server – if you are nor in the chat room you will never get what happened. The result will look like in the image below.

socket-chat

Building our Node Server

This tutorial starts with the actual backend for our app. It’s good to have some Node skills so you could potentially build your own little backend from time to time. First of all we create a new folder and inside an NPM package file, where we also install Express as a minimal framework for our backend:

mkdir SocketServer && cd SocketServer
npm init
npm install --save express socket.io

After creating and installing you should have a package.json inside that file. Make sure inside that file the main file is set to index.js so the scripts knows which file to start!

{
  "name": "socket-server",
  "version": "1.0.0",
  "main": "index.js",
  "dependencies": {
    "express": "^4.15.3",
    "socket.io": "^2.0.3"
  }
}

Finally we got the actual index.js which contains the logic of our server. As said before, we stay really low here and just implement some basic socket functions!

All of our functions are wrapped inside the io.on('connection') block, so these will only happen once a client connects to the server.

We set a function for the events disconnect, set-nickname and add-message which means whenever our app sends out these events the server does something.

If we send a new message, we emit that message to everyone connected as a new object with text, the name of the sending user and a date. Also, we set the name of the socket connection if a users send his nickname.

Finally if a user disconnects, we inform everyone that somebody just left the room. Put all of that code into your index.js:

let app = require('express')();
let http = require('http').Server(app);
let io = require('socket.io')(http);

io.on('connection', (socket) => {
  
  socket.on('disconnect', function(){
    io.emit('users-changed', {user: socket.nickname, event: 'left'});   
  });

  socket.on('set-nickname', (nickname) => {
    socket.nickname = nickname;
    io.emit('users-changed', {user: nickname, event: 'joined'});    
  });
  
  socket.on('add-message', (message) => {
    io.emit('message', {text: message.text, from: socket.nickname, created: new Date()});    
  });
});

var port = process.env.PORT || 3001;

http.listen(port, function(){
   console.log('listening in http://localhost:' + port);
});

Your node backend with Socket.io is now ready! You can start it by running the command below and you should be able to reach it at http://localhost:3001

node index.js

Starting the Ionic Chat App

Inside our Ionic chat app we need 2 screens: On the first screen we will pick a name and join the chat, on the second screen is the actual chatroom with messages.

First of all we create a blank new Ionic app and install the ng-socket-io package to easily connect to our Socket backend, so go ahead and run:

ionic start devdacticSocket blank
cd devdacticSocket
npm install ng-socket-io --save
ionic g page chatRoom

Now make sure to add the package to our src/app/app.module.ts and pass in your backend URL as a parameter:

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 { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { SocketIoModule, SocketIoConfig } from 'ng-socket-io';
const config: SocketIoConfig = { url: 'http://localhost:3001', options: {} };

@NgModule({
  declarations: [
    MyApp,
    HomePage
  ],
  imports: [
    BrowserModule,
    IonicModule.forRoot(MyApp),
    SocketIoModule.forRoot(config)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage
  ],
  providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler}
  ]
})
export class AppModule {}

Your app is now configured to use the backend, so make sure to run the backend when launching your app!

Joining a Chatroom

First of all a user needs to pick a name to join a chatroom. This is just an example so we can actually show who wrote which message, so our view consists of the input field and a button to join the chat. Open your src/pages/home/home.html and change it to:

<ion-header>
  <ion-navbar>
    <ion-title>
      Join Chat
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  <ion-item>
    <ion-label stacked>Set Nickname</ion-label>
    <ion-input type="text" [(ngModel)]="nickname" placeholder="Nickname"></ion-input>
  </ion-item>
  <button ion-button full (click)="joinChat()" [disabled]="nickname === ''">Join Chat as {{ nickname }}</button>
</ion-content>

Inside the class for this view we have the first interaction with Socket. Once we click to join a chat, we need to call connect() so the server recognises a new connection. Then we emit the first message to the server which is to set our nickname.

Once both of this happened we push the next page which is our chatroom, go ahead and change your src/pages/home/home.ts to:

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { Socket } from 'ng-socket-io';

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

  constructor(public navCtrl: NavController, private socket: Socket) { }

  joinChat() {
    this.socket.connect();
    this.socket.emit('set-nickname', this.nickname);
    this.navCtrl.push('ChatRoomPage', { nickname: this.nickname });
  }
}

If you put in some logs you should now already receive a connection and the event on the server-side, but we haven’t added the actual chat functionality so let’s do this.

Building the Chat functionality

To receive new chat messages inside the room we have a function getMessages() which returns an observable. Also, this message listens to Socket events of the type ‘message’ and always calls next() on the observable to pass the new value through.

Whenever we get such a message we simply push the new message to an array of messages. Remember, we are not loading historic data, we will only get messages that come after we are connected!

Sending a new message is almost the same like setting a nickname before, we simply emit our event to the server with the right type.

Finally, we also subscribe to the events of users joining and leaving the room and display a little toast whenever someone comes in or leaves the room. It’s the same logic again, with the socket.on() we can listen to all the events broadcasted from our server!

Go ahead and change your app/pages/chat-room/chat-room.ts to:

import { Component } from '@angular/core';
import { NavController, IonicPage, NavParams, ToastController } from 'ionic-angular';
import { Socket } from 'ng-socket-io';
import { Observable } from 'rxjs/Observable';

@IonicPage()
@Component({
  selector: 'page-chat-room',
  templateUrl: 'chat-room.html',
})
export class ChatRoomPage {
  messages = [];
  nickname = '';
  message = '';

  constructor(private navCtrl: NavController, private navParams: NavParams, private socket: Socket, private toastCtrl: ToastController) {
    this.nickname = this.navParams.get('nickname');

    this.getMessages().subscribe(message => {
      this.messages.push(message);
    });

    this.getUsers().subscribe(data => {
      let user = data['user'];
      if (data['event'] === 'left') {
        this.showToast('User left: ' + user);
      } else {
        this.showToast('User joined: ' + user);
      }
    });
  }

  sendMessage() {
    this.socket.emit('add-message', { text: this.message });
    this.message = '';
  }

  getMessages() {
    let observable = new Observable(observer => {
      this.socket.on('message', (data) => {
        observer.next(data);
      });
    })
    return observable;
  }

  getUsers() {
    let observable = new Observable(observer => {
      this.socket.on('users-changed', (data) => {
        observer.next(data);
      });
    });
    return observable;
  }

  ionViewWillLeave() {
    this.socket.disconnect();
  }

  showToast(msg) {
    let toast = this.toastCtrl.create({
      message: msg,
      duration: 2000
    });
    toast.present();
  }
}

The last missing part is now the view for the chatroom. We have to iterate over all of our messages and distinguish if the message was from us or another user. Therefore, we create 2 different ion-col blocks as we want our messages to have some offset to a side. We could also do this only with CSS but I like using the Ionic grid for styling as far as possible.

With some additional styling added to both our and other people’s messages the chatroom will look almost like iMessages or any familiar chat application, so open your src/pages/chat-room/chat-room.html and insert:

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

<ion-content>
  <ion-grid>
    <ion-row *ngFor="let message of messages">
      
      <ion-col col-9 *ngIf="message.from !== nickname" class="message" [ngClass]="{'my_message': message.from === nickname, 'other_message': message.from !== nickname}">
        <span class="user_name">{{ message.from }}:</span><br>
        <span>{{ message.text }}</span>
        <div class="time">{{message.created | date:'dd.MM hh:MM'}}</div>
      </ion-col>

      <ion-col offset-3 col-9 *ngIf="message.from === nickname" class="message" [ngClass]="{'my_message': message.from === nickname, 'other_message': message.from !== nickname}">
        <span class="user_name">{{ message.from }}:</span><br>
        <span>{{ message.text }}</span>
        <div class="time">{{message.created | date:'dd.MM hh:MM'}}</div>
      </ion-col>

    </ion-row>
  </ion-grid>

</ion-content>

<ion-footer>
  <ion-toolbar>
    <ion-row class="message_row">
      <ion-col col-9>
        <ion-item no-lines>
          <ion-input type="text" placeholder="Message" [(ngModel)]="message"></ion-input>
        </ion-item>
      </ion-col>
      <ion-col col-3>
        <button ion-button clear color="primary" (click)="sendMessage()" [disabled]="message === ''">
        Send
      </button>
      </ion-col>
    </ion-row>
  </ion-toolbar>
</ion-footer>

Below our messages we also have the footer bar which holds another input to send out messages, nothing really fancy.

To make the chat finally look like a chat, add some more CSS to your src/pages/chat-room/chat-room.scss:

page-chat-room {
    .user_name {
        color: #afafaf;
    }
    .message {
        padding: 10px !important;
        transition: all 250ms ease-in-out !important;
        border-radius: 10px !important;
        margin-bottom: 4px !important;
    }
    .my_message {
        background: color($colors, primary) !important;
        color: #000 !important;
    }
    .other_message {
        background: #dcdcdc !important;
        color: #000 !important;
    }
    .time {
        color: #afafaf;
        float: right;
        font-size: small;
    }
    .message_row {
        background-color: #fff;
    }
}

Now launch your app and make sure your backend is up and running!

For testing, you can open a browser and another incognito browser like in my example at the top to chat with yourself.

Conclusion

Don’t be scared of Socket.io and a Node backend, you can easily implement your own realtime backend connection without any problems! Firebase seems often like the easy alternative, but actually we were able to build a live chat app with only a super small backend.

To make this a real chat you might want to add a database and store all the messages once you receive them and add some routes to return the history of a chatroom. Or you might want to create different chatrooms and separate conversations, but this post could be the starting point to your own chat implementation!

You can watch a video version of this article below.

Happy Coding,
Simon

The post Ionic Realtime Chat with Socket.io appeared first on Devdactic.

How to use the YouTube REST API with Ionic

$
0
0

If you want to use a Google Service like the YouTube API you have the chance to make requests even without going through a full user authentication and OAuth process.

For some services you can easily register an API key for your application and make signed requests with that key. And that’s what we will do in this tutorial to receive playlist data and video information directly from Google. Our own little YouTube search app!

Getting a Google API Key for the YouTube API

There are a few steps to get to the key, but all of them are very straight forward so go through these new:

  1. Create a Google account or use your existing
  2. Create a project in the Google Developer console
  3. Inside your new project got to Library and search/select YouTube Data API v3 and hit enable
  4. Go to Credentials and click Create API key

This is now the API key we can use to make requests to the YouTube Data API. In case you want to look up some endpoints or a data format there is a very good documentation about the API here, so if you are looking for all features of the API and things you can do take a closer look at the pages there.

We are now ready to transition to our Ionic app to actually use the API!

Setting up our Ionic YouTube App

We start like always with a blank new Ionic app and add a new provider and page. Make sure to create a module file for that page if your CLI is not doing it. Also, we install the Cordova plugin to get access to the native YouTube player so we can later play our videos directly in that app! Now go ahead and run:

ionic start devdacticYoutube blank
cd devdacticYoutube
ionic g provider yt
ionic g page playlist

ionic cordova plugin add cordova-plugin-youtube-video-player
npm install --save @ionic-native/youtube-video-player

The current CLI will not create any module files for our pages anymore, but as we are using lazy loading here you can easily create a module file for each page following the general scheme for that file. You can find an example here in the listing for home-tabs.module.ts!

In order to use the plugin on Android we need to add our YouTube API key from before to our config.xml, so add this entry now:

<preference name="YouTubeDataApiKey" value="[YOUR YOUTUBE API]" />

Finally we need to make sure everything is connected accordingly inside our app.module.ts so change yours to:

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 { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';

import { HttpModule } from '@angular/http';
import { YtProvider } from '../providers/yt/yt';
import { YoutubeVideoPlayer } from '@ionic-native/youtube-video-player';

@NgModule({
  declarations: [
    MyApp,
    HomePage
  ],
  imports: [
    BrowserModule,
    IonicModule.forRoot(MyApp),
    HttpModule
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage
  ],
  providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler},
    YtProvider,
    YoutubeVideoPlayer
  ]
})
export class AppModule {}

Let’s dive into the fun!

Searching for Playlists of a Channel

The first operation our app allows is searching for playlists of a channel. Of course it’s perhaps not a real use case but it’s a good way to demonstrate the functions of the API.

We have created a provider before and we will make all interaction to the API through the provider now. Besides searching for playlists we then also want to get all videos for a playlist so we can drill into a list and display all videos.

Making a request is basically the URL to the according endpoint of the API plus a few parameters, nothing more needed! In our case we need to append the API key, and if you want to specify some other options you can change the response, what will be included or how many items you get back for example.

So our provider is really simple, therefore change your src/providers/yt/yt.ts to:

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

@Injectable()
export class YtProvider {
  apiKey = 'YOURKEY';

  constructor(public http: Http) { }

  getPlaylistsForChannel(channel) {
    return this.http.get('https://www.googleapis.com/youtube/v3/playlists?key=' + this.apiKey + '&channelId=' + channel + '&part=snippet,id&maxResults=20')
    .map((res) => {
      return res.json()['items'];
    })
  }

  getListVideos(listId) {
    return this.http.get('https://www.googleapis.com/youtube/v3/playlistItems?key=' + this.apiKey + '&playlistId=' + listId +'&part=snippet,id&maxResults=20')
    .map((res) => {
      return res.json()['items'];
    })
  }
}

That’s all we need for our example, but of course you can try out all the different other endpoints as well.

Now we need to build the logic to make a call to the API, and we will have a simple view with an input field and search button which triggers a call to the API with the current search value. After getting the results back we want to display them, and finally we add another function to open a specific playlist which will push another page and pass the ID of the clicked list so we can load the next data there.

I also added my channel ID so you can directly hit search if you are implementing this to see some results (and don’t forget to subscribe here!).

Now open your src/pages/home/home.ts and change it to:

import { YtProvider } from './../../providers/yt/yt';
import { Component } from '@angular/core';
import { NavController, AlertController} from 'ionic-angular';
import { Observable } from 'rxjs/Observable';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {
  channelId = 'UCZZPgUIorPao48a1tBYSDgg'; // Devdactic Channel ID
  playlists: Observable<any[]>;

  constructor(public navCtrl: NavController, private ytProvider: YtProvider, private alertCtrl: AlertController) { }

  searchPlaylists() {
    this.playlists = this.ytProvider.getPlaylistsForChannel(this.channelId);
    this.playlists.subscribe(data => {
      console.log('playlists: ', data);
    }, err => {
      let alert = this.alertCtrl.create({
        title: 'Error',
        message: 'No Playlists found for that Channel ID',
        buttons: ['OK']
      });
      alert.present();
    })
  }

  openPlaylist(id) {
    this.navCtrl.push('PlaylistPage', {id: id});
  }
}

The view for our first page is also super simple, just an input field and the button plus the list which uses our Observable playlists from the class which holds the playlists of the channel we searched for.

As we have included some information for each playlist we can display a thumbnail, title of that list and even when the list was published. You can also check the console logs to see more information, it’s quite a big response with many details!

Go ahead and finish the first part by changing your src/pages/home/home.html to:

<ion-header>
  <ion-navbar color="danger">
    <ion-title>
      Playlist Search
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content>
  <ion-item>
    <ion-label stacked>Channel ID</ion-label>
    <ion-input type="text" [(ngModel)]="channelId"></ion-input>
  </ion-item>
  <button full ion-button (click)="searchPlaylists()" [disabled]="channelId === ''" color="danger">Search Playlists</button>

  <ion-list no-padding>
    <button ion-item *ngFor="let list of playlists | async" (click)="openPlaylist(list.id)">
      <ion-thumbnail item-start>
      <img [src]="list.snippet.thumbnails.standard.url">
    </ion-thumbnail>
    <h2>{{ list.snippet.title }}</h2>
    <p>{{ list.snippet.publishedAt | date:'short' }}</p>
    </button>
  </ion-list>
</ion-content>

You should now already be able to search for data and get a result. If it’s not working by now, perhaps wait a few minute if you just enabled the API as this can take like 10 minutes sometimes!

Loading Videos for a Playlist

The last missing piece of the tutorial is displaying the videos of a selected playlists, but it’s super straight forward after our initial efforts.

We get the ID of the page already passed through the navParams so we only need to make another call through our provider to receive all videos for a specific playlist!

Finally we also add a function to open a specific video by its ID through the Cordova plugin we installed before. I also added a fallback for the browser which will just open the video in a new tab so you can easily test the code without a device.

Go ahead and change your src/pages/playlist/playlist.ts to:

import { YtProvider } from './../../providers/yt/yt';
import { Observable } from 'rxjs/Observable';
import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams, Platform } from 'ionic-angular';
import { YoutubeVideoPlayer } from '@ionic-native/youtube-video-player';

@IonicPage()
@Component({
  selector: 'page-playlist',
  templateUrl: 'playlist.html',
})
export class PlaylistPage {
  videos: Observable<any[]>;

  constructor(private navParams: NavParams, private ytProvider: YtProvider, private youtube: YoutubeVideoPlayer, private plt: Platform) {
    let listId = this.navParams.get('id');
    this.videos = this.ytProvider.getListVideos(listId);
  }

  openVideo(video) {
    if (this.plt.is('cordova')) {
      this.youtube.openVideo(video.snippet.resourceId.videoId);
    } else {
      window.open('https://www.youtube.com/watch?v=' + video.snippet.resourceId.videoId);
    }
  }
}

To display all the videos we use again an ion-list and add a thumbnail and title just like before. The click event will call our function with the id of the video and bring up the native YouTube player or the browser window depending on where you run your app. Finish it up by changing your src/pages/playlist/playlist.html to:

<ion-header>
  <ion-navbar color="danger">
    <ion-title>Playlist</ion-title>
  </ion-navbar>
</ion-header>

<ion-content>
  <ion-list>
    <button ion-item *ngFor="let video of videos | async" (click)="openVideo(video)" detail-none>
      <ion-thumbnail item-start>
      <img [src]="video.snippet.thumbnails.standard.url">
    </ion-thumbnail>
    <h2>{{ video.snippet.title }}</h2>
    <p>{{ video.snippet.description }}</p>
    </button>
  </ion-list>
</ion-content>

Now you are ready to search for playlist of a channel and even display the videos of that playlist with a little thumbnail – directly from YouTube!

Conclusion

You don’t always need a full OAuth flow inside your Ionic app to use Google Services. With an API key you can make requests to some services and enjoy the full power of the available Google data to spice up your apps!

You can also find a video version of this article below.

Happy Coding,
Simon

The post How to use the YouTube REST API with Ionic appeared first on Devdactic.


How to Parse, Visualise and Export CSV Data with Ionic

$
0
0

Recently I came up with a new app idea and had to find a way to display and edit CSV data inside an Ionic app. There is no general way to achieve the visualisation like inside Excel, but actually there is an easy approach we can use to work with CSV data inside Ionic.

Inside this tutorial we will use the Papa Parse package to import and also export CSV data with Ionic. Inside the app we will then craft a table which allows us to see and change all the CSV data even on a small mobile screen like in the image below.

ionic-csv-app

Setting up the CSV App

First of all we need some CSV data of course. For this tutorial we can use the dummy data below, but if you already have another CSV file you want to use feel free to do so!

Date,"First Name","Last Name","Email","Amount","Currency","InvoiceID","Country"
08.22.2017,"Simon","Grimm","saimon@devdactic.com","25,00","EUR","0001","GER"
08.21.2017,"Simon","Grimm","saimon@devdactic.com","25,00","EUR","0002","GER"
08.19.2017,"Simon","Grimm","saimon@devdactic.com","25,00","EUR","0003","GER"
08.18.2017,"Simon","Grimm","saimon@devdactic.com","25,00","EUR","0004","GER"
08.17.2017,"Simon","Grimm","saimon@devdactic.com","25,00","EUR","0005","GER"
08.16.2017,"Simon","Grimm","saimon@devdactic.com","25,00","EUR","0006","GER"

We will save that CSV data inside our app at src/assets/dummyData.csv but before we can do so we need to start a new blank Ionic app and install the needed packages plus typings for our app:

ionic start devdacticCSV blank
cd devdacticCSV
npm install papaparse --save
npm install @types/papaparse --save

Now you can copy in the CSV data file and also make sure to add the HttpModule to our src/app/app.module.ts as we need to load a local file:

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 { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';

import { HttpModule } from '@angular/http';

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

We’ve set up everything so far and can now jump into the fun of working with CSV!

Working with CSV data

To get started we need to load and parse our CSV file. The package we used comes with almost all features we need already, and we can even keep it simple without further configuration as the parser will detect the format of the CSV and get the parsed values right.

In the beginning we will load the local file and parse it which results in an array with our values. We need to display the headline row special, so we move the first data block to an additional array. You could also keep it and remember that the first position is the column description, but I actually like to separate that meta information from the actual data array.

Besides the parsing we also implement the download function which will use the values of the current array inside the unparse() function of Papa Parse to return us a new CSV file. With a little HTML hack we instantly download the file, just as a demonstration if you use Ionic serve to see the changed CSV file.

Now go ahead and open your src/app/pages/home/home.ts and insert:

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { Http } from '@angular/http';
import * as papa from 'papaparse';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {
  csvData: any[] = [];
  headerRow: any[] = [];

  constructor(public navCtrl: NavController, private http: Http) {
    this.readCsvData();
  }

  private readCsvData() {
    this.http.get('assets/dummyData.csv')
      .subscribe(
      data => this.extractData(data),
      err => this.handleError(err)
      );
  }

  private extractData(res) {
    let csvData = res['_body'] || '';
    let parsedData = papa.parse(csvData).data;

    this.headerRow = parsedData[0];

    parsedData.splice(0, 1);
    this.csvData = parsedData;
  }

  downloadCSV() {
    let csv = papa.unparse({
      fields: this.headerRow,
      data: this.csvData
    });

    // Dummy implementation for Desktop download purpose
    var blob = new Blob([csv]);
    var a = window.document.createElement("a");
    a.href = window.URL.createObjectURL(blob);
    a.download = "newdata.csv";
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
  }

  private handleError(err) {
    console.log('something went wrong: ', err);
  }

  trackByFn(index: any, item: any) {
    return index;
  }

}

Now our CSV data is already parsed into an array and we need a way to display the data. Therefore, we use a table with input fields for each value. If your table is small with only a few columns you might only need that, but most tables tend to be bigger so we wrap our table inside an ion-scroll and set the width of the table to a bigger value later trough CSS.

As we change the data of the array later when we use the app, we use the trackBy function like described in this thread.

Besides that there is not really any magic, we craft the header row with the column titles and render the data inside a table below iterating first the rows and then each column of a row.

Finally we add a download button to the header so we can later download our changed data, Now go ahead and change your src/app/pages/home/home.html to:

<ion-header>
  <ion-navbar>
    <ion-title>
      CSV Data
    </ion-title>
    <ion-buttons end>
      <button ion-button icon-only (click)="downloadCSV()">
          <ion-icon name="download"></ion-icon>
        </button>
    </ion-buttons>
  </ion-navbar>
</ion-header>

<ion-content padding>
  <ion-scroll scrollX="true" scrollY="true" class="data-scroll">
    <table border="1" class="data-table">
      <tr>
        <td *ngFor="let header of headerRow" text-center><b>{{ header }}</b></td>
      </tr>
      <tr *ngFor="let row of csvData; let i = index">
        <td *ngFor="let data of row; let j = index; trackBy: trackByFn">
          <ion-input type="text" [(ngModel)]="csvData[i][j]"></ion-input>
        </td>
      </tr>
    </table>
  </ion-scroll>
</ion-content>

As mentioned before, we need some CSS to make the table bigger but also to tell the ion-scroll to cover the whole screen. Of course the size here is up to you and your needs, but for now change the src/pages/home/home.scss to:

page-home {
    .data-scroll {
        position: absolute;
        top: 0;
        bottom: 0;
        left: 0;
        right: 0;
    }
    .data-table {
        min-width: 800px;
    }
}

That’s it! If you run your app now with serve or on a device you should see our CSV data rendered inside the table. You can change all values and finally even export the new CSV data and safe it to your computer. The result is the image below where one column was changed and it’s still super valid CSV data.

ionic-csv-change

Conclusion

In this tutorial we explored a way to get CSV data into our Ionic app, work with the data and finally bring it back to CSV format. You could now add (as a little homework) a function to add new rows or columns so you can build your own Excel lite with Ionic.

Of course showing a table on a mobile device is not always the best idea, there is really limited space and you should be aware of that anytime.

But besides that, it is super simple to work with CSV data inside your Ionic app!

You can find a video version of this article below.

Happy Coding,
Simon

The post How to Parse, Visualise and Export CSV Data with Ionic appeared first on Devdactic.

Ionic Image Upload and Management with Node.js – Part 1: Server

$
0
0

In times of Instagram and the constant need to share our food and cat images, the image upload function inside an Ionic app is one of the most requested tutorials. A while ago you saw how to implement image upload with PHP backend and today it’s time to take this to the next level by building an image upload and management app with Ionic and Node.js backend!

Inside this first part we will develop the backend side for our app which will then give us a nice API we can use from our Ionic app inside the second part.

Part 2 (COMING SOON): Ionic Image Upload and Management with Node.js – Ionic App

If you want to get an email once the next part comes out or if you want to get all the code of this article ready to start, simply put your email in the box below and get your assets!

As said the first step now is to create our backend. We will implement routes for the image upload, manage the images on the server side with a MongoDB and by keeping the information we can also get a list of currently uploaded images, get one image directly streamed from the server or finally delete an image.

So our app is really flexible and this won’t be one of the most easiest tutorials if you haven’t worked with Node.js before, but I bet it’s worth the time to expand your horizon!

The final result of this post is an API which can be tested with Postman like you can see below.

nodejs-image-upload-postman

Setup

We start by creating a new folder and adding a new package.json which holds a reference to all the NPM packages that we need. Also, withing this post I’ll even use TypeScript on the backend side which is why we have a prestart script which compiles our TS files into a dist folder as plain Javascript files.

Open your package.json copy the following inside:

{
  "name": "image-upload",
  "version": "1.0.0",
  "description": "A simple NodeJS server for image upload",
  "scripts": {
    "prestart": "tsc",
    "start": "node dist/server.js"
  },
  "author": "Simon Grimm",
  "license": "MIT",
  "dependencies": {
    "cors": "^2.8.4",
    "del": "^3.0.0",
    "express": "^4.15.4",
    "fs": "0.0.1-security",
    "mongoose": "^4.11.9",
    "multer": "^1.3.0",
    "path": "^0.12.7"
  },
  "devDependencies": {
    "@types/express": "^4.0.37",
    "@types/multer": "^1.3.3",
    "typescript": "^2.4.2"
  }
}

As we want to use Typescript we should also add a tsconfig.json with some general information about our project and what we want to tell the TS compiler:

{
    "compilerOptions": {
        "module": "commonjs",
        "moduleResolution": "node",
        "target": "es6",
        "sourceMap": true,
        "outDir": "dist"
    }
}

Once we have these 2 files, we can go ahead and run npm install to install all the needed dependencies for our project. Also, make sure to create a new folder inside the project called “uploads” because the way we use the upload feature we need the folder to be created before we start!

Now finally you need to make sure you have a local/remote MongoDB instance running. You can follow the MongoDB installation guide here, it shouldn’t take you long and afterwards you can simply start it directly from your command line.

Make sure your database is up and running while following the rest of the tutorial!

Starting the Image Upload Server

We have installed the dependencies and setup the needed config for Typescript, so let’s actually write some code. We start with the entry point to our server which will first of all configure the Multer plugin we use for uploading files.

We tell the plugin to use the diskStorage and pass in 2 callback functions for the destination path and also for the filename of the files as we want to create unique (more or less) filenames and therefore create the name from a special name of the file and the current date.

Afterwards we initialise our express app and tell it to create some API routes from the routes file which we will create later.

Finally we create a connection to our MongoDB, in this case expecting it to be available at localhost which is the case if you have installed it locally. Otherwise simply put in your IP!

Now go ahead and create a new server.ts and insert:

import * as express from 'express';
import * as multer from 'multer'
import * as cors from 'cors'
import * as mongoose from 'mongoose'

// Generell properties
export let UPLOAD_PATH = 'uploads'
export let PORT = 3000;

// Multer Settings for file upload
var storage = multer.diskStorage({
    destination: function (req, file, cb) {
        cb(null, UPLOAD_PATH)
    },
    filename: function (req, file, cb) {
        cb(null, file.fieldname + '-' + Date.now())
    }
})
export let upload = multer({ storage: storage })

// Initialise App
export const app = express();
app.use(cors());

// Load our routes
var routes = require('./routes');

// Setup Database
let uri = 'mongodb://localhost/imageupload';
mongoose.connect(uri, (err) => {
    if (err) {
        console.log(err);
    } else {
        console.log('Connected to MongoDb');
    }
});

// App startup
app.listen(PORT, function () {
    console.log('listening on port: ' + PORT);
});

If you have developed with Node.js before you might have noticed the import syntax at the top is different then the require() syntax we had before. This comes with the usage of TS, but the rest of the code could also be used with normal JS inside a Node.js server if you already have a project.

MongoDB Model and Schema

To make sure we always know which images are available we store a reference to each uploaded image inside our database. To easily work with a MongoDB we can use the awesome ODM Mongoose.

With Mongoose, we need to define a schema for all of our database entries which we can later use to easily create and store new objects in our database. The syntax of the code below is a bit different then without TS but if you are on an older version and have problems just leave a comment and we’ll find a solution.

With TS we need to create an additional interface for our database model and finally connect everything and export the type.

Now go ahead and create a new image.ts and insert:

import * as mongoose from 'mongoose'

// Interface for TS
export interface IImageModel extends mongoose.Document {
    filename: string; 
    originalName: string; 
    desc: string;
    created: Date;
  };

  // Actual DB model
export var imageSchema = new mongoose.Schema({
    filename: String,
    originalName: String,
    desc: String,
    created: { type: Date, default: Date.now }
});

export const Image = mongoose.model<IImageModel>('Image', imageSchema);

As you can see our image will consist of a filename which represents the name on the disk, the original name, a description if we like to and finally the creation date. This model will help us in the next step to create solid objects which will then help us to read them out later again.

Image Upload and Routes

Finally we come to the actual API routes which will be used inside our Ionic app later. There are a few routes we need to implement now:

  • POST /images: Upload new image to the server
  • GET /images: Get a list of all available images on the server
  • GET /images/:id: Get one image streamed back from the server by its ID
  • DELETE /images/id: Delete one image from the disk and database by its ID

Basic routes, and not super tricky given the work we have already done!

Uploading an image is actually super simple on the server side as we use the Multer plugin which will act as a middleware and automatically look inside the request for a file with the key image and then upload it to the server using our previously defined callbacks for path and name.

Normally this is enough, but as we want to keep track we also create a new Image model which we fill with the data of the request and finally call save() upon which stores the new object inside our MongoDB. Afterwards inside the completion block we then return the new created object back just as a sign that everything worked fine.

That’s already everything for the upload, but our other routes are also quite simple so let’s go over them:

To get all images we call find() on our database model without parameters which will return all the entries inside that database. We could return this, but I wanted to already give back the correct path to the images so our app has no additional work todo. That’s why we iterate through all the entries and for each set the URL as a new property on that object.

When the app want’s to load an image it will use this previously created URL, which is the third of our routes. Here the server needs to find the entry of the given image id by using findById() so we can then call createReadStream() on the correct path of our image. If we set the correct Content-type here the result of the call we be simply the image just like opening any image on the web!

Finally a delete route if we (or a user) uploaded a not so cool image. Like before we need to lookoup the image entry but at the same time we want to remove the entry so we use findByIdAndRemove() and afterwards use the del plugin to easily and save remove the file from our disk.

Now create a new routes.ts and insert:

import { UPLOAD_PATH, app, upload } from './server';
import { Image } from './image';
import * as path from 'path'
import * as fs from 'fs'
import * as del from 'del';

// Upload a new image with description
app.post('/images', upload.single('image'), (req, res, next) => {
    // Create a new image model and fill the properties
    let newImage = new Image();
    newImage.filename = req.file.filename;
    newImage.originalName = req.file.originalname;
    newImage.desc = req.body.desc
    newImage.save(err => {
        if (err) {
            return res.sendStatus(400);
        }
        res.status(201).send({ newImage });
    });
});

// Get all uploaded images
app.get('/images', (req, res, next) => {
    // use lean() to get a plain JS object
    // remove the version key from the response
    Image.find({}, '-__v').lean().exec((err, images) => {
        if (err) {
            res.sendStatus(400);
        }

        // Manually set the correct URL to each image
        for (let i = 0; i < images.length; i++) {
            var img = images[i];
            img.url = req.protocol + '://' + req.get('host') + '/images/' + img._id;
        }
        res.json(images);
    })
});

// Get one image by its ID
app.get('/images/:id', (req, res, next) => {
    let imgId = req.params.id;

    Image.findById(imgId, (err, image) => {
        if (err) {
            res.sendStatus(400);
        }
        // stream the image back by loading the file
        res.setHeader('Content-Type', 'image/jpeg');
        fs.createReadStream(path.join(UPLOAD_PATH, image.filename)).pipe(res);
    })
});

// Delete one image by its ID
app.delete('/images/:id', (req, res, next) => {
    let imgId = req.params.id;

    Image.findByIdAndRemove(imgId, (err, image) => {
        if (err && image) {
            res.sendStatus(400);
        }

        del([path.join(UPLOAD_PATH, image.filename)]).then(deleted => {
            res.sendStatus(200);
        })
    })
});

Congratulations, you have now successfully implemented your own Node.js server with image upload and management functionality!

You can now test your features with Postman or already try to start with the app if the second part of the series is not yet released.

Conclusion

Actually it’s not really hard to implement a Node.js server like this with some basic functionalities. If you can bring together a few functions like this while also having the ability to create your frontend, you will always have a good time as you can solve almost any problem alone!

Looking backward, the use of Typescript inside this article was not really needed and made a few things (inside the Mongoose model) more complex then needed. In future articles I might go back or try to make a bit more out of TypeScript, but now you saw that it’s not hard to get started with TypeScript on the server side.

I’ll see you inside the next part of this series, and make sure to leave a comment and share this article if you enjoyed it!

You can also find a video version of this article below.

Happy Coding,
Simon

The post Ionic Image Upload and Management with Node.js – Part 1: Server appeared first on Devdactic.

Ionic Image Upload and Management with Node.js – Part 2: Ionic App

$
0
0

This is the second post of our Ionic Image upload series! Last time we were building the Node.js backend and API routes, now it’s time to actually use them from a real Ionic app to upload images, display a list of images and delete single images!

This means we will implement a few views and make HTTP calls to our API, capture images from the camera or library and send those images with the FileTransfer plugin to our backend including an optional description for each image.

If you haven’t followed the first part, I recommend you check it out now as you otherwise don’t have a working backend.

Part 1: Ionic Image Upload and Management with Node.js – Server

Once we are done, our final app will look like in the animation below, which means we can upload the image, see a nice list of images and even open a bigger view for each image. So this article contains everything you need to build your own Ionic image sharing app!

nodejs-image-upload-app

If you feel lazy, you can also grab the code for the frontend app directly below.

Setting up a new Ionic App

Like always we start with a blank new Ionic app so everyone can follow all steps. We need a provider for our backend interaction and a few additional pages. Also, we install the camera plugin and the file transfer plugin to easily upload images with their Cordova and NPM packages. Go ahead and run:

ionic start devdacticImageUpload blank
cd devdacticImageUpload
ionic g provider images
ionic g page home
ionic g page uploadModal
ionic g page previewModal
ionic cordova plugin add cordova-plugin-camera
ionic cordova plugin add cordova-plugin-file-transfer
npm install --save @ionic-native/camera @ionic-native/file-transfer

Initially I tried the new HttpClient of Angular 4.3 but as it’s not yet (at the time writing this) a dependency of Ionic we would have to change a lot so we will stick with this solution for now. If you come from the future and want to see that way, just leave a comment below!

To use all of our stuff we need to import it to our module so change your src/app/app.module.ts to:

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 { MyApp } from './app.component';

import { ImagesProvider } from '../providers/images/images';
import { HttpModule } from '@angular/http';
import { Camera } from '@ionic-native/camera';
import { FileTransfer } from '@ionic-native/file-transfer';

@NgModule({
  declarations: [
    MyApp
  ],
  imports: [
    BrowserModule,
    HttpModule,
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp
  ],
  providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler},
    ImagesProvider,
    Camera,
    FileTransfer
  ]
})

export class AppModule {}

That’s all for the setup process, we are now ready to make our HTTP calls to our backend with our provider and later use the Cordova plugins.

Provider for Backend Interaction

It’s always recommended to put stuff like backend interaction into a provider so we created a new one for all of our image actions.

Inside this ImagesProvider we have 3 functions to make use of the backend API routes. Getting a list of images and deleting images is just one line as it’s a simple GET or DELETE request using the Http provider of Angular.

Uploading an image file is a bit more complex, but just because we need a few more information at this point. To upload our file using the fileTransfer we need the path to the image, the backend URL and some optional options. In this case we specify the fileKey to be ‘image‘ as this is what the Multer plugin on the Node.js side expects (as we have told it so).

We also wanted to pass some information along which is the params object of the options where we set the desc key to the value the user entered before. Once all of this is done, it’s just calling upload() and the file will be send to our server!

Now open your src/providers/images/images.ts and change it to:

import { Injectable } from '@angular/core';
import 'rxjs/add/operator/map';
import { Http } from '@angular/http';
import { FileTransfer, FileUploadOptions, FileTransferObject } from '@ionic-native/file-transfer';

@Injectable()
export class ImagesProvider {
  apiURL = 'http://localhost:3000/';

  constructor(public http: Http, private transfer: FileTransfer) { }

  getImages() {
    return this.http.get(this.apiURL + 'images').map(res => res.json());
  }

  deleteImage(img) {
    return this.http.delete(this.apiURL + 'images/' + img._id);
  }

  uploadImage(img, desc) {

    // Destination URL
    let url = this.apiURL + 'images';

    // File for Upload
    var targetPath = img;

    var options: FileUploadOptions = {
      fileKey: 'image',
      chunkedMode: false,
      mimeType: 'multipart/form-data',
      params: { 'desc': desc }
    };

    const fileTransfer: FileTransferObject = this.transfer.create();

    // Use the FileTransfer to upload the image
    return fileTransfer.upload(targetPath, url, options);
  }

}

Our API and provider is now ready, so let’s put all of this into action!

Loading Images from the Backend

The first view of our app will display a list of images which we received from the backend side. We just need to call getImages() on our provider and we will get the data back inside the completion block, which we will then use inside the view in the next step.

Additional our first view has the open to delete an image (again just one call) and if we want to open an image for a bigger view of the actual image, we will open a Modal Controller with the PreviewModalPage which we implement later.

Finally the image upload will start with the presentActionSheet() so the user can decide if he wants to use an image from the library or capture a new one. After selecting, the takePicture() uses the Cordova plugin with the correct options to receive a URL to the file on the device.

At this point we could already upload the file, but we want to add an additional caption and perhaps if you want add options to change the image (just like you might know from Instagram) so we show the UploadModalPage which will then take care of calling the final upload function.

Also, once this modal is dismissed we reload our list of images if a special parameter reload was passed back from the modal, which means the upload finished and a new image should be shown inside the list!

Go ahead and change your src/pages/home/home.ts to:

import { ImagesProvider } from './../../providers/images/images';
import { Component } from '@angular/core';
import { IonicPage, NavController, ModalController, ActionSheetController } from 'ionic-angular';
import { Camera, CameraOptions } from '@ionic-native/camera';

@IonicPage()
@Component({
  selector: 'page-home',
  templateUrl: 'home.html',
})
export class HomePage {
  images: any = [];

  constructor(public navCtrl: NavController, private imagesProvider: ImagesProvider, private camera: Camera, private actionSheetCtrl: ActionSheetController, private modalCtrl: ModalController) {
    this.reloadImages();
  }

  reloadImages() {
    this.imagesProvider.getImages().subscribe(data => {
      this.images = data;
    });
  }

  deleteImage(img) {
    this.imagesProvider.deleteImage(img).subscribe(data => {
      this.reloadImages();
    });
  }

  openImage(img) {
    let modal = this.modalCtrl.create('PreviewModalPage', { img: img });
    modal.present();
  }

  presentActionSheet() {
    let actionSheet = this.actionSheetCtrl.create({
      title: 'Select Image Source',
      buttons: [
        {
          text: 'Load from Library',
          handler: () => {
            this.takePicture(this.camera.PictureSourceType.PHOTOLIBRARY);
          }
        },
        {
          text: 'Use Camera',
          handler: () => {
            this.takePicture(this.camera.PictureSourceType.CAMERA);
          }
        },
        {
          text: 'Cancel',
          role: 'cancel'
        }
      ]
    });
    actionSheet.present();
  }

  public takePicture(sourceType) {
    // Create options for the Camera Dialog
    var options = {
      quality: 100,
      destinationType: this.camera.DestinationType.FILE_URI,
      sourceType: sourceType,
      saveToPhotoAlbum: false,
      correctOrientation: true
    };

    // Get the data of an image
    this.camera.getPicture(options).then((imagePath) => {
      let modal = this.modalCtrl.create('UploadModalPage', { data: imagePath });
      modal.present();
      modal.onDidDismiss(data => {
        if (data && data.reload) {
          this.reloadImages();
        }
      });
    }, (err) => {
      console.log('Error: ', err);
    });
  }
}

When we get the initial list of images it will be empty, but later after uploading some images the response will look somehow like this:

[
    {
        "_id": "59a409348068343d9b187d6b",
        "desc": "my description",
        "originalName": "ionic-roadmap-icon.png",
        "filename": "image-1503922484703",
        "created": "2017-08-28T12:14:44.709Z",
        "url": "http://localhost:3000/images/59a409348068343d9b187d6b"
    },
    {
        "_id": "59a4093c8068343d9b187d6c",
        "desc": "Amrum!",
        "originalName": "18645529_1896463043974492_8029820227527114752_n.jpg",
        "filename": "image-1503922492005",
        "created": "2017-08-28T12:14:52.009Z",
        "url": "http://localhost:3000/images/59a4093c8068343d9b187d6c"
    }
]

We now build the view around this response by creating items for each entry and directly using the image URL of the JSON object. Remember, this is the URL we created inside the backend after receiving the entries, so this URL is not stored inside the database!

You might change URLs or backends so it wouldn’t be a good idea to store a absolute URL inside the database, but now that we get it we are happy on the frontend side as there is no additional work required to show images.

We can click each item to open the full screen preview, and sliding the items allows us to use the delete button for each image.

To upload a new image we add an ion-fab button which will float above our content and open the action sheet with the options for library or camera.

Now change your src/pages/home/home.html to:

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

<ion-content>
  <h3 [hidden]="images.length !== 0" text-center>No Images found!</h3>

  <ion-list>
    <ion-item-sliding *ngFor="let img of images">

      <ion-item tappable (click)="openImage(img)">
        <ion-thumbnail item-start>
          <img [src]="img.url">
        </ion-thumbnail>
        <h2>{{ img.desc }}</h2>
        <button ion-button clear icon-only item-end> <ion-icon name="arrow-forward"></ion-icon></button>
      </ion-item>

      <ion-item-options side="right">
        <button ion-button icon-only color="danger" (click)="deleteImage(img)">
        <ion-icon name="trash"></ion-icon>
      </button>
      </ion-item-options>

    </ion-item-sliding>
  </ion-list>

  <ion-fab right bottom>
    <button ion-fab (click)="presentActionSheet()"><ion-icon name="camera"></ion-icon></button>
  </ion-fab>

</ion-content>

You can now run your app already but as you might have not uploaded anything (you could already upload files with Postman!) you will see a blank screen, so let’s work on the upload page now.

Capture & Upload Image to Node.js Server

Most of the logic is already implemented so we just need some more simple views.

The image upload view appears after the user has taken or selected an image. Now we use the data that was passed to our modal page and implement a simple dismiss()function to close the preview and the saveImage() function which will upload the actual image with the caption which the user can also enter on this page. Once the upload is finished, we dismiss the page but pass the reload parameter so our HomePage knows the image list needs to be reloaded.

Open your src/pages/upload-modal/upload-modal.ts and change it to:

import { ImagesProvider } from './../../providers/images/images';
import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams, ViewController } from 'ionic-angular';

@IonicPage()
@Component({
  selector: 'page-upload-modal',
  templateUrl: 'upload-modal.html',
})
export class UploadModalPage {
  imageData: any;
  desc: string;

  constructor(public navCtrl: NavController, private navParams: NavParams, private viewCtrl: ViewController, private imagesProvider: ImagesProvider) {
    this.imageData = this.navParams.get('data');
  }

  saveImage() {
    this.imagesProvider.uploadImage(this.imageData, this.desc).then(res => {
      this.viewCtrl.dismiss({reload: true});
    }, err => {
      this.dismiss();
    });
  }

  dismiss() {
    this.viewCtrl.dismiss();
  }

}

Inside the view for this page we only need the image and the inout field for the description, so really nothing special here. Go ahead and change your src/pages/upload-modal/upload-modal.html to:

<ion-header>
  <ion-navbar color="primary">
    <ion-buttons start>
      <button ion-button icon-only (click)="dismiss()"><ion-icon name="close"></ion-icon></button>
    </ion-buttons>
    <ion-title>Upload Image</ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  <img [src]="imageData" style="width: 100%">
  <ion-item>
    <ion-input placeholder="My Awesome Image" [(ngModel)]="desc"></ion-input>
  </ion-item>

  <button ion-button full icon-left (click)="saveImage()">
          <ion-icon name="checkmark"></ion-icon>
          Save & Upload Image
  </button>
</ion-content>

You are now ready to upload images and should see the new images inside your list! Let’s finish this up with the special image preview.

Opening Images Fullscreen

Of course the list image is a bit small so we want to be able to show the full image with the description and perhaps also the creation date (which is added by the server automatically).

The class of this view is just making use of the passed image and has a dismiss function, so change your src/pages/preview-modal/preview-modal.ts now to:

import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams, ViewController } from 'ionic-angular';

@IonicPage()
@Component({
  selector: 'page-preview-modal',
  templateUrl: 'preview-modal.html',
})
export class PreviewModalPage {
  img: any;

  constructor(public navCtrl: NavController, public navParams: NavParams, private viewCtrl: ViewController) {
    this.img = this.navParams.get('img');
  }

  close() {
    this.viewCtrl.dismiss();
  }

}

For the view we need to get a bit tricky as we want a slightly transparent background which we will achieve with some CSS classes. Also, whenever the user clicks on the background we want to catch that event and hide the full screen image again.

Below the image we also display a card with the description and date, everything is directly retrieved from the JSON object of that image.

Now change your src/pages/preview-modal/preview-modal.html to:

<ion-content>
  <div class="image-modal transparent">
    <ion-item class="close-fake" no-lines (click)="close()">
      <ion-icon name="close"></ion-icon>
    </ion-item>
    <img [src]="img.url" class="fullscreen-image transparent" (click)="close()">
    <ion-card>
      <ion-card-content class="img-info" *ngIf="img.desc">
        <div class="desc-text">"{{ img.desc }}"</div>
        <p>
          <ion-icon name="calendar" item-left *ngIf="img.created"></ion-icon>
          {{ img.created | date: 'short' }}
        </p>
      </ion-card-content>
    </ion-card>
  </div>
</ion-content>

The last missing piece is the CSS to make the page look transparent, so open the src/pages/preview-modal/preview-modal.scss and change it to:

page-preview-modal {
    .img-info {
        padding-left: 20px;
        background: #fff;
    }
    .desc-text {
         font-weight: 500;
         font-size: larger;
         padding-bottom: 15px;
         color: color($colors, dark);
    }

  .content-ios {
    background: rgba(44, 39, 45, 0.84) !important;
  }

  .content-md {
    background: rgba(44, 39, 45, 0.84) !important;
  }

  .close-fake {
      background: transparent;
      color: color($colors, light);
      font-size: 3rem;
  }
}

Woah, we are done! Finally..

To completely use your app you need to deploy it to a device as we are using Cordova plugins to capture images and for the upload process. If you already have data inside your database you should anyway be able to run the app inside your browser to see the list of images and also open the full screen preview!

Conclusion

Inside this 2 part series you have learnt to build a Node.js backend with MongoDB and also an Ionic image sharing App which makes use of our own API and uploads media files to a server.

You are now more or less ready to build the next Instagram or simply an image sharing app between you and your friends. Whatever it is that you build, let us know in the comments (and also if you enjoyed this series style with backend coding!).

You can also find a video version of this article below.

Happy Coding,
Simon

The post Ionic Image Upload and Management with Node.js – Part 2: Ionic App appeared first on Devdactic.

How to Deploy Your Ionic App as Website to Heroku

$
0
0

Our Ionic App is in itself a simple website, and if you want to build your project for mobile and as a webpage, there’s an easy way to make your app available to the whole world with Heroku.

Heroku is a platform to easily deploy your application in a container without thinking about much else. We just need to find a way to bring our Ionic app to Heroku in the correct format so Heroku can take care of the rest!

Before starting, make sure you have a Heroku account and also install the Heroku Toolbelt!

Starting our App

We start with a blank Ionic app, but of course you can also just change your existing app. Go ahead and run:

ionic start herokuApp blank
cd ./herokuApp
npm install --save morgan cors express body-parser

As you can see we not only start a new app but install a few dependencies you might know from NodeJS server development!

That’s because inside Heroku we will start a super simple NodeJS server which will host the files for our Ionic app. Therefore, go ahead and create this server.js at the top-level of your Ionic project and insert:

var express  = require('express');
var app      = express();                               
var morgan = require('morgan');            
var bodyParser = require('body-parser');    
var cors = require('cors');

app.use(morgan('dev'));                                        
app.use(bodyParser.urlencoded({'extended':'true'}));            
app.use(bodyParser.json());                                     
app.use(cors());

app.use(function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header('Access-Control-Allow-Methods', 'DELETE, PUT');
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
  next();
});

app.use(express.static('www'));
app.set('port', process.env.PORT || 5000);
app.listen(app.get('port'), function () {
  console.log('Express server listening on port ' + app.get('port'));
});

It’s basically some general settings for CORS and other stuff, but the most important part is that the app will use our www/ folder which holds the build of our Ionic app!

Before we now get into the deployment, let’s make a small change to display a pop-up with the current platform we are on.

This can be really helpful to see if your app can use Cordova plugins or not, because deployed as a website you won’t have access to these plugins!

Therefore, we add a button to our src/pages/home/home.html like this:

<ion-header>
  <ion-navbar>
    <ion-title>
      Ionic Web
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  <button ion-button full (click)="showPlatform()">Where am I running?</button>
</ion-content>

The function that will be called will get the current platforms we are running on and display them inside an Alert, so open your src/pages/home/home.ts and change it to:

import { Component } from '@angular/core';
import { NavController, AlertController, Platform } from 'ionic-angular';

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

  constructor(public navCtrl: NavController, private alertCtrl: AlertController, private platform: Platform) {

  }

  showPlatform() {
    let text = 'I run on: ' + this.platform.platforms();
    let alert = this.alertCtrl.create({
      title: 'My Home',
      subTitle: text,
      buttons: ['Ok']
    });
    alert.present();
  }

}

To create our new Heroku App we can use our toolbelt, so simply run:

heroku login
# Do the login stuff
heroku create

Alright we are ready, but now we got a little problem:

Normally, our Ionic app is built using the installed Ionic CLI and app scripts on your environment, but those are not (automatically) available on Heroku.

This means, we need to find a way to either bring the locally built folder to Heroku or to enable Heroku to build our Ionic app on its own. For this tutorial I’ll explain the second approach, because the first one involves to comit your build folder to Git which is not recommendable.

Add Ionic Scripts as Dependency

The first step is to move 2 devDependencies a bit up to the real dependencies. Therefore, change your package.json so it looks like this:

"dependencies": {
    ...
    "@ionic/app-scripts": "2.1.4",
    "typescript": "2.3.4"
  },
  "devDependencies": {
  },

Now the dependency to the build scripts will also be installed once we deploy the app, and we just need to tell Heroku to run this build step before starting our app.

For this we can use something called Procfile on Heroku and add the command that should be executed after we deploy the code. Create that file at the toplevel of your project and insert:

web: npm run build && npm start

This tells Heroku to build the app using the app scripts (because there is already a step “build” inside your scripts section) and after that start our previously created server file which than hosts the www/ folder!

To push your changes and deploy the app to Heroku, simply run the following command whenever you make changes now:

git add .
git commit -am 'Heroku Dpeloyment.'
git push heroku master

After running the command Heroku prints the link to your website, or you can also run heroku open to bring up your website. This page can now also be viewed with a mobile device and you get the platforms like in the image below.
ionic-web-mobile

Automation

If you want some more automation, you can simply put your “deployment script” into one file. Therefore, create a new file and give it enough permissions:

touch herokuDeployment.sh
chmod 777 herokuDeployment.sh

Inside that file put everything you need after changing your apps code:

git add .
git commit -am 'HEROKU DEPLOYMENT'
git push heroku master
heroku open

This will commit your changes (and add files if you added something new), push everything to Heroku (where our build takes place) and finally open your website!

On your computer, the website in Chrome will then look like this:
ionic-website-heroku

Conclusion

It’s actually super simple to share your code base between web and mobile – your Ionic app is already a website!

By using Heroku we can easily deploy the full code of our app without maintaining a second repository and we are able to build the mobile app and the web app from one code base.

You can also find a video version of this article below!

The post How to Deploy Your Ionic App as Website to Heroku appeared first on Devdactic.

Ionic AWS S3 Integration with NodeJS – Part 1: Server

$
0
0

For long time the Amazaon Web Services (AWS) have been around and people love to use it as a backend or simply storage engine. In this series we will see how we can upload files from our Ionic app to a S3 bucket inside AWS with a simple NodeJS server in the middle!

You could also directly upload files to S3, but most of the time you have sensitive information that needs to be be protected from public access. Therefore, we will build a server in this first part which will do the handling of AWS and later the Ionic App will only have to use already signed URLs!

Part 2 (COMING SOON): Ionic AWS S3 Integration with NodeJS: Ionic App

Configuring Your AWS Account

First of all you need of course an AWS account which you can create for free.

Once you have your account, wee need to do 2 things:

  1. Create a new user for our backend
  2. Create a bucket in S3

To create a new user, navigate to the IAM service (Identity and Access Management) and click on Users in the menu to the left.

From there, click Add user add the top to create a new user. Inside the first step, give the user a name and make sure to check Programmatic access so our backend can communicate with AWS!

aws-add-user-1

On the second step we need to give the user permissions to access our S3 bucket, therefore pick Attach existing policies directly and search for S3FullAccess and enable it.
aws-add-user-2

Now finish the last steps, and your user is created. As a result you get a screen with the user including the Access key ID and Secret access key which we need for our NodeJS backend.

Finally navigate to the S3 service and simply hit + Create bucket to create a new folder for the files we will upload. That’s all for the setup from the AWS side!

Setting up the Server

Our server is a simple NodeJS server using Express. It’s more or less the same general setup we used for the Ionic Image Upload tutorial.

Create an empty new folder and start by adding the package.json which is needed for our dependencies:

{
  "name": "devdacticAwsUpload",
  "version": "1.0.0",
  "description": "Backend for File upload to S3",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git"
  },
  "author": "Simon Reimler",
  "homepage": "https://devdactic.com",
  "dependencies": {
    "aws-sdk": "^2.83.0",
    "body-parser": "^1.15.2",
    "cors": "^2.8.1",
    "dotenv": "^0.4.0",
    "errorhandler": "^1.1.1",
    "express": "^4.14.0",
    "helmet": "^3.1.0",
    "morgan": "^1.7.0"
  },
  "devDependencies": {
  }
}

Now you can already run npm install to install all of our modules.

Next we need to setup our environment. We will make this backend Heroku ready, so we will later also use the Heroku CLI to start the server locally.

Because we do this, we can simply put our AWS keys into a .env file like this where you need to replace your keys and the bucket name you created previously:

S3_BUCKET=yourbucket
AWS_ACCESS_KEY_ID=yourKey
AWS_SECRET_ACCESS_KEY=yourSecretKey

Heroku will take care of loading the variables into our environment, and to make them better available for the rest of our app we add another secrets.js file which reads these properties:

module.exports = {
      aws_bucket: process.env.S3_BUCKET,
      aws_key: process.env.AWS_ACCESS_KEY_ID,
      aws_secret: process.env.AWS_SECRET_ACCESS_KEY,
};

Now the keys are stored secure inside the server and nobody besides our app will have access to them later. Remember that you shouldn’t add keys like this to your Ionic app as all the source code of your app can be read by other developers when they inspect your app!

Creating the AWS Controller

Next step is to implement the actual actions we want to perform on AWS using the AWS SDK.

First of all we create a general object which holds our region. This is different for me and you, but you can find the region when you open the AWS console inside your browser as ?region= attached to the URL!

Regarding the actual functions we have 4 use cases and routes:

  • Get a signed request to PUT an object to the S3 bucket
  • Get a signed request to a file of the S3 bucket
  • Get a list of all files of the S3 bucket
  • Delete a file from the S3 bucket

For all of this we can use the according calls of the AWS SDK within our controller.

Most of the times we have to specify a params object with the right keys to get the desired outcome.

If you have any question to these different functions and calls, just let me know below!

Otherwise they should be quite self explaining, so go ahead and create a new file in your folder called aws-controller.js and insert:

'use strict';

const aws = require('aws-sdk');
var secrets = require('./secrets');

const s3 = new aws.S3({
    signatureVersion: 'v4',
    region: 'eu-central-1' // Change for your Region, check inside your browser URL for S3 bucket ?region=...
});

exports.signedRequest = function (req, res) {
    const fileName = req.query['file-name'];
    const fileType = req.query['file-type'];
    const s3Params = {
        Bucket: secrets.aws_bucket,
        Key:   fileName,
        Expires: 60,
        ContentType: fileType,
        ACL: 'private'
    };
    
    s3.getSignedUrl('putObject', s3Params, (err, data) => {
        if (err) {
            console.log(err);
            return res.end();
        }
        const returnData = {
            signedRequest: data,
            url: `https://${secrets.aws_bucket}.s3.amazonaws.com/${fileName}`
        };

        return res.json(returnData);
    });
};

exports.getFileSignedRequest = function (req, res) {
    const s3Params = {
        Bucket: secrets.aws_bucket,
        Key: req.params.fileName,
        Expires: 60,
    };

    s3.getSignedUrl('getObject', s3Params, (err, data) => {
        return res.json(data);
    });
}


exports.listFiles = function (req, res) {
    const s3Params = { 
        Bucket: secrets.aws_bucket,
        Delimiter: '/'
    };
    
    s3.listObjects(s3Params, function (err, data) {
        if (err) {
            console.log(err);
            return res.end();
        }
        return res.json(data);
    });
}

exports.deleteFile = function (req, res) {
    const s3Params = { 
        Bucket: secrets.aws_bucket,
        Key: req.params.fileName
    };

    s3.deleteObject(s3Params, function (err, data) {
        if (err) {
            console.log(err);
            return res.end();
        }

        return res.status(200).send({ "msg": "File deleted" });
    });
};

Now we got all actions we need for our backend, we just need to connect them to the right routes.

Starting the Server & Adding Routes

The last file will start the actual server along with some packages which I usually use when developing a NodeJS backend like security, CORS handling, logging..

The most important part of this file is the routing.

We create three GET and one DELETE routes for our backend, which are simply routing the call to the according function of our controller.

The calls will either have the parameter directly inside the URL or attached as query params. This is of course up to you how you want your routes to look like, it’s just an example how it could be done.

Now go ahead and create the server.js file and insert:

var logger          = require('morgan'),
    cors            = require('cors'),
    http            = require('http'),
    express         = require('express'),
    dotenv          = require('dotenv'),
    errorhandler    = require('errorhandler'),
    bodyParser      = require('body-parser'),
    helmet          = require('helmet'),
    secrets = require('./secrets'),
    awsController = require('./aws-controller');

var app = express();
app.use(helmet())

dotenv.load();

app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(cors({origin:true,credentials: true}));

if (process.env.NODE_ENV === 'development') {
  app.use(logger('dev'));
  app.use(errorhandler())
}

app.get('/aws/sign', awsController.signedRequest);
app.get('/aws/files', awsController.listFiles);
app.get('/aws/files/:fileName', awsController.getFileSignedRequest);
app.delete('/aws/files/:fileName', awsController.deleteFile);

var port = process.env.PORT || 5000;
var server = http.createServer(app);

server.listen(port, function (err) {
  console.log('listening in http://localhost:' + port);
});

At the top of the post you can get the Postman collection I used for testing the backend directly to your inbox!

The PUT request is a bit tricky and you need to add a file (image) and select the body type binary, which results in a wrong content type set for the file inside your S3 bucket.

You can manually change this inside the Permissions tab of the file inside the AWS console, but anyway you should see the file appearing there!

In general the flow of the app will be:

  • Get a signed request to upload a new file
  • Call that URL from the app with a PUT call to upload an image to S3
  • Get a list of all files of the bucket
  • Resolve the names of the files to get a signed URL to each file
  • Delete one file by it’s name (key)

You can do all of this already using Postman, so until the second part comes out you should be able to try the backend on your own!

Conclusion

The setup can be a bit tricky but should work flawless if all keys are correctly set up! Also, your app is now ready to be deployed to Heroku to make your backend available everywhere, not just on your local machine.

In the second part we will see how easy it is with the correct server setup to upload files to S3 from our Ionic app.

If you upload using Postman is not working, just let me know below.

You can also find a vide of this first part below!

The post Ionic AWS S3 Integration with NodeJS – Part 1: Server appeared first on Devdactic.

Ionic AWS S3 Integration with NodeJS – Part 2: Ionic App

$
0
0

For long time the Amazaon Web Services (AWS) have been around and people love to use it as a backend or simply storage engine. In this series we will see how we can upload files from our Ionic app to a S3 bucket inside AWS with a simple NodeJS server in the middle!

In the first part of this series we’ve built a simple NodeJS backend which acts as our gateway to AWS. Our backend has they keys and a user to access our AWS services, so our Ionic app will need to ask for permission / the signed URL to make a request to AWS. Make sure you’ve the first part up and running before continuing!

Part 1: Ionic AWS S3 Integration with NodeJS: Server

In this part we will now focus on the Ionic app. We need to be able to capture images, upload them to AWS S3 and also display a list of currently hosted images along with the option to delete them.

Setting up Our App

We start with a blank new Ionic app and install the camera plugin and also the file plugin. We need both of them to upload new images to S3, so go ahead and run:

ionic start devdacticAwsUpload blank
cd devdacticAwsUpload
ionic g provider aws
npm install --save @ionic-native/camera @ionic-native/file
ionic cordova plugin add cordova-plugin-camera
ionic cordova plugin add cordova-plugin-file

We also created a new provider which will take care of accessing our backend URLs inside our app later!

To make use of our plugins and the provider we need to add them to the src/app/app.module.ts so go ahead and change it to:

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 { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { AwsProvider } from '../providers/aws/aws';

import { HttpModule } from '@angular/http';
import { Camera } from '@ionic-native/camera';
import { File } from '@ionic-native/file';

@NgModule({
  declarations: [
    MyApp,
    HomePage
  ],
  imports: [
    BrowserModule,
    HttpModule,
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage
  ],
  providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler},
    AwsProvider,
    Camera,
    File
  ]
})
export class AppModule {}

Now we are ready to dive into the connection to our server!

Developing the AWS Provider

It’s a good idea to separate your HTTP calls from the rest of the app so we put all of the needed requests into our AwsProvider.

Remember that we have 4 URL routes of our backend, so those are the 4 first functions we implement. These routes will allow us to get signed calls to AWS or to list the files and remove files.

When we call getFileList() we get a quite big object but we only need the Contents array, so we directly map those values and only return them to the caller of the function.

The last function of our provider is to upload our file, but as we already got the signed request at that point we just need to make a PUT request with the given URL and the file object (which we need to prepare before) and everything should work alright!

Go ahead and change your src/providers/aws/aws.ts to:

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

@Injectable()
export class AwsProvider {
  apiUrl = 'http://127.0.0.1:5000/';

  constructor(public http: Http) { }

  getSignedUploadRequest(name, type) {
    return this.http.get(`${this.apiUrl}aws/sign?file-name=${name}&file-type=${type}`).map(res => res.json());
  }

  getFileList(): Observable<Array<any>> {
    return this.http.get(`${this.apiUrl}aws/files`)
      .map(res => res.json())
      .map(res => {
        return res['Contents'].map(val => val.Key);
      });
  }

  getSignedFileRequest(name) {
    return this.http.get(`${this.apiUrl}aws/files/${name}`).map(res => res.json());
  }

  deleteFile(name) {
    return this.http.delete(`${this.apiUrl}aws/files/${name}`).map(res => res.json());
  }

  // https://www.thepolyglotdeveloper.com/2015/03/create-a-random-nonce-string-using-javascript/
  randomString = function (length) {
    var text = "";
    var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    for (var i = 0; i < length; i++) {
      text += possible.charAt(Math.floor(Math.random() * possible.length));
    }
    return text;
  }

  uploadFile(url, file) {
    return this.http.put(url, file);
  }
}

Now we only need to call our provider which will talk to the backend. Currently this points to localhost, so if you develop your app local it works, but if you deploy it to Heroku make sure to check your backend URL here!

Putting Everything Into Action

It’s finally time to build our view and logic!

Our controller needs to hold an array of images which we can fill once the app has loaded. But getting the array of files is not enough at this point, because we will only get the key or name of the file like “myfile.png” which is not enough to access our S3 resource as it is protected from public views.

Only our backend has the rights to access these resources, therefore we make a call to getSignedFileRequest() for each image to get a signed URL which is a GET request then to the actual resource. We push all of those URLs to the image object and the array so we can use that URL inside our view later.

When we add an image we first present the action sheet to pick either library or the option to capture a new image.

The hard stuff in here begins after we got the image object inside the completion block of getPicture(), because we need to perform a few things in row:

  1. Resolve the URI to a file of the filesystem
  2. Read that file as arrayBuffer which can be added to the PUT request
  3. Create a new (uniquish) name get the signed URL for the upload
  4. Perform the actual upload with the provider
  5. Get a new signed GET URL for the new resource and add it to our array

If you encounter any problems at this part (which is the pace where most errors can happen) leave a comment with your log below!

Working with files in iOS/Android is not always easy but hopefully everything works fine for you!

Go ahead and change your src/pages/home/home.ts to:

import { File } from '@ionic-native/file';
import { Camera, CameraOptions } from '@ionic-native/camera';
import { AwsProvider } from './../../providers/aws/aws';
import { Component } from '@angular/core';
import { NavController, ActionSheetController, ToastController, LoadingController } from 'ionic-angular';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {
  images = [];

  constructor(public navCtrl: NavController, private loadingCtrl: LoadingController, private toastCtrl: ToastController, private awsProvider: AwsProvider, private actionSheetCtrl: ActionSheetController,private file: File, private camera: Camera) { }

  ionViewWillEnter() {
    this.loadImages();
  }

  loadImages() {
    this.images = [];
    this.awsProvider.getFileList().subscribe(files => {
      for (let name of files) {
        this.awsProvider.getSignedFileRequest(name).subscribe(res => {
          this.images.push({key: name, url: res})
        });
      }
    });
  }

  presentActionSheet() {
    let actionSheet = this.actionSheetCtrl.create({
      title: 'Select Image Source',
      buttons: [
        {
          text: 'Load from Library',
          handler: () => {
            this.takePicture(this.camera.PictureSourceType.PHOTOLIBRARY);
          }
        },
        {
          text: 'Use Camera',
          handler: () => {
            this.takePicture(this.camera.PictureSourceType.CAMERA);
          }
        },
        {
          text: 'Cancel',
          role: 'cancel'
        }
      ]
    });
    actionSheet.present();
  }

  takePicture(sourceType) {
    // Create options for the Camera Dialog
    const options: CameraOptions = {
      quality: 100,
      correctOrientation: true,
      destinationType: this.camera.DestinationType.FILE_URI,
      encodingType: this.camera.EncodingType.JPEG,
      mediaType: this.camera.MediaType.PICTURE,
      sourceType: sourceType
    }

    // Get the picture
    this.camera.getPicture(options).then((imageData) => {

      let loading = this.loadingCtrl.create();
      loading.present();

      // Resolve the picture URI to a file
      this.file.resolveLocalFilesystemUrl(imageData).then(oneFile => {

        // Convert the File to an ArrayBuffer for upload
        this.file.readAsArrayBuffer(this.file.tempDirectory, oneFile.name).then(realFile => {
          let type = 'jpg';
          let newName = this.awsProvider.randomString(6) + new Date().getTime() + '.' + type;

          // Get the URL for our PUT request
          this.awsProvider.getSignedUploadRequest(newName, 'image/jpeg').subscribe(data => {
            let reqUrl = data.signedRequest;

            // Finally upload the file (arrayBuffer) to AWS
            this.awsProvider.uploadFile(reqUrl, realFile).subscribe(result => {

              // Add the resolved URL of the file to our local array
              this.awsProvider.getSignedFileRequest(newName).subscribe(res => {
                this.images.push({key: newName, url: res});
                loading.dismiss();
              });
            });
          });
        });
      }, err => {
        console.log('err: ', err);
      })
    }, (err) => {
      console.log('err: ', err);
    });
  }

  deleteImage(index) {
    let toRemove = this.images.splice(index, 1);
    this.awsProvider.deleteFile(toRemove[0]['key']).subscribe(res => {
      let toast = this.toastCtrl.create({
        message: res['msg'],
        duration: 2000
      });
      toast.present();
    })
  }
}

The last step is to add our view, which becomes now pretty easy.

We have to iterate over the array of images and display each of them inside a card. We use the url parameter of the object which is the already signed GET URL to the actual file on S3!

Besides that we add a little edit function which will enable a delete button at the bottom of each card.

Finally, a stylish FAB button displays nicely the action to add and upload new images.

Overall nothing really fancy here, so finish your app by changing your src/pages/home/home.html to:

<ion-header>
  <ion-navbar color="primary">
    <ion-title>
      Ionic + AWS
    </ion-title>
    <ion-buttons end>
      <button ion-button icon-only (click)="edit = !edit">
          <ion-icon name="unlock" *ngIf="edit"></ion-icon>
          <ion-icon name="lock" *ngIf="!edit"></ion-icon>
      </button>
    </ion-buttons>
  </ion-navbar>
</ion-header>

<ion-content padding>
  <h3 [hidden]="images.length !== 0" text-center>No Images found!</h3>

  <ion-list>
    <ion-card *ngFor="let img of images; let i = index">
      <img [src]="img.url">
      <button ion-button full color="danger" [style.margin]="0" *ngIf="edit" (click)="deleteImage(i)">
              <ion-icon name="trash"></ion-icon>
            </button>
    </ion-card>
  </ion-list>

  <ion-fab right bottom>
    <button ion-fab (click)="presentActionSheet()">
       <ion-icon name="camera"></ion-icon>
      </button>
  </ion-fab>
</ion-content>

That’s it!

Now make sure your backend is up and running and if you run the app on the simulator, you should be able to access the server directly. If you deploy to a device, perhaps just deploy the server to Heroku and use that URL inside the provider.

Conclusion

It’s not super hard to upload files to AWS S3 using Ionic, but it involves some coding on both backend and frontend to make everything secure.

If you have any ideas for improvement please let me know below 🙂

You can also find a video version of this article below!

The post Ionic AWS S3 Integration with NodeJS – Part 2: Ionic App appeared first on Devdactic.

How To Increase Your Teams Efficiency with Ionic Pro

$
0
0

If you’re taking the hybrid route for your app development project you’ll save time compared to multiple separate native projects. What if you could save even more time by introducing another suite of tools to your team?

Ionic was never just a framework. The tooling, the support, the community and services provided by Ionic can be described as a whole universe, and Ionic always had an official platform to easily integrate services into your Ionic app.

By now this platform has changed to Ionic Pro, a suite of tools for the entire development lifecycle of your app. So let’s take a look at how those services can help you & your team to develop and ship your apps faster.

What is Ionic Pro?

Think of Ionic Pro as an optional toolset that can support your app development at different stages, from early prototyping to the native app stores.

Your Ionic app can be connected to the services super easy and everything else is and all the services you can use are optional, so there’s nothing you need to fear at first.

But what’s the actual benefit?

Time, convenience and specialised services exactly for Ionic apps, made by people who know what app developers need.

There are many great services out there for stuff like Prototyping, Testing, Automatic Deployments… And most of them are specialised on one particular step of your development. You could use each of them to cover all important aspects, or you could get all of them in one suite!

Ionic Pro bundles essential services to make your app development with Ionic even faster and easier!

Let’s take a closer look at the different building blocks of Ionic Pro.

Faster Prototyping

At the first stage of your next app project you might want to get a fast prototype or dummy app for your customers. Although I’m not the biggest fan of visual tools, the Ionic Creator is definitely different from what you might have used before.

ionic-pro-creator

With this rapid prototyping tool you (or your project manager) can drag & drop Ionic elements inside an app container, connect different elements and even at logic for transitions.

While this is already great, you can even add more custom logic with code for elements and finally export all of this into a classic Ionic project which you can then continue to develop based on the prototype!

The Git Workflow

While the previous tool is more visual, this part is more like the backbone of the whole Ionic Pro suite.

Most of the connected services depend on the current version of your app (or the apps code) and to send your current code to Ionic Pro all you have to do is following a simple Git workflow like you are already used to from your repository.

While you shouldn’t use the Git support as your main repository, it acts as a new origin for your git folder. The following services all depend heavily on your code, and they have access to the pushed code of your App.

By using the classing Git model Ionic makes it easy to integrate these steps in your already existing landscape without bigger problems while getting the benefits of the following services.

Live Deployments

If your app is built and distributed through the official stores, it takes time to update your code. You have to create a new submission and sometimes wait a few days (hello Apple) until your new app and code is live.

But with Ionic pro you can actually update your apps bundle without going through the process again – and all of this app store compliant.

How?

Your app is not much more than a website, and the JS, HTML and CSS code of the bundle can easily changed. Still, if you add Cordova plugins or native functionality you need to go through the official process again.

ionic-pro-workflow

But for quick bugfixes, you can simply pick the new version of your git workflow, or if something goes wrong you can easily roll back to a previous version!

Testing with Channels

Testing is an essential part of every development lifecycle, and the existing tools with iOS Testflight and the Android alpha/beta stages are already pretty cool.

Still, you can get an even better testing setup by dividing your users/testers into different groups and deploying different stages of your app to different channels.

While this might sound complicated, it actually isn’t as you can simply select your version and the according channel and the new app will be available to those testers.
ionic-pro-channels

By doing this you can grant access to new features to specific people, while another department still works with the production version or another version with specific features.

Getting Testing Feedback

Making a specific version available is only the one side of testing, the other side of the coin is from the users perspective. Here Ionic Pro comes with a specific app users can download, the Ionic View app.

Through this app users can access the different versions of your app, and also they got some more features.
ionic-pro-feedback

Like you can see from the image above, users can simply shake their device to create a bug report that will be submitted to your Ionic Pro dashboard.

This means, it’s super easy for users to report a strange behaviour, and it’s also handy for you to have all the feedback in one place attached with a screenshot of the malfunction!

Packaging Apps in the Cloud

This part might not be interesting for bigger companies as most already own a Mac or at least Mac like environment to build their iOS apps.

Still, if you don’t have a Mac but need to build an iOS app, you can let Ionic Pro take care of the hard work. Simply upload your security profiles (which still nee to be created..) and your Ionic App will be built in the cloud.

Again you can select version from your workflow, and when the process is finished you can download the final artefact which you can then submit to the appstore!

And of course you could also build your Android app there if you wish to.

Monitoring JavaScript Errors

Something most Ionic / hybrid apps lack is a solid error reporting. Traditionally, client errors are hard to log and you are dependent on bug reports of your users.

But with the monitoring module of Ionic Pro your app can now take care of this whole process on it’s own!

Simply integrate the according package and add special logs inside the code of your app and you can track the bugs easily within your Ionic Pro dashboard.
ionic-pro-monitoring

Wondering how the compiled code can link back to a specific line of code?

With TypeScript your project contains something called sourcemaps which will help Ionic Pro to translate the error of your client back to a specific line of your uncompiled code.

Of course your can upload your sourcemaps with a CLI command for every new version so all the errors stay in sync!

How to get Started?

If you want to get started now, you can sign up today for free and start using some of the services.

If you enjoy those or want all the tools, paid accounts start at 29$ or pick another plan from the Ionic Pro pricing.

What are your thoughts on Ionic Pro, do you already use it?

Let us know if it helped your team or what you think could be added as well!

The post How To Increase Your Teams Efficiency with Ionic Pro appeared first on Devdactic.


10 Common Ionic Problems & Error Messages (And How to Fix Them)

$
0
0

We have all seen many error messages while developing our Ionic apps. Some appear more frequent, others appear only on very special occasions. But especially for beginners these messages can be challenging as the meaning is not always clear.

The problem most of the time is that your app will run fine and the build is successful – the problems only occur at runtime and therefore you are completely dependent on the hin inside the error message and understanding those messages is an essential part of being a good developer.

In this article I’ll outline 10 common error messages and problems that can happen quite often and how each of them can be easily solved within your Ionic app!

1. Cannot read property ‘xyz’ of undefined

A very classic error and one that can be easily tracked down although it’s not immediately clear where to look for the solution. This error comes up when you have code like this:

// Inside your class
item: string;

// Inside your view
{{ item.myValue }}

The property of your object is not the problem but the object itself; you are trying to access a property of an element that’s undefined (or in other cases null).

Check where you initialise your object or use the elvis operator to access properties safely without breaking your apps code:

// ? operator prevents crashing here!
{{ item?.myValue }}

2. Uncaught (in promise): invalid link: MyPage

This error appears when you try to navigate to a page that’s not existing. Perhaps you spelled the name wrong?

Especially when using lazy loading and passing page names as string, it can be tough to spot the problem as the linting won’t give you any indication that something is wrong.

Also when you get a message like “Nav Link error MyPage” it’s related to the same obstacle, some navigation is simply not working because Ionic can’t find the page you want to navigate to (or which you have used as root for e.g. a Tab bar).

As a solution, search your project for the name of the page of the error message and see if it’s actually the real name of the page you want to display!

3. _co.myFunction is not a function

This error is quite easy to find, it indicates you are calling a function from your template which you haven’t implemented inside your class.

To fix this, simply make sure that you have a function declared with the according name and that everything is spelled correctly. Again, this error won’t come up through linting because JavaScript won’t find errors within your template that are related to the class. Perhaps one day we get this..

4. Cannot find control with name: ‘myName’

If you are using reactive forms there are all kind of errors that can occur. This one is a very basic one and it indicates you are simply trying to use a formControl which you haven’t defined.

// Inside your class
this.registrationForm = this.fb.group({
  username: ['Simon', [Validators.required]]
});

// Inside your view
<form [formGroup]="registrationForm">
  <ion-input type="text" formControlName="myName"></ion-input>
</form>

In the code above you can see that the form has a username control, but the name used inside the view is myName which is not part of the definition!

5. Type TestPage is part of the declarations of 2 modules

Now things get a bit more tricky, as this error can be very hard to understand for beginners. What actually is a module?

When you use lazy loading for your pages, new generated pages will have their own module file right next to them. Without lazy loading, pages don’t have their own module file.

The trick is: If you page doesn’t have a module file (which means you are not using lazy loading) you need to reference all your pages inside your app.module.ts file.

But if the page already has it’s module and you import it somewhere else (for whatever reason) this error will come up and tell you that you can’t have the page in 2 modules.

Therefore, check if you import your page in some place where it shouldn’t be and make sure it’s only imported in one module file!

6. No Provider for xyz

You think you are pro structuring your code and outsourcing your business logic to a provider, you inject it through your constructor but ffs why does this error appear?

This message is not really indicating the fix very good and therefore can be hard to find. The problem is, you need to let Angular know about the providers of your app so the internal Injector can actually put the right provider into your class.

This error is related to dependency injection and there’s a big internal process behind how you can get your providers through your constructor.

The fix?

You need to add your created Provider to the array of providers inside the app.module.ts so your app knows about it. With newer releases of the Ionic CLI this step should automatically happen, but in case it’s not you know how to fix it now.

7. Cannot find a differ supporting object ‘[object Object]’ of type ‘object’

Working with arrays inside your view and using *ngFor is an awesome feature of Angular, but you can easily break things if you don’t watch out. The error message is not super helpful, but it has some additional information most of the time:

Only arrays and iterables are allowed Error: Cannot find a differ supporting object ‘[object Object]’ of type ‘object’. NgFor only supports binding to Iterables such as Arrays.

This should make more sense, but let’s add a code example.

// Inside your class
items = {};

// Inside your view
<ion-list>
  <ion-item *ngFor="let foo of items"></ion-item>
</ion-list>

The problem in the example above is easy to spot: You are trying to iterate over an object, but ngFor expects an array!

While you can get all keys of an object, it is not meant to be used this way.

Most of the time this error appears when you get data from a REST API and you are not sure about the values. Perhaps you think something is an array, but it actually isn’t.

So always check your responses, pick the right key where the result contains an array and make sure you are not passing a plain object to the ngFor!

8. Template parse errors: ‘custom-component’ is not a known element

Now we got 2 errors that will immediately break your app once it starts, but the problem won’t appear before (so build is still successful).

You can already note that Template parse errors means there’s a problem within your template, your view file.

To get this error you might have something like this in your view:

<ion-content>
  <special-component></special-component>
</ion-content>

You have created a Component and want to use it within your view. The problem is, by default the Ionic generated components are only added to a ComponentsModule, and you have to import this module where you need it!

This means, you can either import it at the top level of your app in app.module.ts or with lazy loading import the module of your components only in the module file of your pages.

Then Angular will know how to reference this special tag of your view and insert the correct component that you’ve created before.

9. Template parse errors: Can’t bind to ‘enabled’ since it isn’t a known property of ‘button’.

Another error that can occur with tags is this one, and it’s actually pretty easy to track down.

Imagine a code like this:

<button ion-button [enabled]="false">Test</button>

Seem fine from the outside, but the problem is that enabled is not a property of button you can bind to!

A fix for this is to use:

<button ion-button [disabled]="false">Test</button>

This is just one example, in general it simply means that you try to bind to something on the tag that’s not really available. It cna happen for all tags, your components and all the things you use in your view.

If you are unsure about the properties of elements, simply check out the according API documentation and see what’s available!

10. cordova_not_available

To wrap this list up, let’s end with a super classic one that hopefully everyone already knows about.

If you code your app and use the live preview within your browser, most of the time all is fine. But once you start to integrate Cordova plugins, you can’t test or use those features within the simple preview.

The Cordova.js is only injected once you build your app on a device (or simulator), and therefore none of those features will work inside the preview and you’ll run into this error.

The fix is simple: Just run your app on a device or, a bit more advanced way, is to mock your plugins.

You can override the Plugins so even on the browser the call returns a dummy result and not an error. If you don’t want to write all your Mocks on your own, check out this awesome package from Chris Griffith to get a full set of Mock objects!

11. BONUS: Nothing works?

Every good list needs a little bonus… And this one is rather simple.

Sometimes you fix all the problems, you are sure that everything is fine and still nothing works or nothing changes.

The solution?

Restart your livereload. Trust me, it has helped me and my students hundreds of times.

From time to time, Ionic releases can contain a few minor issues that break stuff. Things that worked don’t work like that should in the next release. It can happen.

Especially if you generate new files with the CLI I encountered this problem often, and the fix was just to close the command and start it again.

If you are lucky, everything will just work again then!

Anything missing?

Do you have any more common issues you’ve stumbled upon while developing your next epic Ionic app?

Let us know in the comments below and I’ll happily add them to the list to cover even more typical Ionic and Angular error messages!

Happy Coding,
Simon

The post 10 Common Ionic Problems & Error Messages (And How to Fix Them) appeared first on Devdactic.

How to Store Files on Firebase Storage with Ionic

$
0
0

We all know that Firebase is pretty amazing when it comes to realtime updates of your data. But what Firebase is also great for is uploading data and files to the private Storage each of your Firebase Apps has!

In this tutorial we will connect our Ionic app to Firebase and use both the database and storage for writing simple text files to the storage. The database is needed to keep track of the documents stored, and of course you cold store all kind of data there not just texts!

Once we are done our app will work like in the gif below.

firebase-storage-upload-files

Preparing your Firebase App

First of all you need a Firebase account (which is free) and create a new app there. Inside your app navigate to the Database section and select Rules as we want to disable any authentication checks for this tutorial. Paste in the following dummy rules there:

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

Of course for production you might want to add more advanced security rules and also create user accounts and so on. If you want to learn more on Firebase check out the courses of the Ionic Academy where we got multiple courses on different Firebase topics!

To finish your setup go to the overview of your Firebase app and select “Add Firebase to your web app” and copy the config which we will need soon inside Ionic.

Setting up the App

We start with a blank new Ionic app and install a bunch of plugins an packages. We will use the Storage through AngularFire but currently the interface is only available in the latest version of the RC5. Perhaps this might change if you go through the tutorial at a later time!

We also add a new page and provider plus the InAppBrowser plugin to download/open our text files later. Now go ahead and run:

ionic start devdacticFbStorage blank
cd devdacticFbStorage
npm i angularfire2@5.0.0-rc.5-next firebase@4.8.0
ionic g page myFiles
ionic g provider data
ionic cordova plugin add cordova-plugin-inappbrowser
npm i @ionic-native/in-app-browser

Next you can delete the pages/Home folder as we have created a new file for that list. To finally use the lazy loading with our new page change your app/app.component.ts to:

import { Component } from '@angular/core';
import { Platform } from 'ionic-angular';
import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';

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

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

Next we need to load the plugins and also connect our app to Firebase. Here we need the configuration you copied earlier, so replace the values with yours in the code below. Also, add all the needed AngularFire modules to your imports so we can use them later.

Open your app/app.modules.ts and change it to:

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 { MyApp } from './app.component';
import { DataProvider } from '../providers/data/data';

import { AngularFireModule } from 'angularfire2';
import { AngularFireDatabaseModule } from 'angularfire2/database';
import { AngularFireStorageModule } from 'angularfire2/storage';

import { InAppBrowser } from '@ionic-native/in-app-browser';

var firebaseConfig = {
  apiKey: "",
  authDomain: "",
  databaseURL: "",
  projectId: "",
  storageBucket: "",
  messagingSenderId: ""
};

@NgModule({
  declarations: [
    MyApp
  ],
  imports: [
    BrowserModule,
    IonicModule.forRoot(MyApp),
    AngularFireModule.initializeApp(firebaseConfig),
    AngularFireDatabaseModule,
    AngularFireStorageModule
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp
  ],
  providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler},
    DataProvider,
    InAppBrowser
  ]
})
export class AppModule {}

We are now ready to use both the Database and the Storage through AngularFire, so let’s dive into the fun!

Firebase Storage Upload and Database Sync

Our created page needs to display a list of files and be able to add a new text file to the storage. Our Provider will hold all the functionality for those actions and make all the calls to Firebase.

To get a list of files we will simply use a ref() on the files node and map the result so we also get all the keys of the objects which we need to delete entries and files later on.

The upload to the storage happens through the afStorage provider where we create a new uniqueish name for the file with a date and then use the putString() function to write the data to Firebase. That’s all to for uploading!

As you can see, you could also write base64 data as images to the storage which is discussed inside the advanced course inside the Ionic Academy.

Once a file is uploaded we need to store its meta information to the database, which can be achieved by calling push() on the reference to our files list.

Finally deleting files takes place in both the storage and the database to clean up our mess if we no longer need the data.

Now go ahead and change your providers/data/data.ts to:

import { Injectable } from '@angular/core';
import { AngularFireDatabase } from 'angularfire2/database';
import { AngularFireStorage, AngularFireUploadTask } from 'angularfire2/storage';

@Injectable()
export class DataProvider {

  constructor(private db: AngularFireDatabase, private afStorage: AngularFireStorage) { }

  getFiles() {
    let ref = this.db.list('files');

    return ref.snapshotChanges().map(changes => {
      return changes.map(c => ({ key: c.payload.key, ...c.payload.val() }));
    });
  }

  uploadToStorage(information): AngularFireUploadTask {
    let newName = `${new Date().getTime()}.txt`;

    return this.afStorage.ref(`files/${newName}`).putString(information);
  }

  storeInfoToDatabase(metainfo) {
    let toSave = {
      created: metainfo.timeCreated,
      url: metainfo.downloadURLs[0],
      fullPath: metainfo.fullPath,
      contentType: metainfo.contentType
    }
    return this.db.list('files').push(toSave);
  }


  deleteFile(file) {
    let key = file.key;
    let storagePath = file.fullPath;

    let ref = this.db.list('files');

    ref.remove(key);
    return this.afStorage.ref(storagePath).delete();
  }
}

Once we upload a file they will be visible inside the storage like in the image below! Now we only need to connect everything with our view.

firebase-storage-text-files

Building Our File Upload and List

Like most of the time the actual view is not that hard if your got the provider and needed functions already set up. When starting our page we can load the list of files through the provider, and as this is an Observable we will automatically get the new files added and don’t need any more logic to update the list!

When we add a new file we bring up a little alert with one input to write a short text. Probably not the most common use case but we are here to learn about the functionality!

After the alert is closed, we use both of our provider functions to first upload the new information and then store the meta information (after the upload) to the database.

You might notice a strange upload.then().then(...) which is currently needed exactly like this, but perhaps this might change in the future as it looks quite weird in the beginning.

Deleting a file is as easy as one call to the provider, and viewing a file is handled by the In App Browser which we can give the full path to the file hosted on Firebase storage!

Now finish your page by changing the pages/home/home.ts to:

import { InAppBrowser } from '@ionic-native/in-app-browser';
import { DataProvider } from './../../providers/data/data';
import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
import { AlertController } from 'ionic-angular/components/alert/alert-controller';
import { ToastController } from 'ionic-angular/components/toast/toast-controller';
import { Observable } from 'rxjs/Observable';

@IonicPage()
@Component({
  selector: 'page-my-files',
  templateUrl: 'my-files.html',
})
export class MyFilesPage {
  files: Observable<any[]>;

  constructor(public navCtrl: NavController, private dataProvider: DataProvider, private alertCtrl: AlertController, private toastCtrl: ToastController, private iab: InAppBrowser) {
    this.files = this.dataProvider.getFiles();
  }

  addFile() {
    let inputAlert = this.alertCtrl.create({
      title: 'Store new information',
      inputs: [
        {
          name: 'info',
          placeholder: 'Lorem ipsum dolor...'
        }
      ],
      buttons: [
        {
          text: 'Cancel',
          role: 'cancel'
        },
        {
          text: 'Store',
          handler: data => {
            this.uploadInformation(data.info);
          }
        }
      ]
    });
    inputAlert.present();
  }

  uploadInformation(text) {
    let upload = this.dataProvider.uploadToStorage(text);

    // Perhaps this syntax might change, it's no error here!
    upload.then().then(res => {
      this.dataProvider.storeInfoToDatabase(res.metadata).then(() => {
        let toast = this.toastCtrl.create({
          message: 'New File added!',
          duration: 3000
        });
        toast.present();
      });
    });
  }

  deleteFile(file) {
    this.dataProvider.deleteFile(file).subscribe(() => {
      let toast = this.toastCtrl.create({
        message: 'File removed!',
        duration: 3000
      });
      toast.present();
    });
  }

  viewFile(url) {
    this.iab.create(url);
  }
}

Last missing piece is the view to display an array of files as cards using the async pipe. Each card displays some information as well as two buttons for opening and deleting that file.

To add new files we use a FAB at the bottom right corner which always looks pretty nice with Ionic!

Nothing special here, so finish the code by changing the pages/home/home.html to:

<ion-header>

  <ion-navbar color="primary">
    <ion-title>My Files</ion-title>
  </ion-navbar>
</ion-header>


<ion-content>

  <ion-card *ngFor="let file of files | async">
    <ion-card-header>
      {{ file.created | date:'medium' }}
    </ion-card-header>

    <ion-card-content>

      <button ion-button block icon-left outline (click)="viewFile(file.url)">
        <ion-icon name="eye"></ion-icon>
        {{ file.fullPath }}
      </button>

      <button ion-button block outline icon-left color="danger" (click)="deleteFile(file)">
        <ion-icon name="trash"></ion-icon> Delete
      </button>

    </ion-card-content>

  </ion-card>

  <ion-fab right bottom>
    <button ion-fab color="primary" (click)="addFile()">
      <ion-icon name="cloud-upload"></ion-icon>
    </button>
  </ion-fab>

</ion-content>

Now you should be able to run your app either on a device or the browser and see your data roll into the storage of your Firebase app and also inside the realtime database with entries like the one below.

firebase-storage-database-entries

Conclusion

Although we have used a very early version of AngularFire to use the Storage it already works as expected. Of course the syntax might change but everything else is working flawless!

What else can you think of to store inside the Firebase Storage of your App?

let me know if you encounter any troubles!

You can also find a video version of this article below.

Happy Coding,
Simon

The post How to Store Files on Firebase Storage with Ionic appeared first on Devdactic.

Increase Ionic Scroll Performance with Virtual Scroll & Infinite Scroll

$
0
0

When working with Ionic we mostly use the classic Ionic list and iteration patterns we know about. However, if you have a lot of data to display it makes sense to keep an eye on performacne of your application.

To improve the performance of your Ionic lists there are 2 patterns we can use:

As it’s not immediately clear when to use which of these and what they actually add to your app let’s take a closer look at both of them.

Setup a New Ionic Dummy App

Before we dive into the 2 patterns we need a super minimalistic setup for a dummy app so we can receive some JSON data. As always you can start a new Ioni capp like this:

ionic start devdacticScroll blank

To make HTTP calls later we also need to import the apropriate module so change your app/app.module.ts to:

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 { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';

import { HttpClientModule } from '@angular/common/http';

@NgModule({
  declarations: [
    MyApp,
    HomePage
  ],
  imports: [
    BrowserModule,
    IonicModule.forRoot(MyApp),
    HttpClientModule
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage
  ],
  providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler}
  ]
})
export class AppModule {}

Now we are ready so let’s start with the first list pattern.

Implementing Ionic Infinite Scroll

Infinite scroll describes a list which can be scrolled down (or up) all the time while adding new items. Whenever we reach the end of our current loaded list, a function will be triggered and new items can be added to the list.

This means, the infinite scroll is great for pagination.

If your api returns a number of items plus a pages count you can always ask for more results on another page. A typical response might look like this:

{
  "page": 0,
  "maxPages" 18,
  "results": [
   // Actual result objects
  ]
}

In those situations the page parameter needs to be passed to the API, and we will build a little example with exactly that behaviour.

Inside the view of our app we need an iteration over tan array of items. Nothing new so far, but below the list we add the ion-infinite-scroll component. This component will automatically detect when the user scrolls a specified distance from the bottom of the page and trigger the (ionInfinite) function you can declare.

Additional you can also change the loading text, animation and also the position (top or bottom) of the infinite loading component.

Let’s get started by changing your pages/home/home.html to:

<ion-header>
  <ion-navbar>
    <ion-title>
      Infinite Scroll
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content>
  
  <ion-card *ngFor="let user of users">
    <ion-item>
      <ion-avatar item-start>
        <img [src]="user.picture.medium">
      </ion-avatar>
      <h2 text-capitalize>{{ user.name?.first }} {{ user.name?.last }}</h2>
    </ion-item>
    <ion-card-content>
      {{ user.email }}
    </ion-card-content>
  </ion-card>

  <ion-infinite-scroll (ionInfinite)="loadMore($event)" loadingSpinner="bubbles" loadingText="Loading Users...">
    <ion-infinite-scroll-content></ion-infinite-scroll-content>
  </ion-infinite-scroll>

</ion-content>

Now we just need to make the according calls inside our controller to get some data and fill our users array. We make use of the Randomuser API which actually also happens to allow pagination (yeaaah)!

The only thing you need to take care of is not overriding your users all the time but adding the new array of results to the old entries to keep your existing data.

Also if the action was triggered by the infinite scroll we need to finish it with infiniteScroll.complete() to hide the loading and set it back to the regular state.

Of course your pages logic may vary, but this would be an example how to increase your page of the results with each infinite loading call.

Open your pages/home/home.ts and change it to:

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { HttpClient } from '@angular/common/http';
import "rxjs/add/operator/map";

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {
  users = [];
  page = 0;
  maximumPages = 3;

  constructor(public navCtrl: NavController, private httpClient: HttpClient) {
    this.loadUsers();
  }

  loadUsers(infiniteScroll?) {
    this.httpClient.get(`https://randomuser.me/api/?results=20&page=${this.page}`)
    .subscribe(res => {
      this.users = this.users.concat(res['results']);
      if (infiniteScroll) {
        infiniteScroll.complete();
      }
    })
  }

  loadMore(infiniteScroll) {
    this.page++;
    this.loadUsers(infiniteScroll);

    if (this.page === this.maximumPages) {
      infiniteScroll.enable(false);
    }
  }

}

For the example we also added a maximum number of pages because most API results will eventually end at some point. Once that happens we can directly disable the infinite scroll behaviour by calling infiniteScroll.enable(false).

The resulting app will look like below!

ionic-infinite-scroll

When should I use Ionic Infinite Scroll?

If your API offers pagination for your results. You can make calls and receive a small bunch of results which helps to display data faster to the user. Whenever the user needs more data the call will be placed and again the result will be available faster as the response is smaller than if it would contain all results!

So if you can split up your API results and trigger the load of additional data, the infinite scroll is exactly what you need.

Implementing Ionic Virtual Scroll

The Ionic Virtual Scrolling list offers a special behaviour which you might know from native iOS development: The list takes a complete array of data but not all items are rendered immediately, only the ones the user can see and a few nearby. When the user starts scrolling, views will be reused and filled with information of the new rows.

For this pattern let’s start with the logic of our page. We don’t need the pages logic from before, but this time we should make an API call to get a massive result (you could also change 100 to 1000 here). So go ahead and change your pages/home/home.ts one more time to:

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { HttpClient } from '@angular/common/http';
import "rxjs/add/operator/map";

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {
  users = [];

  constructor(public navCtrl: NavController, private httpClient: HttpClient) {
    this.loadUsers();
  }

  loadUsers() {
    this.httpClient.get(`https://randomuser.me/api/?results=100`)
    .subscribe(res => {
      this.users = res['results'];
    })
  }

}

As you can see, we get “all” results of the API. Actually 100 are not all here, but imagine this would be everything.

This means, we expect a huge response with potentially 100 rows of data. And this can be a lot more in the enterprise context as well!

If you would wrap all of those results into a simple ngFor list and call it a day you will regret it very soon as the performance will give you hell.

All cells and views of the list will be created and rendered, but you actually only need the few that the user can see!

Therefore, we can wrap our results into the Virtual Scroll like this inside your pages/home/home.html:

<ion-header>
  <ion-navbar>
    <ion-title>
      Virtual Scroll
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content>

  <ion-list [virtualScroll]="users" [approxItemHeight]="'88px'">
    <ion-card *virtualItem="let user">

      <ion-item text-capitalize>
        <ion-avatar item-start>
          <img [src]="user.picture?.thumbnail">
        </ion-avatar>
        <h2>{{ user.name?.first }} {{ user.name?.last }}</h2>
      </ion-item>

      <ion-card-content>
        {{ user.email }}
      </ion-card-content>

    </ion-card>
  </ion-list>

</ion-content>

You might need to use it a few times until you know the syntax by heart, but once you got it this is a powerful tool you need to know about.

Also, it is recommended to no use the standard image tage inside the elements but the ion-img component.

However when developing this tutorial I could get it to work with the special Ionic component, and there are countless open issues (and sometimes closed for no reason) on Github showing that this component is not really working as expected.

Perhaps give the ion-img a try but be aware that it might have some bugs. But this counts only for the image, not the virtual scroll itself!

When should I use Ionic Virtual Scroll?

If you API delivers huge amounts of data (big data) and you want to display all the data for the user. A regular list would have a poor performance, therefore use the virtual scroll if you have the full response at hand.

Conclusion

The 2 special list patterns can be a real game changer if your app suffers from poor performance. And as we’ve seen, the implementation is so fast that you can’t argue you don’t know how it works anymore!

You can also find a video version of this article below.

The post Increase Ionic Scroll Performance with Virtual Scroll & Infinite Scroll appeared first on Devdactic.

How to Visualise Firebase Data with Chart.js and Ionic

$
0
0

We’ve seen many Firebase realtime apps with chats, shopping lists and the classic todo list. But there’s so much else that can benefit from the live updates and sync of your data. What if we could visualise Firebase Data?

In this tutorial we will combine 3 great things: Firebase, Chart.js and Ionic!

We will build an app that can save data to our Firebase backend and at the same time create a nice visualisation of the aggregated Firebase data. It’s going to be tricky, but here’s what you can expect.

firebase-data-visualisation-app

This means, we track expenses or incomes with a value and month attached (for simplicity here only 4 months, the rest is left for the reader) and automatically update our charts with the new combined data of each month!

But before we get into the actual app, we need some basics set up on Firebase.

Preparing your Firebase App

First of all you need a Firebase account (which is free) and create a new app there. Inside your app navigate to the Database section and select Rules as we want to disable any authentication checks for this tutorial. Paste in the following dummy rules there:

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

Of course for production you might want to add more advanced security rules and also create user accounts and so on. If you want to learn more on Firebase check out the courses of the Ionic Academy which has got multiple courses on different Firebase topics (like Storage, user management..)!

To finish your setup now go to the overview of your Firebase app and select “Add Firebase to your web app” and copy the config which we will need soon inside Ionic.

Setting up the App

We start with a blank new Ionic app and install all the packages we need directly, which means AngularFire and Firebase plus the Chart.js npm package as well:

ionic start devdacticCharts blank
cd devdacticCharts
npm i chart.js firebase angularfire2

Next we need to load the plugins and also connect our app to Firebase. Here we need the configuration you copied earlier, so replace the values with yours in the code below. Also, add the needed AngularFire modules to your imports so we can use them later. In this case we only need the basic module and the Database.

Open your app/app.modules.ts and change it to:

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 { MyApp } from './app.component';
import { DataProvider } from '../providers/data/data';

import { AngularFireModule } from 'angularfire2';
import { AngularFireDatabaseModule } from 'angularfire2/database';

var firebaseConfig = {
  apiKey: "",
  authDomain: "",
  databaseURL: "",
  projectId: "",
  storageBucket: "",
  messagingSenderId: ""
};

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

That’s all for the setup, let’s get into some details!

Loading & Adding Data to Firebase

Before we can display any fancy chart inside our app we need some data stored inside our Firebase Database. In this tutorial I’ll simply split up the single functions of the pages/home/home.ts into code listings but it’s all the same controller over the next listings!

First of all make sure you got all the imports plus some variables. We need those to have a reference to our Firebase list, to display clear names for Months and also a transaction object that we can fill and then add to Firebase.

Also, we need a ViewChild so we can access our charts later which are simply a canvas object (more on that later).

For now, change the base of your pages/home/home.ts to:

import { Component, ViewChild } from '@angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
import { AngularFireDatabase } from 'angularfire2/database';
import { Observable } from 'rxjs/Observable';
import { AngularFireList } from 'angularfire2/database/interfaces';
import { ToastController } from 'ionic-angular/components/toast/toast-controller';
import { Chart } from 'chart.js';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {
  data: Observable<any[]>;
  ref: AngularFireList<any>;

  months = [
    {value: 0, name: 'January'},
    {value: 1, name: 'February'},
    {value: 2, name: 'March'},
    {value: 3, name: 'April'},
  ];

  transaction = {
    value: 0,
    expense: false,
    month: 0
  }

  @ViewChild('valueBarsCanvas') valueBarsCanvas;
  valueBarsChart: any;

  chartData = null;

  constructor(public navCtrl: NavController, private db: AngularFireDatabase, private toastCtrl: ToastController) {
  }

}

First of all we now want to establish a connection to our Firebase data when the view is loaded. Therefore, we query a list and I also added ordering by the child value “month” which is not mandatory but if you want to present the data inside a list as well, ordering it directly makes totally sense!

We can now also subscribe to any changes that occur on this node and immediately get the new data. That’s the spot where we want to either create our update our charts. The setup of a chart only needs to happen once, you can also add it somewhere else if you want to, but make sure it’s called before updating the data next time.

Now ad your first function to the pages/home/home.ts:

ionViewDidLoad() {
    // Reference to our Firebase List
    this.ref = this.db.list('transactions', ref => ref.orderByChild('month'));

    // Catch any update to draw the Chart
    this.ref.valueChanges().subscribe(result => {
      if (this.chartData) {
        this.updateCharts(result)
      } else {
        this.createCharts(result)
      }
    })
  }

Of course we can’t display data if we don’t have any data, therefore we continue with the function to add a new transaction. This function will simply use the existing reference we created before and push a new node with the value of the transaction object. This object will be filled later through our view, so we only need to send it and then reset it plus showing a little toast to inform the user that something happened!

Go ahead and add the function below the above one:

addTransaction() {
    this.ref.push(this.transaction).then(() => {
      this.transaction = {
        value: 0,
        month: 0,
        expense: false
      };
      let toast = this.toastCtrl.create({
        message: 'New Transaction added',
        duration: 3000
      });
      toast.present();
    })
}

Now we got the basic read/write stuff for Firebase in place. It’s always amazing to see how fast those methods actually can be implemented!

Creating Charts from Data

The previous part was easy (hopefully?) so now it get’s a bit more complicated as we need to create our charts and fill them with the right values.

It’s not super trivial, so if you want to change things and build your own visualisation check out the docs on how your data sets need to look like!

For now, we start with a function that will transform the data list we got from Firebase into an array with cumulated values for each month.

In our example we only got 4 months, so we create an object with the 4 keys for the months and then iterate over our Firebase data. If something is an expense we need to subtract it, else we just add the value. If the value inside reportByMonth object is not yet set, we simply start with the value of the iteration.

I’ve added the + before every value to make sure it’s converted to a number, so this line looks kinda strange:

0 - +trans.value;

But it simply says to start with zero and subtract the value of the transaction.

Once we got the key/value pairs ready, we convert all of it back to an array as the chart needs this. Yes we could have used an array in the first place, but making the calculation wouldn’t be as easy as it is with the map.

Anyway, now add your next function below your existing ones:

getReportValues() {
  let reportByMonth = {
    0: null,
    1: null,
    2: null,
    3: null
  };

  for (let trans of this.chartData) {
    if (reportByMonth[trans.month]) {
      if (trans.expense) {
        reportByMonth[trans.month] -= +trans.value;
      } else {
        reportByMonth[trans.month] += +trans.value;
      }
    } else {
      if (trans.expense) {
        reportByMonth[trans.month] = 0 - +trans.value;
      } else {
        reportByMonth[trans.month] = +trans.value;
      }
    }
  }
  return Object.keys(reportByMonth).map(a => reportByMonth[a]);
}

We can now build a useful data object for our chart, so let’s implement the functionality to actually create the chart in the first place.

We can create a new Chart by using our ViewChild and saving it back to a local variable which we need to update the chart later. The initialisation of your Chart can become very long as you can see, and these are just a few settings. You can go really bonkers on all the settings regarding the UI, the axes, the values, the colors..and of course the type of the chart a s well!

In this case we picked the type “bar”, but there are a lot of amazing other types you could give a try.

Besides that special configuration for the bar you only need to make sure you get the data.labels field right and feed in your previously created array of data into data.datasets.data.

Now add the next function like this:

createCharts(data) {
  this.chartData = data;

  // Calculate Values for the Chart
  let chartData = this.getReportValues();

  // Create the chart
  this.valueBarsChart = new Chart(this.valueBarsCanvas.nativeElement, {
    type: 'bar',
    data: {
      labels: Object.keys(this.months).map(a => this.months[a].name),
      datasets: [{
        data: chartData,
        backgroundColor: '#32db64'
      }]
    },
    options: {
      legend: {
        display: false
      },
      tooltips: {
        callbacks: {
          label: function (tooltipItems, data) {
            return data.datasets[tooltipItems.datasetIndex].data[tooltipItems.index] +' $';
          }
        }
      },
      scales: {
        xAxes: [{
          ticks: {
            beginAtZero: true
          }
        }],
        yAxes: [{
          ticks: {
            callback: function (value, index, values) {
              return value + '$';
            },
            suggestedMin: 0
          }
        }]
      },
    }
  });
}

Finally, the update function makes use of the locally stored reference to our chart and simply updates the datasets. In the setup you’ve already seen that datasets itself can be an array, therefore you should make sure you update all datasets (although we know here that there’ll be only one set in this case).

Create your update function like this:

updateCharts(data) {
  this.chartData = data;
  let chartData = this.getReportValues();

  // Update our dataset
  this.valueBarsChart.data.datasets.forEach((dataset) => {
    dataset.data = chartData
  });
  this.valueBarsChart.update();
}

That’s all from the JavaScript side! Hope you are not lost at this point, perhaps take a moment to go back to the report calculation and chart creation before finishing the tutorial with the actual view.

Crafting the View

We have all objects and functions in place so we just need to build a tiny little view that triggers all the great actions. We create a super simple input logic to hook up our fields to the transaction variable and some logic to the button at the start of the row to switch the expense state and therefore display a plus or minus.

The actual chart is inside a canvas and can be reached as a viewChild by the name “valueBarsCanvas“.

If you want to hide your charts while loading data, make sure you are not using *ngIf as this will get you into problems as the object will not be created and therefore your viewChild will not be found. Therefore, I simply used the hidden attribute on the card element above to make sure the card will be displayed only when we got some data for the chart!

Now add the las missing piece and change your pages/home/home.html to:

<ion-header>

    <ion-navbar color="primary">
      <ion-title>Transactions</ion-title>
    </ion-navbar>
  
  </ion-header>
  
  
  <ion-content>
  
    <ion-row align-items-end>
      <ion-col col-2>
        <button ion-button icon-only outline (click)="transaction.expense = !transaction.expense" [color]="transaction.expense ? 'danger' : 'primary'">
          <ion-icon name="remove" *ngIf="transaction.expense"></ion-icon>
          <ion-icon name="add" *ngIf="!transaction.expense"></ion-icon>
        </button>
      </ion-col>
      <ion-col col-5>
        <ion-item>
          <ion-label floating>Value</ion-label>
          <ion-input type="number" [(ngModel)]="transaction.value"></ion-input>
        </ion-item>
  
      </ion-col>
      <ion-col col-5>
        <ion-item>
          <ion-label floating>Month</ion-label>
          <ion-select [(ngModel)]="transaction.month">
            <ion-option [value]="month.value" *ngFor="let month of months">{{ month.name }}</ion-option>
          </ion-select>
        </ion-item>
  
      </ion-col>
    </ion-row>
  
    <ion-row>
      <ion-col>
        <button ion-button full (click)="addTransaction()">Add Transaction</button>
      </ion-col>
    </ion-row>
  
    <ion-card [hidden]="!chartData">
      <ion-card-header>
        Analytics
      </ion-card-header>
      <ion-card-content>
        <canvas #valueBarsCanvas></canvas>
      </ion-card-content>
    </ion-card>
  
  </ion-content>

That’s it!

You’ve just build your own realtime charts app with backend connection – how cool is that?

Think of all the great apps you could build now!

Conclusion

Firebase is really powerful in a lot of ways, and combining it with the great Chart.js package allows us to show a beautiful data visualisation which get’s updated in real time. Perhaps a dashboard with updating charts would be something your boss would really enjoy? I heard they like charts. And colours.

So what could you build based on this template?

Let me know your ideas!

You can also find a video version of this article below.

The post How to Visualise Firebase Data with Chart.js and Ionic appeared first on Devdactic.

Building a Calendar for Ionic With Angular Calendar & Calendar Alternatives

$
0
0

Recently I was in need of a Calendar Component for one of my Ionic apps. Although there are some premade options available, none of them really solved all my needs out of the box. So I tried Angular Calendar..

After doing some researching and finally picking a package, I combined all my learning into this article to give you a little overview about different options for adding a Calendar to your Ionic app and also an approach to one of them!

Angular Calendar Comparison

We don’t have to reinvent the wheel all the time (although we could), there are great packages and modules already out there waiting to be used! Let’s take a look at a few of them I found the most popular among the different options:

Ionic2-Calendar

We’ve seen this calendar in action with Ionic before in this post, and it’s working great with Ionic (as it’s the only one directly made for Ionic). The different views for day/week/month look plain and good, the calendar itself allows some customisation settings as well.

Besides the good, the not so good is that some functionalities are not yet there like dragging or resizing events you might be used to from Google Calendar. Also, it’s pretty hard to do things like marking the active day and in the end you find yourself writing a lot more CSS then you initially thought.

Angular FullCalendar

This Calendar for Angular is using the quite popular FullCalendar, a jQuery component. This Calendar looks super functional from the outside but I’m not a big fan of bringing in any Plugin that has a dependency to jQuery as we generally don’t want that lib in our project as well.

Perhaps I’ll give this a try one day, if you already have definitely let me know because from the UI and funtionality this one looks like it has everything a good calendar needs.

PrimeNg Schedule

Just recently I learned about the Prime Faces package through one of the Ionic Academy members. It’s basically a package with nice UI elements for Angular. And they also got this Schedule component which get’s close to a Calendar component.

As we already have one great library with UI Elements (hello Ionic) I’m here as well not the biggest fan of using another library and increasing our apps size. Besides that objection, the functionality looks just as good as the Full Calendar one before.

Angular Calendar

In terms of environment this one comes the closest to the Ionic Calendar as it’s made for Angular – and what’s made for Angular is also good for Ionic, right? Also, the documentation and examples provided for this package are epic.

This calendar allows a lot of customisation with different options out of the box. The only downside here is the UI which is not really made for mobile as it’s also stated on the package site. However, with some minor customisations we can make this Ionic-ready and this is the one we will inspect throughout the rest of this article!

DIY

Although I advised not to reinvent the wheel sometimes all the options simply don’t work for your project. I wanted to link to 2 more good articles on how to get started, one directly for Ionic and one showing the basics with Angular:

If you want to build your own component, I think those 2 are great starting points!

Anyway, all of the maintainers deserve respect for their hard work.

I’ve simple picked the one I felt most comfortable with using for now, but eventually I’ll give the other options a chance as well. If you would like to see anyone of them in action, just let me know and I’ll happily craft a tutorial with them!

Setting up the Calendar App

Enough of the talk – it’s time for some code! As said before, we will make use of the Angular Calendar now and implement everything we need with Ionic.

We also want to achieve a special weekly view (which is actually the standard in all apps) and the result will look like the image below.

devdactic-calendar-animated

Normally, the Calendar doesn’t support this type of view, but there’s another package that’s basically adding a day view into each day of the weeks view.

To finally get started, create a new Ionic app and install both the basic package as well as the one for our special week view and finally the Angular animations package:

ionic start devdacticCalendar blank
cd devdacticCalendar
npm i angular-calendar angular-calendar-week-hours-view @angular/animations
ionic g provider customEventTitleFormatter
ionic g provider customDateFormatter

We’ve also already created 2 providers which we will use later to format some labels.

Now we need to import all the previous stuff inside our Module. Besides that, I added 3 lines which help to localise our view later on. In this case we are loading the German locale (which is “de”), of course you could change this to whatever Language you need!

If you just want to use English, simply leave this out. Now go ahead and change your app/app.module to:

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 { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';

import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

import { CalendarModule, CalendarDateFormatter, CalendarEventTitleFormatter } from 'angular-calendar';

import { CalendarWeekHoursViewModule } from 'angular-calendar-week-hours-view';

import { CustomEventTitleFormatterProvider } from '../providers/custom-event-title-formatter/custom-event-title-formatter';
import { CustomDateFormatterProvider } from '../providers/custom-date-formatter/custom-date-formatter';

import localeDe from '@angular/common/locales/de';
import { registerLocaleData } from '@angular/common';
registerLocaleData(localeDe);

@NgModule({
  declarations: [
    MyApp,
    HomePage
  ],
  imports: [
    BrowserModule,
    IonicModule.forRoot(MyApp),
    BrowserAnimationsModule,
    CalendarModule.forRoot(),
    CalendarWeekHoursViewModule
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage
  ],
  providers: [
    StatusBar,
    SplashScreen
  ]
})
export class AppModule {}

We will later come back to the Module when we make use of our formatters, for now we can leave it like this.

Copying NPM Module Styles

Both the calendar and the week view have a custom CSS inside their Node Module package which we need to import. It doesn’t work that easy out of the box, we need to create a custom configuration to copy those CSS files to our build folder like described here.

Therefore, start by adding this block to your package.json and make sure you get the commas right and don’t mess up the files structure:

"config": {
   "ionic_copy": "./config/copy.config.js"
}

We have told Ionic to use a custom copy config, so create a folder config at the root of your project and inside a file called copy.config.js with this content:

module.exports = {
    copyAssets: {
        src: ['{{SRC}}/assets/**/*'],
        dest: '{{WWW}}/assets'
    },
    copyIndexContent: {
        src: ['{{SRC}}/index.html', '{{SRC}}/manifest.json', '{{SRC}}/service-worker.js'],
        dest: '{{WWW}}'
    },
    copyFonts: {
        src: ['{{ROOT}}/node_modules/ionicons/dist/fonts/**/*', '{{ROOT}}/node_modules/ionic-angular/fonts/**/*'],
        dest: '{{WWW}}/assets/fonts'
    },
    copyPolyfills: {
        src: ['{{ROOT}}/node_modules/ionic-angular/polyfills/polyfills.js'],
        dest: '{{BUILD}}'
    },
    copySwToolbox: {
        src: ['{{ROOT}}/node_modules/sw-toolbox/sw-toolbox.js'],
        dest: '{{BUILD}}'
    },
    copyCalendarCss: {
        src: './node_modules/angular-calendar/css/angular-calendar.css',
        dest: '{{BUILD}}'
    },
    copyWeekHoursCss: {
        src: './node_modules/angular-calendar-week-hours-view/angular-calendar-week-hours-view.scss',
        dest: '{{BUILD}}'
    }
}

The first 5 entries are actually copied from the original Ionic config, and the last 2 are added to copy over our own CSS files automatically!

When the files will be copied now, we only need to link them inside the src/index.html with the correct URL so add these 2 entries somewhere inside the HEAD area:

<link rel="stylesheet" href="build/angular-calendar.css">
<link rel="stylesheet" href="build/angular-calendar-week-hours-view.scss">

It seems not so super easy but actually it’s a nice way and we don’t have to take care of making sure the files are there – they will be automatically copied to the right location!

Now e got everything setup to finally build our Calendar!

The Calendar View

There’s a lot of code involved in this article so let’s start with the view which is perhaps one of the easier files. First of all, we can make use of a calendarDate pipe which is used to print out the right title for our view based on the current active date. Later we will also change the apperance of this title using our own formatters.

Below the header we craft a bar of 3 buttons to navigate into the future or past and back to today. Nothing special in general – but we can actually use a directive on those buttons that will automatically take care of the operation of changing the current date variable viewDate! That means, we don’t need a dedicated function to change our date.

At the bottom we finally use the calendar module with a few of it’s options. We will create most of the values inside our class and also implement the event functions there. There are even more options you could set, so definitely check out the great documentation for more information.

For now go ahead and change your pages/home/home.html to:

<ion-header>
  <ion-navbar>
    <ion-title>
      {{ viewDate | calendarDate:(view + 'ViewTitle') }}
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content>

  <ion-row>
    <ion-col col-12 col-md-3>
      <ion-row>
        <ion-col col-3 no-padding>
          <button ion-button full clear icon-only mwlCalendarPreviousView [view]="view" [(viewDate)]="viewDate">
            <ion-icon name="ios-arrow-back"></ion-icon>
          </button>
        </ion-col>
        <ion-col col-6 no-padding>
          <button ion-button full clear mwlCalendarToday [(viewDate)]="viewDate">Today</button>
        </ion-col>
        <ion-col col-3 no-padding>
          <button ion-button full clear icon-only mwlCalendarNextView [view]="view" [(viewDate)]="viewDate">
            <ion-icon name="ios-arrow-forward"></ion-icon>
          </button>
        </ion-col>
      </ion-row>
    </ion-col>
  </ion-row>

  <iq-calendar-week-hours-view
    [viewDate]="viewDate"
    [events]="events"
    [hourSegments]="2"
    [dayStartHour]="6"
    [dayEndHour]="20"
    (eventClicked)="handleEvent($event.event)"
    (hourSegmentClicked)="hourSegmentClicked($event)"
    (eventTimesChanged)="eventTimesChanged($event)"
    [weekStartsOn]="1"
    [refresh]="refresh"
    [locale]="locale">
  </iq-calendar-week-hours-view>

</ion-content>

We can also already bring in some CSS. As I said in the beginning, it’s not made for mobile so we have to apply some tweaks here and there and especially make the hour column smaller as it’s taking away too much space on a small device.

For now, change your pages/home/home.scss to:

page-home {

    .custom-event a {
        color: #fff !important;
        font-weight: bold;
        text-decoration: none;
    }

    .cal-day-view .cal-event {
        white-space: normal;
    }

    .cal-day-view .cal-event-container {
        width: 100% !important;
    }
    
    .cal-day-view .cal-time {
        width: 40px !important;
        font-size: smaller !important;
    }

    .cal-week-hours-view .cal-day-container:first-child {
        width: 40px !important;
        flex: none;
    }

    .cal-week-hours-view .cal-header:first-child {
        width: 40px !important;
        flex: none;
        border: none;
    }

    .cal-header {
        border-left: solid 1px #e1e1e1;
    }

    .cal-today {
        color: color($colors, primary) !important;
    }

}

Your Calendar is almost ready but we need some settings and events from our class before we can start it finally, so let’s do this now.

The Calendar Logic

A Calendar needs events, and with this package we can add quite a bunch of options already to our events. We can event set colours and decide whether an event can be resized or dragged!

Before we create the events, we also add a few variables which were used before by our view and the binding between the component and this class. I’m again setting my locale to “de” here, simply switch this to en or whatever you like.

We also need a Subject here which is used later on to inform the Component that it should reload the data.

Below the constructor we then got 3 functions, all of them will be called based on actions of our Calendar:

  • handleEvent: We’ve clicked on an event. Simply open an alert with some information for now
  • eventTimesChanged: The event was dragged or resized and therefore the times need to be changed. When working with this, my function was called twice all the time and messed up the times, therefore I added the isDragging variable which prevents the function from being called and executed immediately again (quick & dirty)
  • hourSegmentClicked: This functions is called whenever we clikc a free time slot. We simply generate a new dummy event at that time and add it to our array. Make sure to call refresh.next() so your calendar is updated!

I’m not a big fan of splitting up listings as it’s easier for you to make errors or forget code, so here’s the full listing for your pages/home/home.ts:

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { Subject } from 'rxjs/Subject';
import { AlertController } from 'ionic-angular/components/alert/alert-controller';
import {
  startOfDay,
  endOfDay,
  subDays,
  addDays,
  endOfMonth,
  isSameDay,
  isSameMonth,
  addHours
} from 'date-fns';
import {
  CalendarEvent,
  CalendarEventTimesChangedEvent
} from 'angular-calendar';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {
  
  viewDate: Date = new Date();
  view = 'week';
  locale: string = 'de';
  isDragging = false;
  refresh: Subject<any> = new Subject();

  events: CalendarEvent[] = [
    {
      start: addHours(startOfDay(new Date()), 7),
      end: addHours(startOfDay(new Date()), 9),
      title: 'First Event',
      cssClass: 'custom-event',
      color: {
        primary: '#488aff',
        secondary: '#bbd0f5'
      },
      resizable: {
        beforeStart: true,
        afterEnd: true
      },
      draggable: true
    },
    {
      start: addHours(startOfDay(new Date()), 10),
      end: addHours(startOfDay(new Date()), 12),
      title: 'Second Event',
      cssClass: 'custom-event',
      color: {
        primary: '#488aff',
        secondary: '#bbd0f5'
      },
      resizable: {
        beforeStart: true,
        afterEnd: true
      },
      draggable: true
    }
  ];

  constructor(public navCtrl: NavController, private alertCtrl: AlertController) { }

  handleEvent(event: CalendarEvent): void {
    let alert = this.alertCtrl.create({
      title: event.title,
      message: event.start + ' to ' + event.end,
      buttons: ['OK']
    });
    alert.present();
  }

  eventTimesChanged({event, newStart, newEnd} : CalendarEventTimesChangedEvent): void {
    if (this.isDragging) {
      return;
    }
    this.isDragging = true;

    event.start = newStart;
    event.end = newEnd;
    this.refresh.next();

    setTimeout(() => {
      this.isDragging = false;
    },1000);
  }

  hourSegmentClicked(event): void {
    let newEvent: CalendarEvent = {
      start: event.date,
      end: addHours(event.date, 1),
      title: 'TEST EVENT',
      cssClass: 'custom-event',
      color: {
        primary: '#488aff',
        secondary: '#bbd0f5'
      },
      resizable: {
        beforeStart: true,
        afterEnd: true
      },
      draggable: true
    }

    this.events.push(newEvent);
    this.refresh.next();
  }

}

It’s nice to have many options and configuration settings directly on the component, and working with the events of the type CalendarEvent works great as well.

Your calendar should now be ready and work like you’ve seen above!

Additional Formatting

If you want to take it one step further you can – of course – customise the view titles and different display options of the calendar. WE’ve created 2 providers in the beginning, let’s bring both of them to life.

The CustomDateFormatterProvider is used for almost all time and date related strings, therefore we override the functions inside our providers/custom-date-formatter/custom-date-formatter.ts like this:

import { CalendarDateFormatter, DateFormatterParams } from 'angular-calendar';
import { DatePipe } from '@angular/common';
import { Injectable } from '@angular/core';
import { getISOWeek } from 'date-fns';

@Injectable()
export class CustomDateFormatterProvider extends CalendarDateFormatter {

  public dayViewHour({ date, locale }: DateFormatterParams): string {
    return new DatePipe(locale).transform(date, 'HH:mm', locale);
  }

  public weekViewTitle({ date, locale }: DateFormatterParams): string {
    const year: string = new DatePipe(locale).transform(date, 'y', locale);
    const weekNumber: number = getISOWeek(date);
    return `Woche ${weekNumber} in ${year}`;
  }

  public weekViewColumnHeader({ date, locale }: DateFormatterParams): string {
    return new DatePipe(locale).transform(date, 'E', locale);
  }

  public weekViewColumnSubHeader({ date, locale }: DateFormatterParams): string {
    return new DatePipe(locale).transform(date, 'MM/dd', locale);
  }

}

You can see that I’m also setting a special German title here and change the formatting of the different labels to make them smaller for a mobile device.

Besides that, there’s a tooltip that pops up on an event and I don’t really like it, so heres the code to not show it anymore. Open your providers/custom-event-title-formatter/custom-event-title-formatter.ts and change it to:

import { CalendarEventTitleFormatter, CalendarEvent } from 'angular-calendar';
import { Injectable } from '@angular/core';

@Injectable()
export class CustomEventTitleFormatterProvider extends CalendarEventTitleFormatter {

  dayTooltip(event: CalendarEvent): string {
    return;
  }
}

Just creating those 2 providers and overriding the original functions is not enough in this case – we need to provide them for our module and tell it to use those 2 instead of the original files.

This is done in the same way you would use Ionic Native Mocks, simply add these entries to the array of providers inside your app/app.module.ts

{
  provide: CalendarDateFormatter,
  useClass: CustomDateFormatterProvider
},
{
  provide: CalendarEventTitleFormatter,
  useClass: CustomEventTitleFormatterProvider
}

Conclusion

So this was the Calendar Compendium, and you’ve seen one of the Calendar options in action now. Again, they all have their pros and cons and everyone has different needs for a Calendar.

The package presented is a good combination but it’s still lacking some mandatory stuff. Right now, you can drag and resize events only on one day, they can’t be moved to the next days. This is due to the implementation of the special week view, so if that’s a show stopper for you try one of the other alternatives!

Which Calendar have you used before?

I’m really interested in what else there might be to find the best possible solution for Ionic apps!

You can also find a video version of this article below.

The post Building a Calendar for Ionic With Angular Calendar & Calendar Alternatives appeared first on Devdactic.

Viewing all 183 articles
Browse latest View live