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

How to Publish a Custom Ionic Module With NPM

$
0
0

Have you ever thought about creating your own custom Ionic Module? Wouldn’t it be nice to publish a part of your work and distribute it through NPM? Truth is, it’s actually more easy than you might think!

Recently I found a great Ionic component on Github but you had to download and integrate it which was not working directly. I thought this would be the perfect spot to create a custom Ionic Module and see how far I can get (spoiler: it is published).

Also, this approach makes great use of the Ionic Module Template which apparently is not working to 100% therefore we’ll go through the needed steps for your own custom module now!

Configure your Custom Ionic Module Package

You can start this tutorial with a blank folder, no Ionic app is needed!

First of all we need some information about our package, the structure, scripts used and where files will be. All of this needs to be available inside the final package as well and therefore we need to put the information inside the package.json like this:

{
  "name": "ionic-academy-package",
  "version": "0.0.1",
  "description": "A Simple Ionic Academy Module",
  "main": "./dist/index.js",
  "typings": "./dist/index.d.ts",
  "files": ["dist"],
  "scripts": {
    "ngc": "ngc",
    "build": "rm -rf dist && npm run ngc",
    "publishPackage": "npm run build && npm publish"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/saimon24/ionic-academy-module.git"
  },
  "keywords": [],
  "author": "Simon Grimm",
  "license": "MIT",
  "homepage": "https://ionicacademy.com",
  "devDependencies": {
    "@angular/common": "5.0.3",
    "@angular/compiler": "5.0.3",
    "@angular/compiler-cli": "5.0.3",
    "@angular/core": "5.0.3",
    "@angular/forms": "^5.2.7",
    "@angular/platform-browser": "5.0.3",
    "@angular/platform-browser-dynamic": "5.0.3",
    "@types/node": "^9.4.6",
    "ionic-angular": "3.9.2",
    "rxjs": "5.5.2",
    "typescript": "2.4.2",
    "zone.js": "0.8.18"
  }
}

For now just create the file and perhaps change the names and URL if you already want to work on your own custom component.

Next step is to give our TypeScript compiler a bit of advise about our project, so create a new file tsconfig.json and insert:

{
  "compilerOptions": {
    "module": "es2015",
    "target": "es5",
    "moduleResolution": "node",
    "sourceMap": true,
    "inlineSources": true,
    "declaration": true,
    "noImplicitAny": false,
    "experimentalDecorators": true,
    "lib": ["dom", "es2015"],
    "outDir": "dist"
  },
  "exclude": [
    "node_modules",
    "dist",
    "scripts"
  ],
  "angularCompilerOptions": {
    "skipTemplateCodegen": true
  }
}

This is just general information about how our project TypeScript files should be transpiled, and you normally find this file automatic inside your Ionic apps folder.

Create your Custom Ionic Module

The previous 2 files are enough for the base, now we focus on creating the actual custom module. Go ahead and create a folder src/ where the logic/view of our custom module will live.

Inside that folder create 2 more folders called components/ and providers/. You know these folders from a standard Ionic app and they will have the same role inside our module as well.

Let’s start with a super simple provider that our module can finally export. For this tutorial we’ll just add one function that returns a reason why you should join the Ionic Academy.

Now create a new file at src/providers/academy-provider.ts and insert:

import { Injectable } from '@angular/core';

@Injectable()
export class AcademyProvider {
  reasonToJoin() {
    return 'The Ionic Academy helps you to learn everything Ionic!';
  }
}

Just like we did for the provider we can now also create a component. Apparently at the moment we need to build the HTML and CSS inline and can’t use a template file like we are used to.

But the rest of the logic follows the same principles, so create a new file a t src/components/academy-component.ts and insert:

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

const HTML_TEMPLATE = `
<ion-header>
  <ion-navbar color="primary">
    <ion-title>
      Ionic Academy
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>

<div class="special-text">Welcome to the special Ionic Academy Module!</div>
  <button ion-button full icon-left (click)="leavePage()">
    <ion-icon name="close"></ion-icon>
  Close the Page</button>
</ion-content>
`;

const CSS_STYLE = `
.special-text {
    font-weight: 800;
    font-size: 15pt;
    text-align: center;
    color: #0000FF;
}
`;

@Component({
  selector: 'academy-component',
  template: HTML_TEMPLATE,
  styles: [CSS_STYLE]
})
export class AcademyComponent {
  constructor(private navCtrl: NavController) {}

  leavePage() {
      this.navCtrl.pop();
  }
}

Now that’s a bit more code, but we basically craft a simple UI inline, add some styling to it and implement one function that will make the page pop back to where we came from.

Inside the app later we can easily push this whole component, and when the user leaves the component we are back inside our app without any problems.

Export Your Custom Ionic Module

We have created the configuration and logic for our custom Ionic Module, now it’s time to export everything so we can use it later.

For this we first of all need a new file at src/index.ts which only exports our 2 files:

export * from './ionic-academy.module';
export * from './components/academy-component';
export * from './providers/academy-provider';

We have referenced the index file inside our package.json in the beginning as the entry point, so it needs all relevant information. Besides that, we also need to create the custom module which can then be imported later in your app.

The module looks quite like the one you might know but has and additional export class which exports the whole module. Go ahead and create the new file at src/ionic-academy.module.ts and insert:
src/ionic-academy.module.ts

import { NgModule, ModuleWithProviders } from '@angular/core';
import { AcademyComponent } from './components/academy-component';
import { AcademyProvider } from './providers/academy-provider';
import { IonicModule } from 'ionic-angular';

@NgModule({
    imports: [
        // Only if you use elements like ion-content, ion-xyz...
        IonicModule
    ],
    declarations: [
        // declare all components that your module uses
        AcademyComponent
    ],
    exports: [
        // export the component(s) that you want others to be able to use
        AcademyComponent
    ]
})
export class IonicAcademyModule {
    static forRoot(): ModuleWithProviders {
        return {
            ngModule: IonicAcademyModule,
            providers: [AcademyProvider]
        };
    }
}

If you get error messages while developing the module make sure to run npm install so the module actually finds the dependencies!

When you are finished, your project structure looks like this:

Of course you can replace everything inside with your own logic (which you should do), but let’s cover the last step to get this published. If you have no NPM account, you need to create one now which is completely free but needed to publish the module.

Actually our app is so well prepared at this point that you only have to run one script:

npm run publishPackage

# Which  runs: npm run build && npm publish

This will build our custom module and then run the publish command to make it available through NPM! If you run the command for the first time you might need to login or answer questions, but once done you should be able to find the final package inside the list of your own published packages inside your profile.

Using the Custom Ionic Module

Congratulations, if you’ve come this far your package should be published through NPM! It’s a bit easier to publish something there than inside iTunes connect, so don’t worry that it will take time, after upload it is ready immediately.

Normally you could also link an NPM package inside another project while developing it but that’s not working right now as stated on the page of the Ionic Module Template.

Anyway our package can be used, so if you have any Ionic app at hand give it a try and install:

npm i ionic-academy-package

This will of course install the custom Ionic Module developed for this article, if yours is available install that! It’s just about understanding the mechanics.

To use a custom module we now need to import all the information inside app/app.module.ts like this:

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 { IonicAcademyModule, AcademyProvider, AcademyComponent } from 'ionic-academy-package';

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

We use everything we’ve created inside the module and after the import statement you should also get some code completion for the module!

To finally use it, you can directly use the created provider and component inside a page like this:

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { AcademyComponent, AcademyProvider } from 'ionic-academy-package';

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

  constructor(public navCtrl: NavController, private academyProvider: AcademyProvider) {
    this.info = this.academyProvider.reasonToJoin();
  }

  showAcademyComponent() {
    this.navCtrl.push(AcademyComponent);
  }
}

As you can see we make use of the provider function reasonToJoin() and also push the AcademyComponent directly on our view stack without any problems. If you’ve used this package, the result looks like the image below where we can see the information of the provider and the new component/page.

ionic-custom-module

And that’s how you create a custom Ionic module – wasn’t actually that hard, right?

Further Additions

We’ve covered only the basics but this should help you get started with your own modules. There are also things I’ve not included here to keep it short which are still super important.

One of them is to create and upload your Github repository so people can also see what’s going on, they can open issues help you grow the module.

Also, when you create the Github repo you need to add a readme file with all the information about how to use your package for other users, and you can find an example of this inside the custom module I created (which basically wraps a component created by Carson Chen).

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

The post How to Publish a Custom Ionic Module With NPM appeared first on Devdactic.


Celebrating One Year Ionic Academy

$
0
0

Almost exactly one year ago on March 28. the Ionic Academy opened doors for the first time and has ever since been the coding school for all Ionic interested developers (and those who want to become Ionic Developers).

Now it’s time to reflect on the first year of this project to give you a better idea what it is all about, why it exists and what you can expect from the Ionic Academy in 2018.

Stay with me till the end of the article for a special One Year Ionic Academy Gift!

A Tough But Good Start

The initial interest even before the project started was huge. More than 500 people were on the waiting list and over 100 joined already in the first week last year. This response completely blew my mind, but there were also critics.

Max from Ionic posted about the launch of the new Ionic Academy and the comments on that article were by no means encouraging and sometimes even a bit offending.

Truth is those comments got me thinking about the whole project, but I never questioned the mission of this endeavour.

To some degree I could understand a few comments as the initial starting page was by no means a good-looking page. But people even questioned the content without having seen any of the courses inside, which seemed kinda rude to me at that time.

Since then I’ve reworked the initial page many times to make it more “friendly” and less “sleezy”, to offer more information about what to expect and what’s inside. Also, there’s a clear refund policy and while not many make use of it, I’ll always stick to my words there.

Finally, the process is never finished. Right now I’m working on allowing free preview of some courses and videos so people can anticipate even better upfront if the Ionic Academy is right for them.

No project will make everyone comfortable, and I understand that the Ionic Academy might not be for everyone.

But I’ll continue to focus on helping developers to learn everything Ionic as fast as possible, which is the main purpose of the Ionic Academy.

Questions & Answers

Over the year I’ve monitored mentions of the Ionic Academy inside the official Ionic forum, on Twitter and wherever possible. Also, I got countless emails with questions so I’ll try to answer a few of the ones that stuck with me.

Is the Ionic Academy created by the official Ionic Company?

No. I (Simon) am the creator of the Ionic Academy and I don’t receive any money from the official Ionic team. The only connection we have is the constant exchange of information and I try to keep Max updated about the progress of the Ionic Academy as it’s of course also in their interest that this learning platform grows.

Is the Ionic Academy free?

No. All the material on this blog, my YouTube channel and the other places I hang around is free but the Ionic Academy is a paid membership site. The reason is simply the higher form of engagement and time needed to create the content, the best possible experience for new Ionic developers and also the support effort on the members only forum.

Is the Ionic Academy worth it?

Well this one depends on your and your needs and financial situation. It is definitely not for everyone but the feedback I’ve gotten over the year is that the content and courses have helped countless people to get started with Ionic a lot faster and build their own apps in no time, even without prior knowledge.

I have basic Programming knowledge, can I join the Ionic Academy?

Yes! The courses start at zero, even with an Angular introduction so if you have seen HTML, CSS and perhaps JavaScript or at least know how to write functions and what coding is about, I’m pretty sure you can follow the basic material and then work your way up.

Ionic Academy 2018 Roadmap

At the end of 2017 I created a post on the Ionic Academy 2017 in Numbers and in terms of numbers, there are currently over 30 courses inside the library.

This number will of course grow again this year, especially with Ionic 4 coming, support for different frameworks, Capacitor, Stencil, PWAs… There is so much to talk about!

Another change already happening is the option for Ionic Academy Business Accounts. Now you can directly provide your team of developers with accounts, managed from one master account.

For 2 months there was an additional challenge which was designed to get people to code something new and then review the results together. This has not worked well, simply because people just don’t have that much spare time.

I still enjoy the idea of these small quizzes, perhaps we’ll see a relaunch in a different form this year.

Finally, a recent conversation on Twitter got me thinking about creating a dedicated app (of course with Ionic) for the Ionic Academy so members could download their courses and watch them on their commute. This is definitely a high priority task and something you can expect later this year!

Not yet a Member?

If you’re not yet a member of the Ionic Academy this week could be your week. To celebrate the first birthday, you can join the Ionic Academy and lock in a reduced fee!

If you want to stick around longer you can save even more with the annual package of the Ionic Academy!

Simply use the links above to get to the registration for the packages directly. This offer is only valid this week until Friday, 30. March!

I’ll see you inside the Ionic Academy for some months or a year.

The post Celebrating One Year Ionic Academy appeared first on Devdactic.

Loading Dynamic Components with Ionic & Angular ComponentFactoryResolver

$
0
0

Recently one member of the Ionic Academy asked for help regarding a tough situation: How to display dynamic components based on a JSON object structure you might get from an API?

One solution to this problem is the use of the Angular ComponentFactoryResolver. Imagine you have a process with different steps that can be ordered by users how they need them – now you want to display the right process step to the user inside your Ionic app based on his decision.

This case is what we will build inside this tutorial so we can finally create dynamic components.

App Structure

To get started we create a blank new Ionic app and add a few components and a provider. However, using the ComponentFactoryResolver we need to directly reference our components at the top level of our app and not only inside a dedicated module like normally.

Therefore, we can remove the components.module.ts file and later add everything to our main module. Also, we need two more files which we will simply create inside the components/ folder but which don’t need a template file. For all this you could run these commands:

ionic start devdacticDynamic blank
cd devdacticDynamic
ionic g provider process
ionic g component stepOne
ionic g component stepTwo
ionic g component stepThree

rm src/components/components.module.ts
touch src/components/process.ts
touch src/components/process-item.ts

We need to directly add our components to both the declarations and entryComponents array of our main module so Angular knows about them and we can dynamically create them later.

Go ahead and 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 { ProcessProvider } from '../providers/process/process';
import { StepOneComponent } from '../components/step-one/step-one';
import { StepTwoComponent } from '../components/step-two/step-two';
import { StepThreeComponent } from '../components/step-three/step-three';

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

That’s it for the basic setup. If you have an existing app just make sure that you reference the components accordingly in both arrays so they are available when we need them later.

Creating the Components

First now we add some basic stuff to our components and keep them under one interface. By doing this we can later easily cast our objects and reference their inputs and know exactly what we are working with!

Open the components/process.ts and add this interface:

export interface ProcessComponent {
    data: any;
}

Now all our 3 created components will implement this interface so all of them have one input which is data. You can go to all 3 of your components and change them like this one example, but of course keep their original class names and annotation definition:

import { ProcessComponent } from './../process';
import { Component, Input } from '@angular/core';

@Component({
  selector: 'step-one',
  templateUrl: 'step-one.html'
})
export class StepOneComponent implements ProcessComponent {

  @Input() data: any;

  constructor() {

  }

}

Finally, add some super simple HTML to all of the 3 views of our components like this:

<ion-slide>
    <h1>{{ data }}</h1>
</ion-slide>

If you want to distinguish them even better, you can change the CSS on the different components as well.

Working with the Process Provider

Now we get to the difficult stuff, but first let’s create another class which will represent a ProcessItem in our app. This item consists of an actual component and some description, and we can use this class again to get some more control over typings (yeah, TypeScript!).

Open your components/process-item.ts and change it to:

import { Type } from '@angular/core';

export class ProcessItem {
  constructor(public component: Type<any>, public desc: string) {}
}

Our provider acts as if it would get a JSON response from an API which consists some information about the process steps. This is just one example, there are various cases in which you might want to dynamically create components.

To get an array of ProcessItem we need to transform this response into the according information of components using getProcessSteps.

The function getPageOrder will then resolve the different steps and either insert a new item or call itself again if the step contains some children (expert mode here).

Finally, the resolveComponentsName needs to resolve the steps to the actual component names. This means, you can’t simply send a string from the API and translate it to a component – there need to be some kind of mapping or logic inside your app and you need to reference the components and imports.

Imagine different teams working on different components – in the end they just need to add their mapping and the backend team can send out new items which the app can resolve as well. So this can help to separate concerns inside your project or distribute the work on multiple teams.

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

import { ProcessItem } from './../../components/process-item';
import { Injectable } from '@angular/core';
import { StepOneComponent } from '../../components/step-one/step-one';
import { StepTwoComponent } from '../../components/step-two/step-two';
import { StepThreeComponent } from '../../components/step-three/step-three';

@Injectable()
export class ProcessProvider {

  private dummyJsonResponse = {
    items: [
      {
        step: 1,
        desc: 'Mighty first step'
      },
      {
        step: 2,
        desc: 'Always first looser'
      },
      {
        step: 3,
        items: [
          {
            step: 3,
            desc: 'I am the best step'
          },
          {
            step: 1,
            desc: 'Mighty first step'
          }
        ]
      },
      {
        step: 2,
        desc: 'Always first looser'
      }
    ]
  }
  
  constructor() { }

  getProcessSteps() : ProcessItem[] {
    return this.getPageOrder(this.dummyJsonResponse.items);
  }

  private getPageOrder(steps) : ProcessItem[] {
    let result : ProcessItem[] = [];

    for (let item of steps) {
      if (item.items) {
        result = result.concat(this.getPageOrder(item.items));
      } else {
        let comp = this.resolveComponentsName(item.step);
        let newItem = new ProcessItem(comp, item.desc);
        result.push(newItem);
      }
    }

    return result;
  }

  private resolveComponentsName(step) {
    if (step === 1) {
      return StepOneComponent;
    } else if (step === 2) {
      return StepTwoComponent;
    } else if (step === 3) {
      return StepThreeComponent;
    }
  }

}

Now we just need a way to create and display our components, and that’s the last step for today.

Loading Dynamic Components

To render the dynamically created components inside our view we need a parent element hosting all of it. Therefore, change your pages/home/home.html to this:

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

<ion-content padding>
  <ion-slides>
    <div #processContainer>
    </div>
  </ion-slides>
</ion-content>

In our case we wrapped our components in slides so we can swipe through our pages and see the different process steps. The processContainer is what we will be fill with all of our components now.

Inside the class we can rely completely on the logic of our provider which has already translated JSON info to component info.

We now get the array of steps along with their component and factory. The resolveComponentFactory then gives us a factory that can be used to create a new component and that’s what we do.

Finally, we can cast the new componentRef using our interface ProcessComponent and then actually set the data input of the component!

The “add this component to the view” step actually also already took place when we created the component on our container, which is the ViewChild we created at the top and which references the hosting element inside our view.

Wrap up your code by changing the pages/home/home.ts to:

import { ProcessComponent } from './../../components/process';
import { ProcessProvider } from './../../providers/process/process';
import { Component, ViewChild, ViewContainerRef, ComponentFactoryResolver } from '@angular/core';
import { NavController } from 'ionic-angular';

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

  @ViewChild('processContainer', { read: ViewContainerRef }) container;

  constructor(public navCtrl: NavController, private processProvider: ProcessProvider, private resolver: ComponentFactoryResolver) { }

  ionViewDidLoad() {
    let steps = this.processProvider.getProcessSteps();

    for (let step of steps) {
      const factory = this.resolver.resolveComponentFactory(step.component);
      let componentRef = this.container.createComponent(factory);
      (<ProcessComponent>componentRef.instance).data = step.desc;
    }
  }

}

Now you can see that your final view is dynamically creating components but actually has none of the imports! Only our provider keeps a reference to all of them, so we don’t have to touch the view and logic here later on and can apply all changes to the provider and main module.

Conclusion

Some ideas can be tricky to implement, but many times there’s an Angular way of doing things – sometimes it’s just a bit hidden and not so popular!

In this tutorial you’ve learned to generate components dynamically using the factory pattern. This was not one of the easier tutorials here, so congrats if you’ve finished all of it!

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

The post Loading Dynamic Components with Ionic & Angular ComponentFactoryResolver appeared first on Devdactic.

Ionic Canvas Drawing and Saving Images as Files

$
0
0

Working with the canvas can be challenging, but there are amazing things you can’t build using it like this little drawing application which can also save your amazing paintings inside the apps folder and load all of them again!

Along this tutorial we will implement a canvas inside our Ionic app on which we can draw in different colours. We’ll then store the canvas data as a new image inside our app and keep the information inside Ionic storage so we can access all of our paintings later.

If you don’t really need the drawing part you can also check out this signature pad tutorial which describes how to build a simple input for a signature below any document.

The final result will look like the image below, so let’s get started with our drawing app!

Starting Our Drawing App

We start with a blank Ionic app and add the Cordova plugins for the Ionic storage and also the File so we can write a new file to the filesystem of our device later. Go ahead and run:

ionic start devdacticImageCanvas blank
cd devdacticImageCanvas
ionic cordova plugin add cordova-sqlite-storage
ionic cordova plugin add cordova-plugin-file
npm install --save @ionic-native/file

Now make sure to import everything correctly into your app/app.module.ts like this:

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 { File } from '@ionic-native/file';
import { IonicStorageModule } from '@ionic/storage';

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

There are no further dependencies as the canvas is always available so that’s all from the configuration side!

Building the Canvas Drawing View

Before we get into the class logic let’s craft a simple view. Our app needs the actual canvas element along with the functions to recognise the touches and movement on the canvas. Also, we have a list of all stored images below the canvas and to make the app more user friendly we need to fix the canvas (or the surrounding div) at the top of the app.

For this, we can use the ion-fixed attribute but still need some manual coding as this would otherwise result in a very strange looking UI where our elements overlap!

Above our canvas we also add a simple selection of colours using a radio-group plus some additional logic to make it look and feel better.

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

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

<ion-content padding no-bounce>
  <div #fixedContainer ion-fixed>
    <ion-row>
      <ion-col *ngFor="let color of colors" [style.background]="color" class="color-block" tappable (click)="selectColor(color)"></ion-col>
    </ion-row>

    <ion-row radio-group [(ngModel)]="selectedColor">
      <ion-col *ngFor="let color of colors" text-center>
        <ion-radio [value]="color"></ion-radio>
      </ion-col>
    </ion-row>

    <canvas #imageCanvas (touchstart)="startDrawing($event)" (touchmove)="moved($event)"></canvas>

    <button ion-button full (click)="saveCanvasImage()">Save Image</button>
  </div>

  <ion-list *ngIf="storedImages.length > 0">
    <ion-list-header>Previous Drawings</ion-list-header>
    <ion-card *ngFor="let obj of storedImages; let i = index">
      <ion-card-content>
        <img [src]="getImagePath(obj.img)">
      </ion-card-content>
      <ion-row>
        <button ion-button full icon-only color="danger" (click)="removeImageAtIndex(i)">
          <ion-icon name="trash"></ion-icon>
        </button>
      </ion-row>
    </ion-card>
  </ion-list>
  
</ion-content>

Additional we add some CSS rules to make the view even better, but those are more or less optional so open your pages/home/home.scss and change it to:

page-home {
    canvas {
        display: block;
        border: 1px solid rgb(187, 178, 178);
    }
    .color-block {
        height: 40px;
    }
}

This won’t work by now as we haven added all the variables and functions so let’s do this now.

Painting On Our Canvas Like Picasso

There’s quite a bit of code needed for the logic and therefore I’ll split up the different parts in this section.

We start with the basic variables we gonna need plus the initial logic to make our div sticky at the top. I also added the link to an open Github issue on this topic where I got the code from to fix this current problem.

Once the app is loaded we try to load all of our stored images. This will be an empty array when we first start the app, but of course later we will get the actual stored images (more precisely: the names!).

Inside the ionViewDidLoad we also update our canvas element and set the appropriate size, and that’s all for the starting phase.

Open your pages/home/home.ts and change it to:

import { Component, ViewChild, Renderer } from '@angular/core';
import { NavController, Platform, normalizeURL, Content } from 'ionic-angular';
import { File, IWriteOptions } from '@ionic-native/file';
import { Storage } from '@ionic/storage';

const STORAGE_KEY = 'IMAGE_LIST';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {
  // Canvas stuff
  @ViewChild('imageCanvas') canvas: any;
  canvasElement: any;

  saveX: number;
  saveY: number;

  storedImages = [];

  // Make Canvas sticky at the top stuff
  @ViewChild(Content) content: Content;
  @ViewChild('fixedContainer') fixedContainer: any;

  // Color Stuff
  selectedColor = '#9e2956';

  colors = [ '#9e2956', '#c2281d', '#de722f', '#edbf4c', '#5db37e', '#459cde', '#4250ad', '#802fa3' ];

  constructor(public navCtrl: NavController, private file: File, private storage: Storage, public renderer: Renderer, private plt: Platform) {
    // Load all stored images when the app is ready
    this.storage.ready().then(() => {
      this.storage.get(STORAGE_KEY).then(data => {
        if (data != undefined) {
          this.storedImages = data;
        }
      });
    });
  }

  ionViewDidEnter() {
    // https://github.com/ionic-team/ionic/issues/9071#issuecomment-362920591
    // Get the height of the fixed item
    let itemHeight = this.fixedContainer.nativeElement.offsetHeight;
    let scroll = this.content.getScrollElement();

    // Add preexisting scroll margin to fixed container size
    itemHeight = Number.parseFloat(scroll.style.marginTop.replace("px", "")) + itemHeight;
    scroll.style.marginTop = itemHeight + 'px';
  }

  ionViewDidLoad() {
    // Set the Canvas Element and its size
    this.canvasElement = this.canvas.nativeElement;
    this.canvasElement.width = this.plt.width() + '';
    this.canvasElement.height = 200;
  }

}

Now we got the basics and can start the actual drawing part of the app. We already added the functions to the view so we now need to handle both the touchstart and touchmove events.

For the first, we simply store the current position of the touch. Also, as our canvas lives somewhere on the screen we need to calculate the offset from the actual screen using getBoundingClientRect. The result is the real position for both the x and y value!

To draw a line we can use both the starting value of our touch and the new value (calculated to with the offset). We then get our drawing context from the canvas and define how lines should look, which color they have and which width they have.

We then execute the stroke onto our canvas creating a line between two points! Actually it’s as simple as that.

These 2 functions now allow us to draw with different selected colours onto our canvas image, but we want to go one step further. Inside our saveCanvasImage function we need to transform the canvas element first of all to a base64 string using toDataURL.

I had problems storing this string directly, therefore we transform the data into a Blob using the function b64toBlob. This Blob object is now perfect to be stored, so we use the File plugin to write the file into our apps data directory.

Now add these functions below the previous code inside pages/home/home.ts and of course inside the class:

selectColor(color) {
  this.selectedColor = color;
}

startDrawing(ev) {
  var canvasPosition = this.canvasElement.getBoundingClientRect();

  this.saveX = ev.touches[0].pageX - canvasPosition.x;
  this.saveY = ev.touches[0].pageY - canvasPosition.y;
}

moved(ev) {
  var canvasPosition = this.canvasElement.getBoundingClientRect();

  let ctx = this.canvasElement.getContext('2d');
  let currentX = ev.touches[0].pageX - canvasPosition.x;
  let currentY = ev.touches[0].pageY - canvasPosition.y;

  ctx.lineJoin = 'round';
  ctx.strokeStyle = this.selectedColor;
  ctx.lineWidth = 5;

  ctx.beginPath();
  ctx.moveTo(this.saveX, this.saveY);
  ctx.lineTo(currentX, currentY);
  ctx.closePath();

  ctx.stroke();

  this.saveX = currentX;
  this.saveY = currentY;
}


saveCanvasImage() {
  var dataUrl = this.canvasElement.toDataURL();

  let ctx = this.canvasElement.getContext('2d');
  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); // Clears the canvas

  let name = new Date().getTime() + '.png';
  let path = this.file.dataDirectory;
  let options: IWriteOptions = { replace: true };

  var data = dataUrl.split(',')[1];
  let blob = this.b64toBlob(data, 'image/png');

  this.file.writeFile(path, name, blob, options).then(res => {
    this.storeImage(name);
  }, err => {
    console.log('error: ', err);
  });
}

// https://forum.ionicframework.com/t/save-base64-encoded-image-to-specific-filepath/96180/3
b64toBlob(b64Data, contentType) {
  contentType = contentType || '';
  var sliceSize = 512;
  var byteCharacters = atob(b64Data);
  var byteArrays = [];

  for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
    var slice = byteCharacters.slice(offset, offset + sliceSize);

    var byteNumbers = new Array(slice.length);
    for (var i = 0; i < slice.length; i++) {
      byteNumbers[i] = slice.charCodeAt(i);
    }

    var byteArray = new Uint8Array(byteNumbers);

    byteArrays.push(byteArray);
  }

  var blob = new Blob(byteArrays, { type: contentType });
  return blob;
}

So this will create the actual file but we still need to keep track of the files we created.

Saving Our Canvas Images

That’s why we use the Ionic storage to store the information of an image which is in this case only the name. It’s important to not store the full path to the file because segments of that URL might change and you won’t be able to find your file later on. Simple store the name and you will always find the files using file.dataDirectory later on!

When we add a new image we also scroll our list to the bottom, a nice little effect to make the app more visual appealing.

To remove an image we now only need the actual image information to remove it from the storage and also to delete the file at the given path.

Finally, the last function takes care of resolving all file URLs when you restart the app. As we only store the names of the images we need to create the correct path to the image using the file plugin.

Additional we use the normalizeURL function of Ionic to prevent any issues with the WKWebView on iOS!

Finish your code by adding the last missing functions below the previous ones inside pages/home/home.ts:

storeImage(imageName) {
  let saveObj = { img: imageName };
  this.storedImages.push(saveObj);
  this.storage.set(STORAGE_KEY, this.storedImages).then(() => {
    setTimeout(() =>  {
      this.content.scrollToBottom();
    }, 500);
  });
}

removeImageAtIndex(index) {
  let removed = this.storedImages.splice(index, 1);
  this.file.removeFile(this.file.dataDirectory, removed[0].img).then(res => {
  }, err => {
    console.log('remove err; ' ,err);
  });
  this.storage.set(STORAGE_KEY, this.storedImages);
}

getImagePath(imageName) {
  let path = this.file.dataDirectory + imageName;
  // https://ionicframework.com/docs/wkwebview/#my-local-resources-do-not-load
  path = normalizeURL(path);
  return path;
}

Conclusion

It’s actually not super hard to build your own drawing app, and working with the file system has gotten so much easier since earlier versions.

Although there’s some code involved in here, nothing of this is really fancy stuff but of course if you have any problems or issues feel free to leave a comment!

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

The post Ionic Canvas Drawing and Saving Images as Files appeared first on Devdactic.

Building an Ionic Geolocation Tracker with Google Map and Track Drawing

$
0
0

Within your Ionic App you can easily track the position of your users with the Ionic Geolocation plugin. It is also easy to add a Google Map to your app, so why not combine these 2 features into a useful app?

A few weeks ago members of the Ionic Academy brought up the idea of a geolocation tracking app like Runtastic or other apps you might know. And this can be build with basic plugins and not super hard code!

In this tutorial we will build an Ionic Geolocation Tracker which can track the route of users, display the path the user has walked inside a Google map and finally also save those information to display previous runs. It’s gonna be fun!

ionic-geolocation-tracker

Starting our Geolocation Tracker

We start by creating a blank new Ionic app and install the Geolocation plugin and also the SQLite storage so Ionic Storage uses a real database inside our final app. Right now we already add a variable to the Geolocation plugin but we need another fix for iOS later on as well:

ionic start devdacticLocationTracker blank
cd devdacticLocationTracker
ionic cordova plugin add cordova-plugin-geolocation --variable GEOLOCATION_USAGE_DESCRIPTION="To track your walks"
ionic cordova plugin add cordova-sqlite-storage
npm install --save @ionic-native/geolocation

As we want to use Google Maps we also need to load the SDK and therefore we can simply add this directly to our src/index.html right before the cordova import:

<script src="http://maps.google.com/maps/api/js?key=YOUR_API_KEY_HERE"></script>

Also, you need to insert your own key here (simply replace YOUR_API_KEY_HERE) but you can easily generate an API key by going to this page.

Now we also need to load our plugins, therefore make sure to import and connect them inside your 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 { Geolocation } from '@ionic-native/geolocation';
import { IonicStorageModule } from '@ionic/storage';

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

Finally, I already mentioned that we need some fix for iOS. If you build your app you will get a warning that you haven’t added the according information to your *.plist why you want to use the Geolocation, but you can simply add this little snippet to your config.xml which will always add the NSLocationWhenInUseUsageDescription when you build the iOS app:

<edit-config file="*-Info.plist" mode="merge" target="NSLocationWhenInUseUsageDescription">
    <string>This App wants to track your location</string>
</edit-config>

Basic View and Google Map Positions

We’ll split the next part into 2 sections, and we start with the basic view of our Google Map. Our view mainly consists of a button to start and stop our tracking, the actual Google map and also a list of previous routes which can be loaded once again into our map just lake you are used to it from your running apps!

Go ahead and start with the view by changing the pages/home/home.html:

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

<ion-content padding>

  <button ion-button full icon-left (click)="startTracking()" *ngIf="!isTracking">
      <ion-icon name="locate"></ion-icon>
      Start Tracking
    </button>
  <button ion-button full color="danger" icon-left (click)="stopTracking()" *ngIf="isTracking">
      <ion-icon name="hand"></ion-icon>
      Stop Tracking
  </button>

  <div #map id="map"></div>

  <ion-list>
    <ion-list-header>Previous Tracks</ion-list-header>
    <ion-item *ngFor="let route of previousTracks">
      {{ route.finished | date }}, {{ route.path.length }} Waypoints
      <button ion-button clear item-end (click)="showHistoryRoute(route.path)">View Route</button>
    </ion-item>
  </ion-list>
</ion-content>

Nothing fancy yet, the map is really just one element and we’ll do all the logic from inside the class but first we have to add some styling to make the map look good inside our app.

Therefore, add this bit of styling inside your pages/home/home.scss:

page-home {
    #map {
        width: 100%;
        height: 300px;
      }
}

We want to load the map on start of the app and also focus it on our current position so we will notice when the track drawing begins.

First of all we need a reference to our map inside the view using @ViewChild and then we can create a new map using new google.maps.Map with additional options. In this case we disable the different view options displayed on the map and pick the Roadmap style for the map.

We’ll also keep track of this new map variable as we need it later to draw our tracks!

After the map initialisation we also try to get the current position of the user with the geolocation plugin and if we get back the coordinates we can focus our map on this spot! We could also display a marker here but that’s left for the reader.

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

import { Component, ViewChild, ElementRef } from '@angular/core';
import { NavController, Platform } from 'ionic-angular';
import { Geolocation } from '@ionic-native/geolocation';
import { Subscription } from 'rxjs/Subscription';
import { filter } from 'rxjs/operators';
import { Storage } from '@ionic/storage';

declare var google;

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {
  @ViewChild('map') mapElement: ElementRef;
  map: any;
  currentMapTrack = null;

  isTracking = false;
  trackedRoute = [];
  previousTracks = [];

  positionSubscription: Subscription;

  constructor(public navCtrl: NavController, private plt: Platform, private geolocation: Geolocation, private storage: Storage) { }

  ionViewDidLoad() {
    this.plt.ready().then(() => {
      this.loadHistoricRoutes();

      let mapOptions = {
        zoom: 13,
        mapTypeId: google.maps.MapTypeId.ROADMAP,
        mapTypeControl: false,
        streetViewControl: false,
        fullscreenControl: false
      }
      this.map = new google.maps.Map(this.mapElement.nativeElement, mapOptions);

      this.geolocation.getCurrentPosition().then(pos => {
        let latLng = new google.maps.LatLng(pos.coords.latitude, pos.coords.longitude);
        this.map.setCenter(latLng);
        this.map.setZoom(16);
      }).catch((error) => {
        console.log('Error getting location', error);
      });
    });
  }

  loadHistoricRoutes() {
    this.storage.get('routes').then(data => {
      if (data) {
        this.previousTracks = data;
      }
    });
  }
}

You might have noticed that we also added declare var google at the top which prevents TypeScript errors later inside our code.

Right now, our app is also loading historic routes from the storage using loadHistoricRoutes but of course that’s still empty. Later this will be an array of objects, so let’s work on adding data to that array!

Tracking Geolocation and Drawing on Google Map

We now need to do 2 things:

  • Watch the current GPS position of the user
  • Draw a path on our map

After that we also need to add the tracked route to the storage, but that’s the easiest part of our app.

Inside the startTracking function we subscribe to the position of the user which means we automatically get new coordinates from the Observable!

We can then use those values to first of all add a new entry to our trackedRoute array (to have a reference to all the coordinates of the walk) and then call the redrawPath if we want to immediately update our map.

Inside that function we also check a variable currentMapTrack which always holds a reference to the current path on our map!

To actually draw we can use a Polyline which can have a few options and most important the path which expects an array of objects like [{ lat: '', lng: '' }]

This function will then automatically connect the different coordinates and draw the line, and finally after we have constructed this line we call setMap on this line which draws it onto our map!

Now go ahead and add these 2 functions below the previous functions inside pages/home/home.ts:

startTracking() {
    this.isTracking = true;
    this.trackedRoute = [];

    this.positionSubscription = this.geolocation.watchPosition()
      .pipe(
        filter((p) => p.coords !== undefined) //Filter Out Errors
      )
      .subscribe(data => {
        setTimeout(() => {
          this.trackedRoute.push({ lat: data.coords.latitude, lng: data.coords.longitude });
          this.redrawPath(this.trackedRoute);
        }, 0);
      });

  }

  redrawPath(path) {
    if (this.currentMapTrack) {
      this.currentMapTrack.setMap(null);
    }

    if (path.length > 1) {
      this.currentMapTrack = new google.maps.Polyline({
        path: path,
        geodesic: true,
        strokeColor: '#ff00ff',
        strokeOpacity: 1.0,
        strokeWeight: 3
      });
      this.currentMapTrack.setMap(this.map);
    }
  }

The last missing function is when we stop the tracking. Then we need to store the information of the previous walk and also clear our map again.

Along with the tracked route we also store the current date to have some sort of information on the actual element, of course a running app would allow to review and share your path and then perhaps add more information like the time it took, distance covered and so on.

For now though this is enough, and the last function of this tutorial is the loading of historic routes – which is actually only a call to our redraw function with the information stored in the object!

Therefore, wrapn this up by adding the last functions to your pages/home/home.ts:

stopTracking() {
  let newRoute = { finished: new Date().getTime(), path: this.trackedRoute };
  this.previousTracks.push(newRoute);
  this.storage.set('routes', this.previousTracks);

  this.isTracking = false;
  this.positionSubscription.unsubscribe();
  this.currentMapTrack.setMap(null);
}

showHistoryRoute(route) {
  this.redrawPath(route);
}

That’s it, now make sure to run your app on a real device or at least a simulator where you can change the location to test out the functionality like I did within the initial Gif at the top of this tutorial!

Conclusion

An app with features like this was requested by members of the Ionic Academy and it’s actually pretty cool and easy to implement this kind of geolocation tracker like popular apps do it!

If you have developed any apps with the help of this tutorial and the plugins definitely let us know in the comments below.

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

The post Building an Ionic Geolocation Tracker with Google Map and Track Drawing appeared first on Devdactic.

Building an Ionic OCR App with Tesseract

$
0
0

If you need some sort of text recognition inside images for your app you’ll come across the two most popular libraries called Ocrad and Tesseract. Both of them work pretty good out of the box so why not add the OCR functionality to your Ionic app?

Inside this tutorial we will use the Tesseract library with JavaScript to build a text recognition app with Ionic. Also, we’ll add the ngx-progressbar component to get a nice visual feedback of the loading state while our text recognition is working in the background!

Getting Started with OCR

We start with a blank new Ionic app and install the Tesseract JavaScript library, the progress bar and also the Ionic Native Camera plugin so we can capture images. We can also add the types for better code completion and finally of course the Cordova plugin for the camera as well, so get started with:

ionic start ocrexample blank
cd ocrexample
npm install tesseract.js @ionic-native/camera @ngx-progressbar/core
npm install @types/tesseract.js --save-dev
ionic cordova plugin add cordova-plugin-camera

Now we need to hook up everything inside our src/app/app.module.ts so we can later use the progress bar and camera therefore 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 { Camera } from '@ionic-native/camera';
import { NgProgressModule } from '@ngx-progressbar/core';

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

Finally, on iOS your app will complain if you don’t specify why you want to use the camera or library. To automatically get the needed entries inside your plist, you can add these lines to your config.xml:

<edit-config file="*-Info.plist" mode="merge" target="NSCameraUsageDescription">
    <string>We need fresh images for OCR</string>
</edit-config>
<edit-config file="*-Info.plist" mode="merge" target="NSPhotoLibraryUsageDescription">
    <string>We can also scan your library images</string>
</edit-config>

That’s all for the setup, let’s get started with our actual app!

Adding the Ionic OCR Functionality

We start with the view of our OCR example, which contains basically 2 buttons to capture and decode the image plus an Ionic card to display what the library thinks is the text inside the image. Also, we add the progress bar at the top which will only show if we actually use it from our code later.

For now change your pages/home/home.html to;

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

<ion-content padding>
  <ng-progress [min]="0" [max]="1"></ng-progress>

  <button ion-button full (click)="selectSource()">Select Image</button>
  <button ion-button full (click)="recognizeImage()" [disabled]="!selectedImage">Recognize Image</button>

  <img [src]="selectedImage" *ngIf="selectedImage">

  <ion-card *ngIf="imageText">
    <ion-card-header>
      Image Text
    </ion-card-header>
    <ion-card-content>
      {{ imageText }}
    </ion-card-content>
  </ion-card>
</ion-content>

The progress bar would normally be at the top of the page, however within Ionic it will be hidden behind the navigation bar. To fix this, simply shift the bar a bit down through the pages/home/home.scss like this:

page-home {
    .ng-progress-bar {
        margin-top: 88px;
    }
}

It’s a quick fix, perhaps you’ll also need a different distance on Android. We are also using the basic functionality of the bar, there are many more great options you can specific for this component as well!

To use Tesseract we need an image, and to get an image we start by presenting an action sheet to either select one from the library or capture a new image. Once the user has selected/captured an image it will be set as base64 value to our selectedImage which is displayed within our view.

When we want to decode the image, we can use the recognize() function of Tesseract which will also emit all the progress events so we can update the bar to display the current status. If you run it the first time this might take a few moments as it’s loading some sources but the following times will be a lot faster.

Go ahead and change your pages/home/home.ts to;

import { Component } from '@angular/core';
import { NavController, ActionSheetController, LoadingController } from 'ionic-angular';
import { Camera, PictureSourceType } from '@ionic-native/camera';
import * as Tesseract from 'tesseract.js'
import { NgProgress } from '@ngx-progressbar/core';

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

  selectedImage: string;
  imageText: string;

  constructor(public navCtrl: NavController, private camera: Camera, private actionSheetCtrl: ActionSheetController, public progress: NgProgress) {
  }

  selectSource() {    
    let actionSheet = this.actionSheetCtrl.create({
      buttons: [
        {
          text: 'Use Library',
          handler: () => {
            this.getPicture(this.camera.PictureSourceType.PHOTOLIBRARY);
          }
        }, {
          text: 'Capture Image',
          handler: () => {
            this.getPicture(this.camera.PictureSourceType.CAMERA);
          }
        }, {
          text: 'Cancel',
          role: 'cancel'
        }
      ]
    });
    actionSheet.present();
  }

  getPicture(sourceType: PictureSourceType) {
    this.camera.getPicture({
      quality: 100,
      destinationType: this.camera.DestinationType.DATA_URL,
      sourceType: sourceType,
      allowEdit: true,
      saveToPhotoAlbum: false,
      correctOrientation: true
    }).then((imageData) => {
      this.selectedImage = `data:image/jpeg;base64,${imageData}`;
    });
  }

  recognizeImage() {
    Tesseract.recognize(this.selectedImage)
    .progress(message => {
      if (message.status === 'recognizing text')
      this.progress.set(message.progress);
    })
    .catch(err => console.error(err))
    .then(result => {
      this.imageText = result.text;
    })
    .finally(resultOrError => {
      this.progress.complete();
    });
  }

}

Now test out your app on a device to capture and image and scan all the texts!

Conclusion

It’s pretty easy to add some OCR functionality to your Ionic app using the Tesseract library. If you want to use a different way, you can also give the Tesseract Cordova plugin a try (haven’t tried it yet).

Also, there’s more you can do with the library like figuring out which language your text is in or specifying a language for the recognition to improve the result.

If you’ve built and app with this functionality, definitely share your app below!

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

The post Building an Ionic OCR App with Tesseract appeared first on Devdactic.

Building an Ionic Spotify App – Part 1: OAuth

$
0
0

After a recent comment under a video of my YouTube channel I was challenged to build an app with Spotify integration – and the process was everything but easy.

Years ago I already wrote about the flow for an Ionic v1 App but many things have changed since then including my understanding of the OAuth login flow. Therefore this 2 part series will help you to build a proper login flow using Spotify OAuth with your own little server and finally Ionic Spotify app!

Also, the previous article was using free 30 second snippets rather than the full power of the Spotify API (due to the used login back then)!

This article is inspired by the great work of cordova-spotify-oauth and their implementation plus we’ll also use the mentioned plugin which needs a little server to work correctly. At the end of this part we’ll have a fully working setup and Spotify access token so we can build the actual functionality of the app against the Spotify API in the second part.

Spotify App Setup

We begin our journey by creating a new Spotify app inside their developer dashboard. Go ahead and create an account there if you don’t already have and then hit the “Create a Client ID” button to create a new app.

Fill out the mandatory fields and save the app. Then navigate into the app and select “Edit Settings” and set add a redirect URI like this “devdacticspotify://callback

This URI is needed when the login flow returns back to your app and will open the app using a custom URL scheme. The scheme I picked is devdacticspotify and we’ll talk more about the custom scheme later when we build the Ionic app, for now just keep in mind that you might want to change that URI to match your company/ apps name a bit more!

So if you pick a different one (what I recommend yo do) make sure you remember it later in all the spots where I use this URI.

Spotify OAuth Server Prerequisite

The OAuth login is not super easy and especially if you want a robust setup including access and refresh tokens you might need your own server. There’s already one server inside the cordova-spotify-oauth repo but it’s for AWS hosting and I also wanted to get a better understanding of what’s happening so I rebuilt it using NodeJS as we can then deploy it to Heroku in seconds.

Therefore, the next step is to go into your Heroku account (create if you don’t have, it’s free) and hit “New -> Create new app“.

Enter the information as you wish and then head over to your command line as we will use the Heroku CLI to connect our local server with the created app.

Go ahead and run these commands in an empty folder of your choice:

# Answer the question with enter to set up a new package.json
npm init

# Install dependencies
npm i body-parser cors crypto-js dotenv express http request

# Start git for Heroku
git init
heroku git:remote -a yourherokuappname

First, go through all the questions of the npm init and simply hit enter. Then, install a bunch of dependencies that we need for our server.

Finally, initialise a Git repository in your folder and add your Heroku app as a remote to that repository. Of course use the name of your Heroku app in here so you can later deploy your app with one command!

To tell Heroku what should be started, created a new file, call it Procfile and simply insert:

web: node index.js

Awesome, now we need some environment variables and the easiest way to add them is using the CLI again, so run these commands but replace it with your values:

heroku config:set CLIENT_CALLBACK_URL=devdacticspotify://callback
heroku config:set CLIENT_ID=your-spotify-app-id
heroku config:set CLIENT_SECRET=your-spotify-app-secret
heroku config:set ENCRYPTION_SECRET=my-secret!

By setting them here they will be secretly available on Heroku to our app as well and we don’t have to define them anywhere else. You can find the Spotify ID and secret inside your previously created app, the callback URI is what you specified and the encryption key can be whatever you want, it’s simply a key to make sure the communication of the refresh token is secure.

This token could be used to obtain new access keys and it’s kinda bad if this token get’s lost in the wild, so do your best to protect it.

Building the OAuth Server

Now we can finally get to the actual coding after a lot of setup. Our NodeJS server has 2 routes:

  • /exchange: Get a new access token after login
  • /refresh: Get a new access token using a refresh token

But before we get to this, open a new file index.js and insert the general server bootstrapping stuff:

var
  cors = require('cors'),
  http = require('http'),
  express = require('express'),
  dotenv = require('dotenv'),
  bodyParser = require('body-parser'),
  request = require('request'),
  CryptoJS = require('crypto-js');

var app = express();
dotenv.load();

const API_URL = "https://accounts.spotify.com/api/token";
const CLIENT_ID = process.env.CLIENT_ID;
const CLIENT_SECRET = process.env.CLIENT_SECRET;
const CLIENT_CALLBACK_URL = process.env.CLIENT_CALLBACK_URL;
const ENCRYPTION_SECRET = process.env.ENCRYPTION_SECRET;

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

We need to load our environment variables and also use the API url for the Spotify token Endpoint as specified in their documentation.

To make a request to the Spotify servers we need to add the headers and parameters in a special format, therefore we add a new function spotifyRequest that our two following routes can use. This function will make an appropriate request and then return the result in a meaningful way to the caller, so add this to your index.js:

const spotifyRequest = params => {
  return new Promise((resolve, reject) => {
      request.post(API_URL, {
        form: params,
        headers: {
          "Authorization": "Basic " + new Buffer(CLIENT_ID + ":" + CLIENT_SECRET).toString('base64')
        },
        json: true
      }, (err, resp) => err ? reject(err) : resolve(resp));
    })
    .then(resp => {
      if (resp.statusCode != 200) {
        return Promise.reject({
          statusCode: resp.statusCode,
          body: resp.body
        });
      }
      return Promise.resolve(resp.body);
    })
    .catch(err => {
      return Promise.reject({
        statusCode: 500,
        body: JSON.stringify({})
      });
    });
};

Now we can also add the request which is called after our app user went through the OAuth login inside the app. This route returns an object with access_token, expires_in and refresh_token if everything went well. Here we’ll also encrypt the refresh token as discussed earlier, the functions for this will follow inside the last snippet.

The only interesting thing in here is the information we pass to our previous spotifyRequest function, especially that we are using the grant type authorization_code which is the best way to interact with the Spotify API later on. The authorization code flow gives us full access to the user and the API but it’s also the trickiest to implement. But we are here to learn, right?

The second route will be called if the user is working with our app again and the access token stored in the app is expired. The plugin (which we use later) will then automatically make a call to the /refresh route to exchange the refresh token for a new access token!

This is the standard OAuth flow, and I guess this server could be build into a nice little OAuth helper package as well for other services if anyone is interested in doing this 😉

Now go ahead and add the new route below your previous code inside the index.js:

// Route to obtain a new Token
app.post('/exchange', (req, res) => {

  const params = req.body;
  if (!params.code) {
    return res.json({
      "error": "Parameter missing"
    });
  }

  spotifyRequest({
      grant_type: "authorization_code",
      redirect_uri: CLIENT_CALLBACK_URL,
      code: params.code
    })
    .then(session => {
      let result = {
        "access_token": session.access_token,
        "expires_in": session.expires_in,
        "refresh_token": encrypt(session.refresh_token)
      };
      return res.send(result);
    })
    .catch(response => {
      return res.json(response);
    });
});

// Get a new access token from a refresh token
app.post('/refresh', (req, res) => {
  const params = req.body;
  if (!params.refresh_token) {
    return res.json({
      "error": "Parameter missing"
    });
  }

  spotifyRequest({
      grant_type: "refresh_token",
      refresh_token: decrypt(params.refresh_token)
    })
    .then(session => {
      return res.send({
          "access_token": session.access_token,
          "expires_in": session.expires_in
      });
    })
    .catch(response => {
      return res.json(response);
    });
});

These are the only 2 routes our app needs for the login. But of course we need some helper functions and make sure to start the server. It’s important to use the PORT from the environment as otherwise your app will result in a crash once you deploy it to Heroku!

Finish your server by adding this to the index.js:

// Helper functions
function encrypt(text) {
  return CryptoJS.AES.encrypt(text, ENCRYPTION_SECRET).toString();
};

function decrypt(text) {
  var bytes = CryptoJS.AES.decrypt(text, ENCRYPTION_SECRET);
  return bytes.toString(CryptoJS.enc.Utf8);
};

// Start the server
var server = http.createServer(app);

server.listen(process.env.PORT || 5000, function (err) {
  console.info('listening in http://localhost:8080');
});

In the beginning you’ve already (hopefully) created the Git repository as described, so now you only need to add all your files, commit them and then push the new code to the Heroku remote which will trigger a build for your app:

git add .
git commit -am 'My Heroku deployment commit message.'
git push heroku master

Done!

Your app is now deployed to Heroku and you can find the URL for the app either in the CLI output or inside your Heroku Dashboard. The login might work locally as well but we need the app on a device anyway so it’s a lot easier to have this server available from anywhere without the stress of setting up a local testing environment.

Also, testing the 2 routes is not working well as we need a code from Spotify that is only returned after a user went through the authorization dialog of the app, so let’s better finish this tutorial with the real Ionic app to make some progress!

The Ionic App Setup

We start like always with a blank Ionic app and add the before mentioned oauth plugin which will take care of showing the Spotify login plus a plugin to set a custom URL scheme for our app.

Remember, you don’t have to use exactly my wording in here but make sure it’s the same you used in the beginning or your login will never return to your app and you’ll spend hours debugging (I already did that for your..):

ionic start devdacticSpotify blank
cd devdacticSpotify
ionic cordova plugin add cordova-spotify-oauth
ionic cordova plugin add cordova-plugin-customurlscheme --variable URL_SCHEME=devdacticspotify

With our new app we now just need to create a little configuration and then call our Cordova plugin, so let’s add the code to the pages/home/home.ts like this before we go into details:

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

declare var cordova: any;

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

  constructor(public navCtrl: NavController) { }

  authWithSpotify() {
    const config = {
      clientId: "your-spotify-client-id",
      redirectUrl: "devdacticspotify://callback",
      scopes: ["streaming", "playlist-read-private", "user-read-email", "user-read-private"],
      tokenExchangeUrl: "https://spotifyoauthserver.herokuapp.com/exchange",
      tokenRefreshUrl: "https://spotifyoauthserver.herokuapp.com/refresh",
    };

    cordova.plugins.spotifyAuth.authorize(config)
      .then(({ accessToken, encryptedRefreshToken, expiresAt }) => {
        this.result = { access_token: accessToken, expires_in: expiresAt, ref: encryptedRefreshToken };
      });
  }
}

To login with Spotify we need the Client ID of our Spotify App we created in the first step. Then we need the callback URL that we’ve added to that app and set inside our server as well.

The scopes variable is an array of elements you can pick from the scopes documentation, this basically defines what resources our app can access later and the user will also see a list of these permissions and hopefully accept what we request.

Finally, the URLs for the tokens are the URLs of your Heroku app so make sure to copy the right values and set them so the Cordova plugin automatically knows which routes to call for the tokens!

To make this app run, simply add a button to the view that calls the authWithSpotify function but I think if you’ve followed this tutorial you’ll know how to do it.

If you want to test the app, make sure to deploy it to your device as it’s not going to work from within your browser! Then you can log out the result after the login and hopefully you’ll app will have an access token at that point!

What’s next

We’ve only built the basics for our Ionic Spotify app but we’re on a pretty good way. Right now we got a full-blown OAuth setup with our own server and token exchange/refresh mechanism in place. In the next part we’ll then use our scopes and tokens to make requests against the Spotify API to build our own Ionic Spotify client!

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

The post Building an Ionic Spotify App – Part 1: OAuth appeared first on Devdactic.

Building an Ionic Spotify App – Part 2: Spotify API

$
0
0

After our initial setup for the Spotify OAuth dialog we are now ready to create a simple Ionic Spotify client that can access the Web API of Spotify to retrieve all kinds of information about the user, playlists or tracks!

If you haven’t went through the first part of this series, make sure to complete it first and then come back to this tutorial.

Go back to the first part: Building an Ionic Spotify App – Part 1: OAuth

Inside this second part we will use the web API JS wrapper that only needs our access token and allows us to contact all the endpoints without having to lookup everything inside the documentation.

Once we are finished our app will display our playlists and also allow some options for the tracks inside those lists like you can see below.

Setting up the Ionic Spotify Client

We finished the first tutorial with a simple login to try our OAuth flow, and we’ll continue with that but you can also simply start your app now if you haven’t. For those coming from part 1 make sure to run the additional commands as well!

ionic start devdacticSpotify blank
cd devdacticSpotify
ionic cordova plugin add cordova-spotify-oauth
ionic cordova plugin add cordova-plugin-customurlscheme --variable URL_SCHEME=devdacticspotify

# Additional commands
ionic cordova plugin add cordova-plugin-media
npm i @ionic-native/media
npm i spotify-web-api-js
ionic g page playlist

Now we got a basic app with all the plugins and pages we need. Make sure you have used your own custom URL scheme that you used within the first part! Also, we now need the Media plugin to play a track preview later. Therefore, make sure to add it to your app/app.module.ts like this:

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 { Media } from '@ionic-native/media';

import { IonicStorageModule } from '@ionic/storage';

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

I’ve also included the Ionic Storage so we can better keep track whether a user was previously logged in and automate parts of the token exchange flow under the hood when the user comes back.

Login with Spotify & User Playlists

The first part of the login was already discussed before, but now we finally make real use of the access token we get back after login. We can directly pass it to an instance of the SpotifyWebApi so ll the future requests can use the token and make a valid request against the API.

We’ll also keep track of the loggedIn state of a user to show a logout button and also try to see if a user was previously logged in from the storage to automatically trigger our authorise flow again. Remember, this won’t open the whole dialog again but simply request a new access token from our server using the refresh token!

At this point I also encountered some problems with CryptoJS and decoding the refresh token, so in case you run into any problems just let me know below. It looks like for some reason the params that are sent to the server are not escaped and the “+” character gets replaced with an empty space sometimes..

Anyway, if everything works fine we can then use the API to retrieve the playlists of a user by calling getUserPlaylists(). You can also checkout all the functions inside the documentation of the library! We then got a nice array of playlists that we can present inside the view, so go ahead and change your pages/home/home.ts to:

import { Component } from '@angular/core';
import { NavController, Platform, LoadingController, Loading } from 'ionic-angular';
import * as SpotifyWebApi from 'spotify-web-api-js';
import { Storage } from '@ionic/storage';

declare var cordova: any;

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {
  result = {};
  data = '';
  playlists = [];
  spotifyApi: any;
  loggedIn = false;
  loading: Loading;

  constructor(public navCtrl: NavController, private storage: Storage, private plt: Platform, private loadingCtrl: LoadingController) {
    this.spotifyApi = new SpotifyWebApi();

    this.plt.ready().then(() => {
      this.storage.get('logged_in').then(res => {
        if (res) {
          this.authWithSpotify(true);
        }
      });
    });
  }

  authWithSpotify(showLoading = false) {
    const config = {
      clientId: "your-spotify-client-id",
      redirectUrl: "devdacticspotify://callback",
      scopes: ["streaming", "playlist-read-private", "user-read-email", "user-read-private"],
      tokenExchangeUrl: "https://spotifyoauthserver.herokuapp.com/exchange",
      tokenRefreshUrl: "https://spotifyoauthserver.herokuapp.com/refresh",
    };

    if (showLoading) {
      this.loading = this.loadingCtrl.create();
      this.loading.present();
    }

    cordova.plugins.spotifyAuth.authorize(config)
      .then(({ accessToken, encryptedRefreshToken, expiresAt }) => {
        if (this.loading) {
          this.loading.dismiss();
        }

        this.result = { access_token: accessToken, expires_in: expiresAt, refresh_token: encryptedRefreshToken };
        this.loggedIn = true;
        this.spotifyApi.setAccessToken(accessToken);
        this.getUserPlaylists();
        this.storage.set('logged_in', true);
      }, err => {
        console.error(err);
        if (this.loading) {
          this.loading.dismiss();
        }
      });
  }

  getUserPlaylists() {
    this.loading = this.loadingCtrl.create({
      content: "Loading Playlists...",
    });
    this.loading.present();

    this.spotifyApi.getUserPlaylists()
      .then(data => {
        if (this.loading) {
          this.loading.dismiss();
        }
        this.playlists = data.items;
      }, err => {
        console.error(err);
        if (this.loading) {
          this.loading.dismiss();
        }
      });
  }

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

  logout() {
    // Should be a promise but isn't
    cordova.plugins.spotifyAuth.forget();

    this.loggedIn = false;
    this.playlists = [];
    this.storage.set('logged_in', false);
  }

}

We’ve also added a logout function now, but the plugin was not working 100% like described so for me it’s not returning a Promise, therefore we just call it and remove all the other stored information.

Finally, we add a function to push a second page that will then display all the tracks of a single playlist!

Now we move on to the view which basically consists of our playlist list (is this correct?) and images that are already contained within the API response. There are far more information so simply log out the JSON to find the values you want to present. For now though simply change your pages/home/home.html to:

<ion-header>
  <ion-navbar>
    <ion-title>
      Ionic Spotify Client
    </ion-title>
    <ion-buttons end>
      <button ion-button clear (click)="authWithSpotify()" *ngIf="!loggedIn">Login</button>
      <button ion-button clear (click)="logout()" *ngIf="loggedIn">Logout</button>
    </ion-buttons>
  </ion-navbar>
</ion-header>

<ion-content>

  <div *ngIf="!loggedIn" text-center padding>Please Login to load your Playlists!</div>

  <ion-list>
    <button ion-item *ngFor="let item of playlists" (click)="openPlaylist(item)">
      <ion-avatar item-start *ngIf="item.images.length > 0">
        <img [src]="item.images[0].url">
      </ion-avatar>
      {{ item.name }}
      <p>Tracks: {{ item.tracks.total }}</p>
    </button>
  </ion-list>
</ion-content>

Now you are able to log in with Spotify and retrieve your own Playlists!

Working with Spotify Tracks

To add some more features we now dive into the tracks of our playlist. Beware: There is currently now way to stream the whole song through the API (*at least from mobile devices)!

The Spotify team is working on this, but right now we can’t build a true Spotify app, but you can do almost everything else from your app.

In our case we will offer 3 options for every track:

  • Play the preview of the track (if it exists)
  • Play the track on our active Spotify device
  • Open the Spotify client and play the track

Especially the second option is kinda crazy and you can see it in action in the video linked at the end of this tutorial!

Inside our page we will use the library again and call getPlaylist() where we also pass in the ID of the owner and the playlist ID itself. The result contains an array of tracks that we can use later inside our view to iterate over all the entries.

As for the functions, the play/stop logic for the preview works with the Media plugin and starts the song in the background. Make sure to enable sound and turn it up on your device if you can’t hear anything 😉

The “Open in Spotify” function is using the URL of the link, which will then automatically open the Spotify app with the track. The mechanism in the background is a universal link and we might talk about this in a future tutorial as well, so raise you hand below if you want more information on that topic.

Finally, we can even play the track on the last device that was using Spotify! I’m not sure what exactly happens in the background but being able to control the Spotify client on my Mac from my Ionic app felt kinda epic. And that’s just one function, you could even control the sound and seek to a position and much more..

To get all of this, simply change your pages/playlist/playlist.ts to:

import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams, LoadingController, Loading } from 'ionic-angular';
import * as SpotifyWebApi from 'spotify-web-api-js';
import { Media, MediaObject } from '@ionic-native/media';

@IonicPage()
@Component({
  selector: 'page-playlist',
  templateUrl: 'playlist.html',
})
export class PlaylistPage {
  tracks = [];
  playlistInfo = null;
  playing = false;
  spotifyApi: any;
  currentTrack: MediaObject = null;
  loading: Loading;

  constructor(public navCtrl: NavController, public navParams: NavParams, private media: Media, private loadingCtrl: LoadingController) {
    let playlist = this.navParams.get('playlist');
    this.spotifyApi = new SpotifyWebApi();

    this.loadPlaylistData(playlist);
  }

  loadPlaylistData(playlist) {
    this.loading = this.loadingCtrl.create({
      content: "Loading Tracks...",
    });
    this.loading.present();

    this.spotifyApi.getPlaylist(playlist.owner.id, playlist.id).then(data => {
      this.playlistInfo = data;
      this.tracks = data.tracks.items;
      if (this.loading) {
        this.loading.dismiss();
      }
    });
  }

  play(item) {
    this.playing = true;

    this.currentTrack = this.media.create(item);

    this.currentTrack.onSuccess.subscribe(() => {
      this.playing = false;
    });
    this.currentTrack.onError.subscribe(error => {
      this.playing = false;
    });

    this.currentTrack.play();
  }

  playActiveDevice(item) {
    this.spotifyApi.play({ uris: [item.track.uri] });
  }

  stop() {
    if (this.currentTrack) {
      this.currentTrack.stop();
      this.playing = false;
    }
  }

  open(item) {
    window.open(item.track.external_urls.spotify, '_system', 'location=yes');
  }

}

Alright we are close to finishing this, now we only need the view for our tracks which is again a simple iteration over the tracks we received from the API.

Additionally we add some controls below each card in order to play the preview, play it on our active device or open the track inside Spotify. No real magic in here, so simply change your pages/playlist/playlist.html to:

<ion-header>
  <ion-navbar>
    <ion-title *ngIf="playlistInfo">{{ playlistInfo.name }}</ion-title>
  </ion-navbar>
</ion-header>

<ion-content>
  <ion-list>

    <ion-card *ngFor="let item of tracks">
      <ion-item>
        <ion-avatar item-start>
          <img [src]="item.track.album.images[0].url">
        </ion-avatar>
        <h2>{{ item.track.name }}</h2>
        <p>{{ item.track.artists[0].name}}</p>
      </ion-item>

      <ion-row>
        <ion-col *ngIf="item.track.preview_url && !playing">
          <button ion-button icon-left (click)="play(item.track.preview_url)" clear small>
            <ion-icon name="play"></ion-icon>
            Preview
          </button>
        </ion-col>
        <ion-col *ngIf="item.track.preview_url && playing">
          <button ion-button icon-left (click)="stop()" clear small>
            <ion-icon name="close"></ion-icon>
            Stop
          </button>
        </ion-col>
        <ion-col>
          <button ion-button icon-left (click)="playActiveDevice(item)" clear small>
            <ion-icon name="musical-note"></ion-icon>
            Device
          </button>
        </ion-col>
        <ion-col>
          <button ion-button icon-left (click)="open(item)" clear small>
            <ion-icon name="open"></ion-icon>
            Spotify
          </button>
        </ion-col>

      </ion-row>
    </ion-card>
  </ion-list>
</ion-content>

And that’s it!

Make sure to run your app on a device and make sure that your OAuth server is reachable on Heroku. Then you should be able to use the full power of the Spotify API within your Ionic app!

Conclusion

The process to connect safely to the Spotify API is not trivial but it’s possible and once your are authenticated you can do almost all actions like searching, editing playlists and almost everything besides streaming the tracks.

We might see the new SDK over the next time so perhaps we’ll then be even able to build a full Ionic Spotify client app, until then enjoy what the API offers and build great Ionic apps with it (and of course show them)!

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

The post Building an Ionic Spotify App – Part 2: Spotify API appeared first on Devdactic.


10 Simple Ionic Hacks

$
0
0

Even years after starting with Ionic we can learn something new that can safe us time or just make it easier for us to work with Ionic. From time to time I stumble upon a quick fix to an issue I lived with for a long time, therefore I wanted to document those Ionic hacks in one easy to remember place.

And of course this list is open for more tricks, so if you have learned and awesome trick in the last time feel free to leave a comment below and we’ll make some additions.

1. Hide Scrollbar Chrome

The live preview is great, especially the ionic lab that gives you an almost accurate preview of your app on different platforms. One thing that is really annoying when developing with the lab is the visible scrollbar I always get on Chrome.

Although this bar is not visible in the final version, it changes the available space for then rest of your UI and if you want to present something or make screenshots for your clients, this bar just looks odd!

The good thing is we can easily disable this bar with 3 lines of css added to our app/app.scss:

* ::-webkit-scrollbar {
  display: none;
}

Be aware that this will hide all the scrollbars inside your application!

If you’ve never encountered this problem just go next.

2. Making Text Selectable

You might have noticed that you can’t select the text inside your iOS app, which is especially challenging if you want to deploy it as a website / PWA. People just need to copy some texts, but again a short CSS statement can change everything for you:

body {
    -webkit-user-select: text;
    user-select: text;
}

Now your users can happily copy all the text areas inside their app without any further additions or plugins!

3. Debugging faster on iOS & Android

While we love to develop in the browser, some features just need to be tested on a real device. And if you don’t know how to get the app to your device or debug it, you gonna have a hard time.

First of all, besides running ionic cordova build ios/android you could also run ionic cordova build ios/android -lc which is directly deploying the app to your connected device plus automatically reloading whenever you change something! It’s like the live reload but happening on your real device.

However, you also need to debug your app properly and this is where remote debugging is the key. For both iOS and Android you can connect from your computer to the device using Safari/Chrome.

Once you are attached with your browser, you get all the logs of your app, you can inspect all the files and especially also debug HTML elements of your DOM!

If you know how to run and debug your Ionic app properly on a device you can save a lot of time in your development cycle.

4. Adding iOS Permissions to Plist Automatically

If you are using the camera or photo library on iOS you have to ask your users for permissions, otherwise your app will crash at that point or at least your app will be rejected once you submit it to iTunes Connect.

Adding those entries manually to the plist can’t be the solution, and of course it isn’t because you can add these lines to your config.xml:

<edit-config file="*-Info.plist" mode="merge" target="NSCameraUsageDescription">
    <string>need camera access to take pictures</string>
</edit-config>
<edit-config file="*-Info.plist" mode="merge" target="NSPhotoLibraryUsageDescription">
    <string>need photo library access to get pictures from there</string>
</edit-config>

This will automatically create the needed entries whenever you build your Ionic app for iOS, just make sure to add reasonable information in here. One of my apps was actually once rejected because I didn’t explain enough why the camera was needed..

5. Targeting Platforms and Operating Systems

Although we build for all the platforms and everywhere the web runs, sometimes we need to distinguish between platforms, devices and operating systems.

Good that Ionic already included a function to test where exactly your app is running called is(), and we can use it for different checks like this:

constructor(public plt: Platform) {
  if (this.plt.is('ios')) {
    // We are on iOS
  }

  if (this.plt.is('cordova')) {
    // We are installed through the appstore
  }

  if (this.plt.is('iphone')) {
    // Running on an iPhone
  }
}

Also, if you are building a PWA you can easily find out if you are running in standalone mode with a simple test:

constructor() {
    const isInStandaloneMode = () => ('standalone' in window.navigator) && (window.navigator['standalone']);

    if (isInStandaloneMode()) {
      // App is accessed as a PWA
    }
}

You can also find a course with more PWA information inside the Ionic Academy!

6. Using VS Code Plugins

For new Ionic developers I always recommend to get Visual Studio Code as it’s currently one of the best free editors out there to develop Ionic / Angular apps (and a lot more of course)

What’s awesome about it is the market place with a wide range of free extensions you can install, simply search for Ionic and you’ll already find a bunch of great ones.

With those extensions, the times are over for looking up the list, header or card syntax!

If you haven’t seen me use those snippets it’s because I was too lazy to get used to them. But they could definitely save some time.

7. Improving Loading Times of your Ionic App

Performance of Ionic apps is often a big topic, and you are first of all responsible for writing good code that performs. It’s not the fault of Ionic (most of the time) if your app loads slowly, but you can easily improve your performance.

First, make sure to use lazy loading of your pages. By doing this, your app won’t load all the pages on startup immediately but rather prolong the loading a bit so the first page will appear a lot faster.

Also, you shouldn’t have the big logic and loading operations inside your constructor as this will also prolong the creation of your pages when they are initialised.

Finally, what can make a huge change for all Ionic apps is building for production. Simply run:

ionic cordova build ios --prod --release

This build will take longer, but your app will start a lot faster and you should always create your final deployments like this to run all the custom app scripts that will improve your underlying code.

8. Making always use of Theming & Variables

You can write custom CSS in every page, you can target your special ids and classes but the easiest way of approaching the styling of your app is to keep it at the top most level, using SASS variables and the predefined color scheme (which you of course can change).

Also, you don’t have to reinvent the wheel as Ionic is already shipping with some modifications that you can easily apply to your typography elements
.

Instead of coming up with custom classes, try to use the Ionic variables and override them to fit your needs. By doing this you will have it a lot easier later on when you want to change the appearance again as most of the customization will be in one file, especially if you make strong use of your SASS variables inside the child SASS files!

9. Configure your Ionic App Config

While we can change the general appearance through the SASS files we can also modify the general behaviour of components through the root configuration of our app.

The Ionic Config allows you to change stuff like the position of your tab bar, animations of modals, icons used and event the text of your back button.

While you can set these values from code, you can also directly apply them to elements like:

<ion-tabs tabsPlacement="top">
  <ion-tab tabTitle="Home" tabIcon="home" [root]="tabRoot"></ion-tab>
</ion-tabs>

Of course all of those values can be written to from your code later as well.

10. Using Custom Scripts

Finally, as we already got a nice package.json, why not add our own scripts in there instead of writing deployment scripts inside a shell file? There are already a bunch of run scripts at the top of your file out of the box, but you can of course create your own tasks as well!

In one of my projects I added these scripts for building & deploying a PWA to Firebase:

"dist": "rm -rf www/* && npm run build --prod && node ./cache-busting.js && workbox injectManifest",
"deploy": "npm run dist && firebase deploy"

Whenever I want to upload a new version, I simply run:

npm run deploy

That’s easy to remember and performs all the hard underlying work of invoking different scripts and tasks!

Also, the package.json can be used to use some custom copy scripts that override the Ionic behaviour e.g. if you want to copy some CSS from an installed node module (like in this post). You can implement your own behaviour and simply specify your scripts like this:

"config": {
    "ionic_webpack": "./config/webpack.config.js",
    "ionic_sass": "./config/sass.config.js",
    "ionic_copy": "./config/copy.config.js"
  }

Don’t rely on executing your custom scripts or tasks by hand and make it easy for your teammates to run and deploy your app by creating meaningful tasks that everyone can use!

Conclusion

There are thousands of little snippets and hacks that can speed up your Ionic development flow, and if you have great additions definitely share them below!

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

The post 10 Simple Ionic Hacks appeared first on Devdactic.

Creating Ionic Datatable With ngx-datatable

$
0
0

If you are working with a lot of data you might have encountered the problem of presenting this data in an Excel like way inside your Ionic app. Although the datatable pattern is not always the most recommended for mobile apps, especially in times of PWAs having the ability to create a table within your Ionic app is a great alternative.

Inside this tutorial we will use the ngx-datatable package which was made for Angular apps, so we can perfectly use this inside our Ionic app as well!

But as you can see from the gif above, using this pattern on small devices is problematic but let’s talk about the good things for now and see how to add it to our app.

Adding ngx-datatable to your Ionic Project

Like always we start with a blank Ionic app and install the datatable packe to our app, so go ahead and run:

ionic start dataTable blank
cd dataTable
npm i @swimlane/ngx-datatable
ionic g page table

We’ve also created a new page which comes with a module file, because the initial page doesn’t. Therefore, go ahead and remove all references to the HomePage from your module file and then set a new entry point for our app inside the app/app.component.ts:

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 = 'TablePage';

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

The ngx-datatable comes with some predefined styling we can use, but first we need to make sure the styling and an additional font is copied over to our build folder. Therefore, add a new block to your package.json

"config": {
  "ionic_sass": "./config/sass.config.js",
  "ionic_copy": "./config/copy.config.js"
}

We've changed the copy times already in many other articles, and the only change we really need is to copy over the fonts in an additional task. Create the folder and new file at config/copy.config.js and insert everything from the regular Ionic copy script plus a block which copies over the fonts:

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}}'
    },
    copyNgxFont: {
        src: ['{{ROOT}}/node_modules/@swimlane/ngx-datatable/release/assets/fonts/data-table.ttf',
        '{{ROOT}}/node_modules/@swimlane/ngx-datatable/release/assets/fonts/data-table.woff'],
        dest: '{{BUILD}}/fonts'
    }
}

For the second special file, create another new file at config/sass.config.js. For this file, copy the contents of the original file (which is pretty long) from /node_modules/@ionic/app-scripts/config/sass.config.js.

Then, search for the includePaths block and add one line to it at the end like this:

includePaths: [
  'node_modules/ionic-angular/themes',
  'node_modules/ionicons/dist/scss',
  'node_modules/ionic-angular/fonts',
  'node_modules/@swimlane/ngx-datatable/release'
],

Now both the font and styling of the package is copied to our build folder and we can use it easily! To make the styling available to our app we also need to import both available styles, so change your app/app.scss to:

@import 'themes/bootstrap';
@import 'themes/dark';
@import 'assets/icons';

That’s all for the integration, now to the actual usage!

Presenting a Basic Table

We start with a super basic example where we’ll display some information in the most easy way possible.

To use this package inside your pages you first need to add it to the imports of the module, in our case we need to change the pages/table/table.module.ts to this:

import { NgModule } from '@angular/core';
import { IonicPageModule } from 'ionic-angular';
import { TablePage } from './table';
import { NgxDatatableModule } from '@swimlane/ngx-datatable';

@NgModule({
  declarations: [
    TablePage,
  ],
  imports: [
    IonicPageModule.forChild(TablePage),
    NgxDatatableModule
  ],
})
export class TablePageModule {}

Next we need some data which I’ve copied from one of their examples.

Additionally I’ve already incorporated a little switch so you can change the theme of your datatable from bootstrap to dark so you can see both of them in action!

There’s not much to it right now, so change your pages/table/table.ts to:

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

@IonicPage()
@Component({
  selector: 'page-table',
  templateUrl: 'table.html',
})
export class TablePage {
  // https://github.com/swimlane/ngx-datatable/blob/master/assets/data/company.json
  rows = [
    {
      "name": "Ethel Price",
      "gender": "female",
      "age": 22
    },
    {
      "name": "Claudine Neal",
      "gender": "female",
      "age": 55
    },
    {
      "name": "Beryl Rice",
      "gender": "female",
      "age": 67
    },
    {
      "name": "Simon Grimm",
      "gender": "male",
      "age": 28
    }
  ];

  tablestyle = 'bootstrap';

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

  switchStyle() {
    if (this.tablestyle == 'dark') {
      this.tablestyle = 'bootstrap';
    } else {
      this.tablestyle = 'dark';
    }
  }

}

We wanted to build the most basic version of a table first, so here it is! We only add one extra button to change our table style, and inside the view we craft the table.

This table needs rows which are the datasource and even the additional attributes I’ve adde might be obsolete but are needed to present the table in an appealing way on a mobile device.

Inside the table you then define columns for your values, in our case three of them which will look up the value of their name. The name doesn’t have to be the key inside the array, but more on this later.

The table will now iterate over your data and create the according rows and columns, so for now change the pages/table/table.html to:

<ion-header>
  <ion-navbar color="dark">
    <ion-title>ngx-Table</ion-title>
    <ion-buttons end>
      <button ion-button icon-only (click)="switchStyle()">
        <ion-icon name="bulb"></ion-icon>
      </button>
    </ion-buttons>
  </ion-navbar>
</ion-header>
<ion-content>

  <ngx-datatable class="fullscreen" [ngClass]="tablestyle" [rows]="rows" [columnMode]="'force'" [sortType]="'multi'" [reorderable]="false">
    <ngx-datatable-column name="Name"></ngx-datatable-column>
    <ngx-datatable-column name="Gender"></ngx-datatable-column>
    <ngx-datatable-column name="Age"></ngx-datatable-column>
  </ngx-datatable>
</ion-content>

The result of this simple is what you can see below.

Kinda nice for just a few lines of HTML that create everything from a given datasource, right?

Advanced Ionic Datatable

Let’s build a more sophisticated example with some reordering mechanism, additional row buttons and special styling.

For this, we start by adding a few functions to our class. To determine a special class that get’s added to a row we define the getRowClass() function that checks if the dataset of the row is a male or female person. In both cases we return the name of a CSS class that we will define later.

When we open a row, we simply present an alert with basic information gathered from the row to show how everyhting works.

Finally we also have some summary functions that can be used to display a summary row inside your table. Most of the time the default summary for your column won’t work so with these functions you can write your own logic for creating the sum or average or whatever you want to display!

In our case we present the sum of male/female persons as well as the average age of our persons.

Go ahead and add the new functions to your pages/table/table.ts like this:

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

@IonicPage()
@Component({
  selector: 'page-table',
  templateUrl: 'table.html',
})
export class TablePage {
  // https://github.com/swimlane/ngx-datatable/blob/master/assets/data/company.json
  rows = [
    {
      "name": "Ethel Price",
      "gender": "female",
      "age": 22
    },
    {
      "name": "Claudine Neal",
      "gender": "female",
      "age": 55
    },
    {
      "name": "Beryl Rice",
      "gender": "female",
      "age": 67
    },
    {
      "name": "Simon Grimm",
      "gender": "male",
      "age": 28
    }
  ];

  tablestyle = 'bootstrap';

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

  switchStyle() {
    if (this.tablestyle == 'dark') {
      this.tablestyle = 'bootstrap';
    } else {
      this.tablestyle = 'dark';
    }
  }

  getRowClass(row) {
    return row.gender == 'male' ? 'male-row' : 'female-row';
  }

  open(row) {
    let alert = this.alertCtrl.create({
      title: 'Row',
      message: `${row.name} is ${row.age} years old!`,
      buttons: ['OK']
    });
    alert.present();
  }

  //
  // Summary Functions
  //
  genderSummary(values) {
    let male = values.filter(val => val == 'male').length;
    let female = values.filter(val => val == 'female').length;

    return `${male} / ${female}`;
  }

  ageSummary(values) {
    return values.reduce((a, b) => a+b, 0) / values.length;
  }

}

Now we also need to add some changes to the view in order to make use of the new functionality we’ve implemented. First of all we add the CSS that is needed to customize rows based on the gender, so change your pages/table/table.scss to:

page-table {
    .male-row {
        background-color: green !important;
    }
    .female-row {
        background-color: red !important;
    }
}

The last missing piece now is the view which needs to be changed for our custom columns. We’ve previously seen basic columns, but we can also have more control about the header, the used value for the column or which summary function should be used.

There are quite a few options you can set for both the table and columns, so for all the information check out the official documentation which also contains examples for almost everything the package offers!

Let’s change the code and talk about a few elements afterwards, so open your pages/table/table.html and change it to:

<ion-header>
  <ion-navbar color="dark">
    <ion-title>ngx-Table</ion-title>
    <ion-buttons end>
      <button ion-button icon-only (click)="switchStyle()">
        <ion-icon name="bulb"></ion-icon>
      </button>
    </ion-buttons>
  </ion-navbar>
</ion-header>
<ion-content>

  <ngx-datatable class="fullscreen" [ngClass]="tablestyle" [rows]="rows" [columnMode]="'force'" [sortType]="'multi'" [reorderable]="false"
    [rowHeight]="50" [rowClass]="getRowClass" [summaryRow]="true" [summaryPosition]="'top'">

    <ngx-datatable-column prop="name" [resizeable]="false">
      <ng-template let-column="column" ngx-datatable-header-template let-sort="sortFn">
        <span (click)="sort()">Special User</span>
      </ng-template>
      <ng-template let-value="value" ngx-datatable-cell-template>
        {{ value }}
      </ng-template>
    </ngx-datatable-column>

    <ngx-datatable-column name="Gender" [summaryFunc]="genderSummary.bind(this)"></ngx-datatable-column>
    
    <ngx-datatable-column name="Age" [summaryFunc]="ageSummary.bind(this)"></ngx-datatable-column>

    <ngx-datatable-column name="id" [resizeable]="false">
      <ng-template ngx-datatable-header-template>
        Action
      </ng-template>
      <ng-template let-row="row" ngx-datatable-cell-template>
        <button ion-button small outline color="light" (click)="open(row)">Details</button>
      </ng-template>
    </ngx-datatable-column>

  </ngx-datatable>
</ion-content>

Here is a bit more information about what we’ve used and what it does:

  • let-sort=”sortFn”: Use the default alpha numeric ordering mechanism for this column
  • prop: Use the specified key inside the datasource to lookup the value for the column
  • summaryFunc: Use a special summary function for the whole column
  • .bind(this): Not needed here but makes this available inside the function called!
  • let-value=”value”: Makes the value of the column available as value
  • let-row=”row”: Make the whole row object available inside the column

As said before, there’s really a lot you can customize and some elements won’t work that well together. In my testing, the now applied summary function actually breaks the reordering mechanism, but I’m sure there is some fix for that issue as well.

Conclusion

Overall you should be careful when you use a table inside a mobile app, but if you decide you need one the ngx-datatable is an awesome package that offers a lot of options to customize how your data is presented!

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

The post Creating Ionic Datatable With ngx-datatable appeared first on Devdactic.

How I Built a MEAN Prototype in 24 Hours

$
0
0

Normally we talk about specific Ionic or Angular code examples, but today I want to share some Experiences and steps you can take to build and deploy prototype versions of your ideas in less than 24 hours.

Do you know these times when you find a simply idea, you fall in love with it and just have to build something and show it to the world?

It doesn’t really matter if it’s going to be a huge project or just a helpful tool, you know you need to finish this as fast as possible before your head can work on anything else again.

That happened to me a few weeks ago and I was surprised how fast you can build and deploy a full app with API these days, even completely for free! As a spoiler, the result is the Ionic Snippet library (more on this at the end).

Because you might not yet be sure how everything works together or how you could develop your own real prototype as fast as possible I’ll share my experiences of those 24 hours with you and which stages I went through.

1. Idea

It started with a simple idea: What about a snippet library for Ionic?

Places like these already exists for other frameworks and languages. People around the world upload or submit their snippets and others can browse the archive to find helpful shortcuts. Upload, storing, presenting – basic CRUD operations that shouldn’t take too long!

The idea was small enough for a fast test but at the same time has the potential to become a bigger problem if it finds traction one day. I was fired up and knew that it was about time to get my hands dirty.

2. Framework Selection

Because I usually blog about Ionic I’m familiar with Angular. However, I’ve not used pure Angular and the CLI since many versions but I felt this was the right time to re-connect with the base of where I was coming from.

Also, this time there was not really the need for a mobile app, this project should simply be a website for now and therefore the decision for the frontend was Angular 6.

At the same time the idea needed a backend. A place to store the snippets and give the user results back for searches. Ever since the MEAN stack got more popularity I started my APIs with NodeJs and a MongoDB, and that was again my choice this time.

Although the project contains all elements of the MEAN stack, I always separate the Angular frontend from my Node server. I don’t like having everything inside one project, the server for me is 99% of the time a REST API so I got all the flexibility later to built whatever frontend I enjoy or works best.

3. Critical Function Test

The framework selection was done, not the actual coding started. Before I make huge plans for a project I think about the critical elements of the app.

What might be a pain in the *ss down the road?

For this project it was both the upload and presentation of snippets. Ionic snippets only work with a bunch of files, and although you could only show some HTML or JS I felt that this won’t do the trick for users. If you browse the library, you also want to see how this looks in action as fast as possible.

Because of that, I decided to integrate Stackblitz which actually happens to offer a great Javascript SDK! Stackblitz also offers the chance to directly start a new Ionic project with one click inside their editor which is perfect to showcase new ideas or, exactly, snippets.

Therefore my critical test was to load a Stackblitz project like this by it’s URL and present it in an appropriate way inside an Angular application. To show you some code, here’s how the integration looks like:

All you need is the ID of the project and optionally some options how you want to embed the Stackblitz editor within your page. For me, this combination worked out best and afterwards just call embedProjectId to load the editor into an element inside your view:

import sdk from '@stackblitz/sdk';

let options: any = {
  'height': 500,
  'hideDevTools': true,
  'hideNavigation': '1',
  'forceEmbedLayout': '1'
};

sdk.embedProjectId('vm', 'ionic-5d6z3e', options).then(vm => {
  // Hooray!
});

Yo just need a div with the same ID inside your view like this:

<div id="vm"></div>

That was the critical function test. And although the result only contains a few lines, getting there took some more time.

But at that point I knew that showing a list, a form and all the other things wouldn’t cause too many problems, so I went ahead.

4. Most Basic Version Feedback

When you are building something new, early feedback is the best way to validate if you should continue with your endeavour. Although this was not a super ambitious project I wanted to know if Ionic developers would be actually interested in the idea.

Therefore, I created the most basic version possible that could work inside a GIF and asked the holy Twitter community.

The feedback was overwhelming and I thought this will be super helpful for many developers out there.

Not sure if I would have changed my course if nobody had reacted, because the idea still needed to get out of my brain.. Anyway, because I continued this article doesn’t end here but goes a bit more into details now.

5. Building the Angular 6 App

Angular comes with it’s awesome CLI that just like Ionic bootstraps your projects, creates components at the right places and even does a bit more. That might also be the reason why we’ll se a closer integration with this once Ionic v4 is out!

Stil, the project structure is a bit different, and especially the routing works completely different compared to Ionic 3.x which means: You start as a beginner again.

The project would have been fast if I took the time to properly understand the Angular routing concepts but of course I jumped right into it and expected everything to work my way. After failed attempts I finally found this awesome article on Angular + Bootstrap which is now the base for my navigation concept and general page structure.

As an alternative you could also use Angular Material instead of Bootstrap but that’s more or less a personal choice what you prefer. I just wanted basic styling and no additional components so I picked Bootstrap.

When a user submits a form he expects a response, and here I added the great nxg-toastr component which works right out of the box without much configuration and does the job perfectly.

The code itself is not really spectacular, the Stackblitz SDK usage might be the most interesting part actually. If you still have questions about the structure, navigation or any other page just ask inside the comments below!

Once all basic frontend features were ready for the MVP I even added Google Analytics with Angular to get and idea how many people visited my page.

All of this worked so flawless and was overall just fun to code!

6. Building the NodeJS API

The last step and this step were actually carried out simultaneously. I’m not really a backend developer so I just build what I need for the frontend side. This sometimes takes a few extra iterations that you could save if you had a strict plan in the first place but you know.. things change, you get new ideas, it just happens!

The code of the API is just as interesting as the frontend. It’s not even more than 100 lines combined in real files, but it’s still powerful to simply run your own server with database where you can potentially control everything that’s happening!

When working with Node and MongoDB I’d always recommend to use Mongoose to model your database objects and to easily access the collections later. Besides that there are a few other dependencies so if you want to create your own little API together, here are my packages:

"dependencies": {
    "body-parser": "^1.6.5",
    "compression": "^1.0.11",
    "cors": "^2.8.4",
    "dotenv": "^0.4.0",
    "errorhandler": "^1.1.1",
    "express": "^4.16.3",
    "helmet": "^3.9.0",
    "mongoose": "^5.2.0",
    "mongoose-paginate": "^5.0.3",
    "mongoose-timestamp": "^0.6.0",
    "morgan": "^1.2.3"
  }

The only special thing I’ve used here is the Mongoose Pagination plugin which allows to easily paginate through your results. If you think you could have a lot of data one day, start now by adding pagination for both the API and the frontend.

If both your backend and frontend are ready for the world, it’s time to get out!

7. Domain, Hosting & Go Live

Maybe some of you fear this step or don’t know how to do it, how much it cost but here’s the truth:
It is easy, super fast and in the beginning free!

There’s really no excuse to stop or not start a project because you fear this step. This is a bit different if you want to get into the iOS and Android Appstore, but for the web it’s true.

Let’s see how to deploy both of our parts.

API

Our backend is a NodeJS application that needs a MongoDB. There are some services out there that allow you to easily host your app, but the fastest I found (and used many times already) is Heroku.

With the Heroku CLI integration you can directly connect your App with Heroku and your GIT (which I highly recommend to use inside your project every time for versioning!). Once you’ve created an app insie your Heroku dashboard and added the remote to your project, you just need to add a Procfile to the root of your project like this:

web: node server.js

Heroku will read this file and start the server when you push your code to Heroku like this:

git push heroku master

That’s all to deploy or redeploy your app – Heroku will handle the rest.

Now you still need that database – and there comes the Heroku Addons into play. With a few clicks you can directly add a free instance of mLab to your Heroku app!

When you add these resources, the URI to your database is stored inside the Heroku environment variables when the app is started. Therefore, you need to prepare your app to always take the right URI to the database wherever you start/deploy the API, so in my case a config part could look like this:

module.exports = {
    db: process.env.MONGOLAB_URI || 'mongodb://localhost:27017/ionicsnippets'
};

In the beginning you can use free dynos which will automatically shut down after some time and restart once they are called, but you can also keep them up running for just a few bucks.

Website

This part of the deployment is actually even faster. If you don’t have any webspace or hosting available, Firebase Hosting will do the trick for you.

To start with this hosting, simply create a new Firebase project inside your account, install the Firebase CLI and login to your account from the CLI.

Once this is done, you can call firebase init inside your Angular project and run through the wizard. Select hosting and your project and Firebase will create a .firebaserc and firebase.json.

During that wizard make sure to select the right options like this:

$ What do you want to use as your public directory? www/yourprojectname
$ Configure as a single-page app (rewrite all urls to /index.html)? Yes
§ File www/index.html already exists. Overwrite? No

Finally, it’s time to build the Angular app for production and upload it to Firebase, therefore you can run:

ng build --prod
firebase deploy

Those two commands will do the magic and Firebase will give you the hosting URL as a result. This URL is not pretty so and therefore you might consider getting your own domain.

Domain

I bought the domain actually before the project started. I kinda like purchasing domains and thinking about the project I could use them for…

Anyway, this step is optional and there are infinite hosting or domain providers out there. You should be able to purchase them wherever you want, I used a German provider so that won’t be really helpful for you.

But what you definitely need to do is go to your Firebase -> Hosting dashboard and select “Connect domain“. Inside this wizard enter your domain name and you will get a TXT record like this:

Now you need to open your DNS settings somewhere inside the account where you purchased the domain and insert a new TXT record with the value that Firebase gave you.

Once this step is finished and verified by Firebase you can finally add A records to your DNS settings that point directly to the IP of the Firebase hosting instance. Those settings should be on the same page as your TXT settings, it’s just some stuff you need to handle once but then you can access your project even under the good looking domain!

8. Build it and they will…

So what about “build it and they will come” ? That quote is definitely outdated.

Although the response on my initial idea was amazing, until now I’m the only person adding snippets on Ionic Snippets.

Even if you think your project is helpful, people won’t use it from day one. It’s an early version, perhaps it lacks many features and besides you loving your little baby project it’s not yet ready for the big world.

At this point, don’t be sad.

Even if no-one will ever use your project, you have still learned a bunch along the way so let’s recap:

  • Building a modern Angular webapp
  • Creating your own API with Database
  • Running a server on Heroku
  • Uploading an Angular project to Firebase Hosting
  • using a custom domain

All of this will be useful and your next MVP will likely just take half the time and have twice as many features. Don’t underestimate the learnings you get from going trough this process on your own. You need to separate the result from the process.

PS: All of this took me actually less then 24 hours, it was just the full time between idea and go live. The actual work on the project was less time because I had to sleep.

If you need any guidance along your way or have more questions about certain steps, feel free to ask below!

The post How I Built a MEAN Prototype in 24 Hours appeared first on Devdactic.

Building an Ionic Spotify App – Part 3: Native Spotify Integration

$
0
0

Over the last months we had a series on using Spotify with Ionic and to conclude this series one more post follows that how to truly use the full power of Spotify by combining all previous elements.

Make sure that you have followed along the previous tutorials as this one is based on the second part.

Building an Ionic Spotify App – Part 1: OAuth
Building an Ionic Spotify App – Part 2: App with Web API

Today we will use the Cordova Spotify plugin to play a complete song from Spotify and not only a short preview. We will use the finished code from the last part, add the plugin and change a few parts so let’s go!

Getting Started

First of all you need to add the plugin to your current project:

ionic cordova plugin add cordova-spotify

If you encounter problems during the installation, you might need to remove your platform, check the dependencies that are added to your package.json and see if you can find the issue. After a few attempts it finally worked for me although I didn’t really change anything.

The app should already work from the previous examples which means you can log in , retrieve the playlists of a user and also display the tracks of a playlist.

Storing the Access Token

In order to play our tracks in full length we need to pass the clientId and accessToken to the plugin once we call play(), therefore we need a way to pass this information to the next page.

Normally you would of course have a service that stores the information so your plugin can easily retrieve the dat, but for now we will just keep track of that data and pass it to the next playlist page. In your project you need to change the pages/home/home.ts to:

import { Component } from '@angular/core';
import { NavController, Platform, LoadingController, Loading } from 'ionic-angular';
import SpotifyWebApi from 'spotify-web-api-js';
import { Storage } from '@ionic/storage';

declare var cordova: any;

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {
  result = {};
  spotifyApi = new SpotifyWebApi();
  loading: Loading;
  loggedIn = false;
  playlists = [];

  clientId = 'your-spotify-app-id';
  accessToken = null;

  constructor(public navCtrl: NavController, private storage: Storage, private plt: Platform, private loadingCtrl: LoadingController) {
    this.plt.ready().then(() => {
      this.storage.get('logged_in').then(res => {
        if (res) {
          this.authWithSpotify(true);
        }
      })
    });
  }

  authWithSpotify(showLoading = false) {
    const config = {
      clientId: this.clientId,
      redirectUrl: "devdacticspotify://callback",
      scopes: ["streaming", "playlist-read-private", "user-read-email", "user-read-private"],
      tokenExchangeUrl: "https://spotifyoauthserver.herokuapp.com/exchange",
      tokenRefreshUrl: "https://spotifyoauthserver.herokuapp.com/refresh",
    };

    if (showLoading) {
      this.loading = this.loadingCtrl.create();
      this.loading.present();
    }

    cordova.plugins.spotifyAuth.authorize(config)
    .then(({accessToken, encryptedRefreshToken, expiresAt }) => {
      if (this.loading) {
        this.loading.dismiss();
      }
      this.accessToken = accessToken;
      this.spotifyApi.setAccessToken(accessToken);
      this.loggedIn = true;
      this.storage.set('logged_in', true);
      this.getUserPlaylists();
    }, err => {
      if (this.loading) {
        this.loading.dismiss();
      }
    });
  }

  getUserPlaylists() {
    this.loading = this.loadingCtrl.create({
      content: 'Loading Playlists...'
    });
    this.loading.present();

    this.spotifyApi.getUserPlaylists()
    .then(data => {
      if (this.loading) {
        this.loading.dismiss();
      }
      this.playlists = data.items;
    }, err => {
      if (this.loading) {
        this.loading.dismiss();
      }
    });
  }

  openPlaylist(item) {
    this.navCtrl.push('PlaylistPage', { playlist: item, access: this.accessToken, clientId: this.clientId });
  }

  logout() {
    cordova.plugins.spotifyAuth.forget();

    this.loggedIn = false;
    this.playlists = [];
    this.storage.set('logged_in', false);
  }

}

It’s still mostly the same code and I’ve marked the changed lines for you.

Changing Our Playlist Logic

Now our page get’s the information we need, and we can simply call the Cordova plugin directly to access the functions. You can see an overview about the functions and return values here.

There are not so many features included, but we can play, pause, resume or skip to a position inside a track and we gonna add all of that. We also still have the logic from the Web API to retrieve the tracks, so we now combine both approaches inside our app!

We also keep track of the current playing state to show the right buttons at the right time. In comparison to the last part we also removed the Ionic Native Media plugin as we are now using the native audio through the plugin.

The changes are marked again so open your pages/playlist/playlist.ts to:

import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams, LoadingController, Loading } from 'ionic-angular';
import SpotifyWebApi from 'spotify-web-api-js';

declare var cordova: any;

@IonicPage()
@Component({
  selector: 'page-playlist',
  templateUrl: 'playlist.html',
})
export class PlaylistPage {
  spotifyApi: any;
  loading: Loading;
  playlistInfo = null;
  tracks = [];

  playing = false
  paused = false;

  clientId = null;
  accessToken = null;

  constructor(public navCtrl: NavController, public navParams: NavParams,
    private loadingCtrl: LoadingController) {

    this.clientId = this.navParams.get('clientId');
    this.accessToken = this.navParams.get('access');

    let playlist = this.navParams.get('playlist');
    this.spotifyApi = new SpotifyWebApi();
    this.loadPlaylistData(playlist);
  }

  loadPlaylistData(playlist) {
    this.loading = this.loadingCtrl.create({
      content: 'Loading Tracks...'
    });
    this.loading.present();

    this.spotifyApi.getPlaylist(playlist.owner.id, playlist.id).then(data => {
      this.playlistInfo = data;
      this.tracks = data.tracks.items;
      if (this.loading) {
        this.loading.dismiss();
      }
    });
  }

  play(item) {
    cordova.plugins.spotify.play(item.track.uri, {
      clientId: this.clientId,
      token: this.accessToken
    })
      .then(() => {
        this.playing = true;
        this.paused = false;
      });
  }

  pause() {
    cordova.plugins.spotify.pause()
      .then(() => {
        this.playing = false;
        this.paused = true;
      });
  }

  resume()  {
    cordova.plugins.spotify.resume()
      .then(() => {
        this.playing = true;
        this.paused = false;
      });
  }

  seekTo() {
    cordova.plugins.spotify.seekTo(1000 * 120)
      .then(() => console.log(`Skipped forward`))
  }
}

The last missing piece is to change the logic of our view a bit and use all the functions we have added. Besides that, nothing really changed as we are still relying on the information from the Web API so go and finish it by changing your pages/playlist/playlist.html:

<ion-header>
  <ion-navbar>
    <ion-title *ngIf="playlistInfo">{{ playlistInfo.name }}</ion-title>
  </ion-navbar>
</ion-header>

<ion-content>

  <ion-list>
    <ion-card *ngFor="let item of tracks">
      <ion-item>
        <ion-avatar item-start>
          <img [src]="item.track.album.images[0].url">
        </ion-avatar>
        <h2>{{ item.track.name }}</h2>
        <p>{{ item.track.artists[0].name}}</p>
      </ion-item>

      <ion-row>
        <ion-col>
          <button ion-button icon-left (click)="play(item)" clear small>
            <ion-icon name="play"></ion-icon>
            Play
          </button>
        </ion-col>
        <ion-col *ngIf="playing && !paused">
          <button ion-button icon-left (click)="pause()" clear small>
            <ion-icon name="close"></ion-icon>
            Pause
          </button>
        </ion-col>
        <ion-col *ngIf="paused">
          <button ion-button icon-left (click)="resume()" clear small>
            <ion-icon name="open"></ion-icon>
            Resume
          </button>
        </ion-col>
        <ion-col>
          <button ion-button icon-left (click)="seekTo()" clear small>
            <ion-icon name="open"></ion-icon>
            Seek To
          </button>
        </ion-col>
      </ion-row>

    </ion-card>
  </ion-list>

</ion-content>

That’s it and now you are playing native Spotify music inside your Ionic app!

Conclusion

We already had a good app after the first 2 parts but now we can actually use everything from Spotify. However, I’m not 100% in how far the plugin is still maintained and what happens if new iOS or Android versions break the plugin.

Also, you might have noticed that we are using it as a pure Cordova plugin, so if you want to see it inside Ionic Native you might have to create a pull request for it!

The post Building an Ionic Spotify App – Part 3: Native Spotify Integration appeared first on Devdactic.

Is Ionic the Right Choice for My Project?

$
0
0

Ionic has been around for quite some years and with the latest release of version 4 it becomes an even better option for developing hybrid apps than it was already. Still, there are drawbacks and scenarios where Ionic might not be (or should not be) your first choice.

In this article we’ll take a look at questions you should answer before picking Ionic for your next project. In the end, the result might very likely still be Ionic but there are other great alternatives as well.

Software Development is no competition and we certainly don’t need a winner here. Accept that there are many great frameworks in this world, each having its unique strengths and tradeoffs. You can embrace a great framework for your job or spend time explaining why framework x is so bad. The decision is up to you, so choose wisely how you really want to spend your projects time.


Software Development is no competition and we certainly don’t need a winner here.
Click To Tweet


Be aware that the term Hybrid Apps can sometimes still be a red flag for decision makers, so if they are not convinced after uncovering the 10 Hybrid Myths this article will hopefully give them a clearer path.

If you’ve already fallen in love with Ionic or want to learn more about it, you can take the next step with my exclusive Ionic learning platform the Ionic Academy which offers courses, projects and an awesome community to help you become an Ionic Developer.

What’s your Project?

In the beginning, there’s an idea or task that will become a project later. At this stage, you need to find out what platforms you want to support and what the biggest priorities of your project are. If you develop something completely new it’s of course easier to pick something new then when you already have requirements based on a legacy system.

First of all, where do you want to offer the app? Inside the native app stores, on the web, as a PWA or even as a desktop application?

Your answer will be a part or combination of these, and especially if you are targeting many platforms Ionic is interesting for your.

When you only need a web application, create for example a pure Angular project. If you only need desktop apps, maybe use Electron but perhaps use something completely different (this is not my main area of focus, really).

But once you see that you need to target mobile and web, Ionic offers a great way to keep your code in code base with one language. At this point it doesn’t really matter if you need a native app or PWA because you can get both with Ionic.

The result of using a cross-platform framework is potentially higher when you really use it as a cross-platform tool. This always means it’s not going to be 100% like doing it with the inherent approach of the respective platform, but you can get close to it to a certain degree while your cost decrease.

Which brings us to the priorities of the project. If you priority is to have the best 3D visuals an app has ever seen, stop reading here. That’s not a use case for Ionic but something like Unity.

If, on the other hand, your priority is to offer your customers a solution on all of their preferred platforms, Ionic can help you to get there more quickly. Because you can build your one code base into many different forms in the end, you are more flexible and able to target a variety of platforms.

In the end, every project has a budget and timeline. This means, it’s a business decision based on many variables you can sometimes only estimate very rough upfront. Do your job, outline what’s important and where you want to be. This will give you a first indication whether to use Ionic or not.

What are your teams Skills?

When you have a team of 5 non developers that are just starting to learn what an Array is, there’s no big difference between picking framework a or b. If on the other hand you have a team of 5 Angular developers, the likelihood of picking Ionic suddenly raise a lot.

Although you should (of course) always try to use the best tool to deliver the best possible result for your project, it doesn’t mean that this selection makes the most sense or has the highest business value.

If you already got experienced developers with a lot of C# knowledge, why not try something like Xamarin?

If all of your team knows React, why not use React Native?

ionic-alternatives

As said in the beginning, there are many great frameworks available each with their own unique approach. And if you can benefit from the experience of your developers, you will get started a lot faster and perhaps also get a better result with something they would have to learn from ground zero.

When you have a handful of web developers with basic Javascript skills, Ionic is the perfect framework to transition into mobile apps.

Also, in case you have a development team of 100 native developers, chances are high you should just go native because it seems like your business focus is developing the best possible app for your end-user.

But when your team is not at that stage (yet?) evaluate your previous skills. Keep in mind the target platforms of your project from the first step because not all of the mentioned framework can deliver the same result across those platforms like Ionic does.

Combine those 2 elements to see what framework could be a match!

Benefits You Get with Ionic

Once you decided that Ionic might be the right fit, it’s time to look at the potential upsides of using it.

Codebase

As already mentioned, your result is one codebase which you can use to build for multiple platforms. That not only means the initial development time is faster, but also further maintenance and updates are easier to ships as it only requires changes in one project.

While this sounds awesome, it’s never going to be 50% (or more) of the development time compared to creating x projects for each separate platform you target. Sometimes you just need to take care of special behaviours so you need to add clauses like:

if (this.plt.is('ios')) {
  // do ios Stuff
}

if (this.plt.is('android')) {
 // do android stuff
}

Ionic is already doing there best to simplify this process by a project called Capacitor that defines one API that will work both on the web and with native SDKs. At the time writing this it has not yet reached a stable version but looks very promising for the future.

Platforms

Because Ionic is betting strongly on the web, Ionic apps can run almost anywhere today. An Ionic 4 project is basically a web application that gets packaged into the right container for a specific platform.

On the web, this means it can be deployed directly as it is. As a PWA, you just need to comment in a snippet and it’s ready. For the native app stores (iOS/Android), Cordova will package your application and make the underlying SDKs and device features available. And for desktop, you can use Electron which is already used by applications like Visual Studio or Slack.

UI Elements

If you want to explain Ionic very (very, very) simplified, it’s a great UI library of elements. Especially with the version 4 update Ionic moves towards a direction where it can be easily added to any project as its components are now basically web components created with their own tool Stencil.

While Bootstrap was and still is great for the web, having platform specific components is almost a must have today if you want your users to enjoy your app.

Ever seen an Android designed iOS App?

It either never get’s through the Apple Submission guidelines check or feels just wrong when used.

Therefore, Ionic automatically uses the styling based on the platform where the app is running. While they are already looking good out of the box most of the time, everything can be customised to meet your expectations. It’s not like you have to life with predefined colors or anything, there are just standard values to help you get started faster.

Tooling & Development Flow

If your development environment sucks, your productivity decreases. If you have the right tools and feel comfortable with your flow, your productivity will likewise be a lot higher.

With Ionic 4 you get the power of the Ionic CLI of previous releases, plus you can also use the Angular CLI without any problems right inside your project!

This means, bootstrapping projects, adding new files, creating the right structure becomes a lot easier.

Also, compared to native development, the live reload of your app is something those developers can only dream of.

Combined with the additional safety of TypeScript and a great editor like Visual Studio Code, developing Ionic apps becomes less a job and more pure fun.

Support & Community

While you expect great support and a friendly community from all famous frameworks, it’s not always going to be like you imagine it. Because the main focus of Ionic is still Angular, you can not only get help from fellow Ionites but also benefit from the whole Angular community.

And once more users of other frameworks start to use Ionic, the size of the community will increase more and more over time.

If you are looking for another great Ionic community, you can find a friendly place inside my Ionic Academy.

Also, looking up something inside a documentation can be quite painful but Ionic makes delivering an awesome experience in this area a high priority. Just check out there beautiful redesigned Ionic 4 docs to see what I mean. It’s definitely more than you can expect from an open source framework!

Ionic Pro

Although Ionic is completely open source and free, you can add another set of tools by using Ionic Pro. This paid service adds additional functionality like visual App creation with the creator, improved deployment process (live app updates), error monitoring or testing channels.

While none of these is a must have, it’s an amazing suite of tools that you might want to have if you can afford the price.

For enterprise teams the benefits will clearly pay off and also for smaller companies that take their apps serious it’s something they should thing about to increase the productivity of their development team.

Areas of Improvement within Ionic

Until now I’ve blown the trumpet quite hard for Ionic. This is because I’ve fallen in love with it over and over again in the last years. But of course there are drawbacks in every framework, and Ionic is no exception.

Performance

The most and biggest concern of all time against cross-platform apps will always be the performance. Yes, Ionic apps run inside a Webview. They are no real native elements, and therefore they will always be inside this container and at least one level above real native apps.

On the web in general, that’s not really an issue as Ionic apps are a website like everything else.

As a native application, this can become a painpoint especially if a bridge to a native functionality is the bottleneck that keeps hanging and slows down your app. Again, if your top priority is the best performance ever seen by a mobile application you might want to go full native.

Also, the usage of Javascript is super easy but at the same time there are many areas where unexperienced developers can go wrong which results in slow apps. Josh Morony has a great article on why not the framework but your code might be the real problem to the performance.

You can definitely build super performant apps with Ionic, but it’s easier to mess up with performance than it is with a framework that’s closer to native code (or completely native).

Reload

The cool reload was a benefit just some paragraphs ago and now it’s a drawback?

To understand it, you need to learn the difference between live reload and hot reload.

The first is used with Ionic and means your app is updated once you save your code. The second means your app is updated once you save the code in the exactly same state it was before.

That’s a difference, especially if you are testing your app on a mobile device or simulator where the loading times can really slow down your process.

Of course this is not a showstopper, but something developers would highly benefit from in the future.

Native SDKs

If one of your platforms is a native app, odds are high that you want to use device features or the underlying SDK. While this is not a problem itself through Cordova, it can be painful sometimes.

There are Cordova plugins for almost everything you can imagine, but once you need a very unique feature and discover that the only plugin for this was updated 3 years ago you know you gonna have a hard time.

This doesn’t mean it’s the end of the road, but at this point you might have to touch native code that you feel not comfortable with at all. Also, the connection between our Javascript and the native code through Cordova means we can’t simply access native functionality directly.

With other frameworks like NativeScript it’s a lot easier to work directly with the native SDKs, but for Ionic you currently still need a bridge between.

Maybe this painpoint will be removed or decreased once Capacitor is ready for prime time, until then you must live with what you get or become active by developing your own plugin.

And what about the AirBnB and Facebook News?

Coming back to the here and now, you might have heard the news of AirBnB sunsetting their work with React Native or the older news from the Zuck that betting on HTML5 was the biggest mistake of Facebook (while they have certainly done bigger mistakes recently).

Especially in higher positions articles like these create a lot of doubt against the general cross-platform approach.

Is developing cross-platform still valuable?

The short answer is: Yes, more than ever.

To understand how those companies came to these decisions you would have to truly understand their business and their situation. AirBnB has more than 100 native developers.
How many does your company have?

If I had a team of 100 experienced natives devs I guess the last thing I would do is tell them to stop all native work and go for Ionic now.

This means, you shouldn’t be scared by big headlines of big companies.

The decision for or against Ionic or any framework is based on many factors, and just because one company stops using a tool it doesn’t mean the tool sucks. It was just not the right fit for their needs.

Ionic could be right for your next project or not. Don’t let that decision be based on big headlines but your own evaluations.


Ionic could be right for your next project or not. It simply depends on many factors.
Click To Tweet


Conclusion

In this article we went through many steps to see whether Ionic might be the right fit for your next project or cross-platform app. Especially with Ionic 4 and the closer Angular integration it’s a great choice for any web and mobile project, but it always depends on your business case, priorities and factors your value!

Just don’t fall back into preconceptions you have built and fostered over the years.

What’s your currently preferred framework and what are you building with it? I’d love to hear what you are using today and why!

The post Is Ionic the Right Choice for My Project? appeared first on Devdactic.

Building a Basic Ionic 4 Login Flow with Angular Router

$
0
0

It’s time for Ionic v4 content, and what fit better than the all-time classic login flow example? With Ionic 4 it becomes a lot easier to protect our apps the real Angular way using the Angular Router.

Inside this (quite long) tutorial we will build a dummy authentication flow logic for an Ionic 4 app using Angular. We are not going to use a real backend or users, but you can easily plug in the calls to your API in the right places and use this logic as a blueprint for your authentication.

At the time writing this I’m using the v4 Beta so things might change a bit over time. If you encounter problems, just leave a comment below this tutorial.

We will use the CLI, Angular Router, Guards, Services.. Let’s do this!

Creating the Ionic Basic App

We start with a blank Ionic app and generate a few pages and services at the appropriate places inside our app. Also, I’m using the Angular CLI to generate an additional module inside the members folder (which should be created after running the commands).

Perhaps this will also work with the Ionic CLI soon, but as the Angular CLI is closely integrated in Ionic 4 projects there’s no problem in using it so go ahead and bootstrap your project like this:

ionic start devdacticLogin blank --type=angular
cd devdacticLogin
npm install --save @ionic/storage
ionic g page public/login
ionic g page public/register
ionic g page members/dashboard
ionic g service services/authentication
ionic g service services/authGuard
ng generate module members/member-routing --flat

The --flat simple means to create no additional folder for the file!

You can now also go ahead and remove the app/home folder and we will also remove the other references to the HomePage later.

We also want to use the Ionic Storage inside our app which you would later use to store your JWT so we need to add it to our app/app.module.ts like this:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router';

import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';

import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
import { IonicStorageModule } from '@ionic/storage';

@NgModule({
  declarations: [AppComponent],
  entryComponents: [],
  imports: [
    BrowserModule, 
    IonicModule.forRoot(), 
    AppRoutingModule,
    IonicStorageModule.forRoot()
  ],
  providers: [
    StatusBar,
    SplashScreen,
    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

That’s the basic setup so far, now we can focus on adding some security.

Adding Authentication Provider and Guard

Like always it’s a good idea to have the authentication logic in one place, and here it is our AuthenticationService. This service will handle the login/logout and in our case perform just dummy operations.

Normally you would send the credentials to your server and then get something like a token back. In our case we skip that part and directly store a token inside the Ionic Storage. Then we use our BehaviorSubject to tell everyone that the user is now authenticated.

Other pages can subscribe to this authenticationState or check if the user is authenticated using the current value of the Subject.

Although we don’t have any real Server attached, the logic and flow is nearly the same so you could use this as a base for your own authentication class.

Now go ahead and change the app/services/authentication.service.ts to:

import { Platform } from '@ionic/angular';
import { Injectable } from '@angular/core';
import { Storage } from '@ionic/storage';
import { BehaviorSubject } from 'rxjs';

const TOKEN_KEY = 'auth-token';

@Injectable({
  providedIn: 'root'
})
export class AuthenticationService {

  authenticationState = new BehaviorSubject(false);

  constructor(private storage: Storage, private plt: Platform) { 
    this.plt.ready().then(() => {
      this.checkToken();
    });
  }

  checkToken() {
    this.storage.get(TOKEN_KEY).then(res => {
      if (res) {
        this.authenticationState.next(true);
      }
    })
  }

  login() {
    return this.storage.set(TOKEN_KEY, 'Bearer 1234567').then(() => {
      this.authenticationState.next(true);
    });
  }

  logout() {
    return this.storage.remove(TOKEN_KEY).then(() => {
      this.authenticationState.next(false);
    });
  }

  isAuthenticated() {
    return this.authenticationState.value;
  }

}

Also, we have added a check to the constructor so we look for a stored token once the app starts. By doing this, we can automatically change the authentication state if the user was previously logged in. In a real scenario you could add an expired check here.

To protect our pages we will later use the Angular Router and a check to see if a user is allowed to access a route. With Angular we use the Auth Guards and therefore create our own Guard where we check our service for the current state of the user.

The service implements only this one function in which we use the previous service so go ahead and change the app/services/auth-guard.service.ts to:

import { Injectable } from '@angular/core';
import { CanActivate } from '@angular/router';
import { AuthenticationService } from './authentication.service';

@Injectable({
  providedIn: 'root'
})
export class AuthGuardService implements CanActivate {

  constructor(public auth: AuthenticationService) {}

  canActivate(): boolean {
    return this.auth.isAuthenticated();
  }
}

Using these guards is the easiest way to protect pages or URLs of your app so users can’t access them without the proper conditions!

App Routing Logic

With Ionic 4 we can now use the standard Angular routing and therefore we create a routing configuration for our app now. The top routing allows to navigate to the register and login page without any checks, but behind the members path every pages will go through the canActivate check so they can only be access once a user is authenticated!

You could also add this checks to every single route, but having all of the routes you want to protect within one child routing module helps to save some code and structure your app better.

First of all change the already created app/app-routing.module.ts to:

import { AuthGuardService } from './services/auth-guard.service';
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

const routes: Routes = [
  { path: '', redirectTo: 'login', pathMatch: 'full' },
  { path: 'login', loadChildren: './public/login/login.module#LoginPageModule' },
  { path: 'register', loadChildren: './public/register/register.module#RegisterPageModule' },
  { 
    path: 'members', 
    canActivate: [AuthGuardService],
    loadChildren: './members/member-routing.module#MemberRoutingModule'
  },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

All the pages use lazy loading therefore we are using the files plus a hash and the name of the module.

For the last route with the auth guard we are also not loading the real module but another routing, the routing for the members area. We’ve created this additional file with the Angular CLI and inside we only need to reference the Dashboard, so go ahead and change the app/members/member-routing.module.ts to:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

const routes: Routes = [
  { path: 'dashboard', loadChildren: './dashboard/dashboard.module#DashboardPageModule' }
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class MemberRoutingModule { }

Notice that this is a child routing and therefore the routes are added to the router with forChild()!

Finally, we can influence our routing logic at the top of the app like you might have seen in v3 in most Firebase tutorials.

We simply subscribe to our own authentication state and if a user becomes authenticated, we automatically switch to the inside area or otherwise for a logged out user back to the login.

To change the according pages we use the Angular Router and navigate to the appropriate pages. Add the code to your app/app.component.ts like this:

import { Router } from '@angular/router';
import { AuthenticationService } from './services/authentication.service';
import { Component } from '@angular/core';

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

@Component({
  selector: 'app-root',
  templateUrl: 'app.component.html'
})
export class AppComponent {
  constructor(
    private platform: Platform,
    private splashScreen: SplashScreen,
    private statusBar: StatusBar,
    private authenticationService: AuthenticationService,
    private router: Router
  ) {
    this.initializeApp();
  }

  initializeApp() {
    this.platform.ready().then(() => {
      this.statusBar.styleDefault();
      this.splashScreen.hide();

      this.authenticationService.authenticationState.subscribe(state => {
        if (state) {
          this.router.navigate(['members', 'dashboard']);
        } else {
          this.router.navigate(['login']);
        }
      });

    });
  }
}

Now the app will automatically change pages on login or logout, and we don’t need to navigate from the single pages as all logic is already available at this place.

Public App Pages

We have all logic in place but we still need a few buttons and functions to navigate around our Ionic app. First of all we start with the login and two buttons.

The first will call a function while the second will directly navigate to the register page using a href. Yes, there are many ways to navigate around!

You can put the code right to your app/public/login/login.page.html:

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

<ion-content padding>
  <ion-button (click)="login()" expand="block">Login</ion-button>
  <ion-button expand="block" color="secondary" href="/register" routerDirection="forward">Register</ion-button>
</ion-content>

The login is no magic so add our service and call the function once the user hits login. Again, normally you would have the input fields and pass the value to your service but we don’t need that logic right now. Therefore, just add the little changes to your app/public/login/login.page.ts:

import { AuthenticationService } from './../../services/authentication.service';
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-login',
  templateUrl: './login.page.html',
  styleUrls: ['./login.page.scss'],
})
export class LoginPage implements OnInit {

  constructor(private authService: AuthenticationService) { }

  ngOnInit() {
  }

  login() {
    this.authService.login();
  }

}

The register page is more or less pushed like with v3 but we don’t get the back button automatically so we need to add it to our page. Also, you can specify a defaultHref so whenever someone directly visits the register page the back button would appear and allow navigating back to the login!

To get the button, simple add it like this to your app/public/register/register.page.html:

<ion-header>
  <ion-toolbar>
    <ion-buttons slot="start">
        <ion-back-button defaultHref="/login"></ion-back-button>
    </ion-buttons>
    <ion-title>Register</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content padding>

</ion-content>

That’s all for the public pages, let’s finish with the inside area!

Private Member App Pages

Just like we did with the login before we can now add a button to the dashboard to log a user out again and there’s not much to do right now.

Open your app/members/dashboard/dashboard.page.html and add the button like this:

<ion-header>
  <ion-toolbar>
    <ion-buttons slot="start">
      <ion-button (click)="logout()">
        <ion-icon slot="icon-only" name="log-out"></ion-icon>
      </ion-button>
    </ion-buttons>
    <ion-title>My Dashboard</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content padding>

</ion-content>

Finally, again, just one more line of code to call our service and the rest of the logic will be handled through the Subject in the background, therefore finish this tutorial by changing your app/members/dashboard/dashboard.page.ts:

import { AuthenticationService } from './../../services/authentication.service';
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-dashboard',
  templateUrl: './dashboard.page.html',
  styleUrls: ['./dashboard.page.scss'],
})
export class DashboardPage implements OnInit {

  constructor(private authService: AuthenticationService) { }

  ngOnInit() {
  }

  logout() {
    this.authService.logout();
  }
}

And that’s how you build a simple login flow with Ionic 4 and the Angular router!

Conclusion

With the closer integration of Angular inside Ionic 4 we can now use the full power of the CLI or great features like Guards, routing and more things we’ll talk about soon.

This basic Ionic login example is of course not a replacement or real authentication example, so if you want to see it in action with a JWT server and real tokens just let me know below!

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

The post Building a Basic Ionic 4 Login Flow with Angular Router appeared first on Devdactic.

How to Create a Simple Ionic 4 App with Firebase and AngularFire

$
0
0

The Ionic Firebase combination remains one of the most used in terms of a cloud backend, and today we will use the latest version of Ionic with the AngularFire RC11 to build a simple todo application!

Although the todo list example is a bit boring, this guide is especially interesting if you are just starting with Ionic 4 because it also contains some basic routing information but also the usage of the (new) Firestore database of Firebase.

ionic-4-firebase-angularfire

Once you are finished with this tutorial you will have your basic Ionic Firebase app to create, read, update and delete data inside your Firebase database!

Setup the Ionic 4 Firebase App

For now we start with a blank Ionic 4 app and at the time writing this I’m using the beta so we have to append the type of our project to get the version 4 app. Also, we install the needed packages for Firebase and add another page and service, so go ahead and run:

ionic start devdacticFire blank --type=angular
cd devdacticFire
npm install firebase angularfire2
ionic g page pages/todoDetails
ionic g service services/todo

Now you need to make sure you have created a Firebase app so either use an existing project or create a new one inside the console.

To add the connection to your app, go to the dashboard of your Firebase app and hit “Add Firebase to your web app” which will bring up your configuration object. With Ionic 4 we can now simply add this to our app/environments/environment.ts like this:

export const environment = {
  production: false,
  firebase: {
    apiKey: '<your-key>',
    authDomain: '<your-project-authdomain>',
    databaseURL: '<your-database-URL>',
    projectId: '<your-project-id>',
    storageBucket: '<your-storage-bucket>',
    messagingSenderId: '<your-messaging-sender-id>'
  }
};

Finally we need to load the environment configuration and also setup our module to use the AngularFire package, so go ahead and change your app/app.module.ts to:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule, RouteReuseStrategy, Routes } from '@angular/router';

import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';

import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';

import { AngularFireModule } from 'angularfire2';
import { environment } from '../environments/environment';
import { AngularFirestoreModule } from 'angularfire2/firestore';

@NgModule({
  declarations: [AppComponent],
  entryComponents: [],
  imports: [
    BrowserModule, IonicModule.forRoot(), AppRoutingModule,
    AngularFireModule.initializeApp(environment.firebase),
    AngularFirestoreModule,
  ],
  providers: [
    StatusBar,
    SplashScreen,
    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

As we are using the routing system of Angular inside our app now we need to create the routing configuration to navigate around. We will start on the home page and add 2 routes to the same details page, but we can either navigate their without a parameter or with an additional id.

To do so, change your app/app-routing.module.ts to include all the routes:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

const routes: Routes = [
  { path: '', redirectTo: 'home', pathMatch: 'full' },
  { path: 'home', loadChildren: './home/home.module#HomePageModule' },
  { path: 'details/:id', loadChildren: './pages/todo-details/todo-details.module#TodoDetailsPageModule' },
  { path: 'details', loadChildren: './pages/todo-details/todo-details.module#TodoDetailsPageModule' },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

To finish the setup head back to your Firebase app and from the menu navigate to Database. In here you can now select the Database you wish to use (if it’s a new project) and we will use the new Firestore database which is basically a NoSQL database.

ionic-4-firebase-firestore-create

Also make sure to select the test mode rules for testing so we can easily read and write all objects. Of course you should have reasonable rules in place for a productive app!

Creating the Firebase Service

Now that our app is connected to Firebase we should create a service to interact with our database.

This service will take care of our create, read update and delete functions (CRUD). All of the interaction will happen on our todosCollection which will load the data from the ‘todos’ path inside our database.

But to display the data in realtime and have all the information present when we need it we need to call the snapshotChanges() function. Also, we need to map those elements because normally they wouldn’t contain the ID of the document, and this ID is what you need most of them to update or delete documents.

The other functions are then only calling the operations on our collection or a single doc() inside the database, so nothing spectacular.

Therefore go ahead and change your app/services/todo.service.ts to:

import { Injectable } from '@angular/core';
import { AngularFirestore, AngularFirestoreCollection } from 'angularfire2/firestore';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

export interface Todo {
  task: string;
  priority: number;
  createdAt: number;
}

@Injectable({
  providedIn: 'root'
})
export class TodoService {
  private todosCollection: AngularFirestoreCollection<Todo>;

  private todos: Observable<Todo[]>;

  constructor(db: AngularFirestore) {
    this.todosCollection = db.collection<Todo>('todos');

    this.todos = this.todosCollection.snapshotChanges().pipe(
      map(actions => {
        return actions.map(a => {
          const data = a.payload.doc.data();
          const id = a.payload.doc.id;
          return { id, ...data };
        });
      })
    );
  }

  getTodos() {
    return this.todos;
  }

  getTodo(id) {
    return this.todosCollection.doc<Todo>(id).valueChanges();
  }

  updateTodo(todo: Todo, id: string) {
    return this.todosCollection.doc(id).update(todo);
  }

  addTodo(todo: Todo) {
    return this.todosCollection.add(todo);
  }

  removeTodo(id) {
    return this.todosCollection.doc(id).delete();
  }
}

If you have a service like this in place it’s later really easy to use all the functions while the connection to the database is in one single class!

Loading the Firebase Collection

Now we just need to create our views and classes to use all the great functions of our service. The first is our home page where we display a list of todos.

The class actually only needs to load the data from the service, and initially I used the Observable directly but the result was that data was duplicate or strange loading results happened. Therefore, we can also subscribe to the Observable and update our local todos array which works fine.

For now, change your app/home/home.page.ts to:

import { Component, OnInit } from '@angular/core';
import { Todo, TodoService } from '../services/todo.service';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage implements OnInit {

  todos: Todo[];

  constructor(private todoService: TodoService) { }

  ngOnInit() {
    this.todoService.getTodos().subscribe(res => {
      this.todos = res;
    });
  }

  remove(item) {
    this.todoService.removeTodo(item.id);
  }
}

Now we got all the data and just need to iterate the todos inside our view. Therefore we create an ngFor and sliding items so we can both click them to update the details or also mark them as finished.

Here we use the routerLink to construct the URL of the next page, and if you go back to the routing you see that we can open the details with an id parameter, so that route will be called!

I’ve also added the ion-skeleton-text element which is a cool way of indicating that content is loading like you might have seen on Facebook or YouTube!

Finally at the bottom right of the page we add a ion-fab which is floating above the content. Again, this button is not calling a function to push a new page but simply navigates to the details path of our app so we don’t need any additional logic!

Quite nice this routing new, hu?

Now you can go ahead and replace the code inside your app/home/home.page.html with:

<ion-header>
  <ion-toolbar color="primary">
    <ion-title>
      Ionic FireTodos
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>

  <ion-list>

    <ng-container *ngIf="!todos || todos.length == 0">
      <div *ngFor="let n of [0,1,2]" padding>
        <ion-skeleton-text></ion-skeleton-text>
        <p>
          <ion-skeleton-text class="fake-skeleton"></ion-skeleton-text>
        </p>
      </div>
    </ng-container>

    <ion-item-sliding *ngFor="let item of todos">
      <ion-item lines="inset" button [routerLink]="['/details', item.id]">
        <ion-label>
          {{ item.task }}
          <p>{{ item.createdAt | date:'short' }}</p>
        </ion-label>
        <ion-note slot="end" color="primary">{{ item.priority }}</ion-note>
      </ion-item>

      <ion-item-options side="end">
        <ion-item-option (click)="remove(item)" color="secondary">
          Check
          <ion-icon name="checkmark" slot="end"></ion-icon>
        </ion-item-option>
      </ion-item-options>
    </ion-item-sliding>

  </ion-list>

  <ion-fab vertical="bottom" horizontal="end" slot="fixed">
    <ion-fab-button routerLink="/details" routerDirection="forward">
      <ion-icon name="add"></ion-icon>
    </ion-fab-button>
  </ion-fab>

</ion-content>

If you want the skeleton to look a bit more fancy you can give it different widths for example by adding this to your app/home/home.page.scss:

.fake-skeleton {
    width: 60%;
}

Now the home view of our Ionic Firebase Todolist is working but we need the second view so we can actually create todos!

Creating & Updating Firestore Documents

Our details view takes care of creating new todos and also updating existing todos. This means, we need to find out if we navigated to the page with the id of a document or without any information.

To do so, we use the Angular Router and the ActivatedRoute, especially the snapshot data of the current route. If we then got the id from the params, we call our loadTodo() function which will load only one specific document of our collection (through our TodoService).

You might have noticed that we also use our Todo interface along the code examples which adds nice typings to our objects!

Once we want to save our todo we need to check if we are updating an existing todo or add a todo – but in both cases we can use the appropriate function of our service. We also show a loading while performing our operations and because we use async/await for the promises, we need to mark the functions as async as well.

Now go ahead and change your app/pages/todo-details/todo-details.page.ts to:

import { Todo, TodoService } from './../../services/todo.service';
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { NavController, LoadingController } from '@ionic/angular';

@Component({
  selector: 'app-todo-details',
  templateUrl: './todo-details.page.html',
  styleUrls: ['./todo-details.page.scss'],
})
export class TodoDetailsPage implements OnInit {

  todo: Todo = {
    task: 'test',
    createdAt: new Date().getTime(),
    priority: 2
  };

  todoId = null;

  constructor(private route: ActivatedRoute, private nav: NavController, private todoService: TodoService, private loadingController: LoadingController) { }

  ngOnInit() {
    this.todoId = this.route.snapshot.params['id'];
    if (this.todoId)  {
      this.loadTodo();
    }
  }

  async loadTodo() {
    const loading = await this.loadingController.create({
      content: 'Loading Todo..'
    });
    await loading.present();

    this.todoService.getTodo(this.todoId).subscribe(res => {
      loading.dismiss();
      this.todo = res;
    });
  }

  async saveTodo() {

    const loading = await this.loadingController.create({
      content: 'Saving Todo..'
    });
    await loading.present();

    if (this.todoId) {
      this.todoService.updateTodo(this.todo, this.todoId).then(() => {
        loading.dismiss();
        this.nav.goBack('home');
      });
    } else {
      this.todoService.addTodo(this.todo).then(() => {
        loading.dismiss();
        this.nav.goBack('home');
      });
    }
  }

}

Finally the last missing piece which might actually be the most boring of all snippets. We just need to input elements and connect them using ngModel. The result looks like this inside your app/pages/todo-details/todo-details.page.html

<ion-header>
  <ion-toolbar color="primary">
    <ion-buttons slot="start">
      <ion-back-button defaultHref="/home"></ion-back-button>
    </ion-buttons>
    <ion-title>Details</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content padding>
  <ion-list lines="full">
    <ion-item>
      <ion-input required type="text" placeholder="Task" [(ngModel)]="todo.task"></ion-input>
    </ion-item>
    <ion-item>
      <ion-input required type="number" placeholder="Priority" [(ngModel)]="todo.priority"></ion-input>
    </ion-item>
  </ion-list>
  <ion-button expand="full" (click)="saveTodo()">Save</ion-button>
</ion-content>

Nothing special, but now your Ionic Firebase app is finished and you have all the basic operations in place to work with the Firestore database!

Conclusion

The Ionic 4 routing might be new but the connection to Firebase with AngularFire works just as good as always. Especially the environment that’s now available makes it easy to add different configurations in case you have multiple systems/projects for test/staging/prod.

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

The post How to Create a Simple Ionic 4 App with Firebase and AngularFire appeared first on Devdactic.


The Ionic 4 Images Guide (Capture, Store & Upload with POST)

$
0
0

If your app is working with images and you need to handle both local files and upload files things can get a bit tricky with Ionic. Especially as debugging the filesystem and paths is cumbersome, it’s not automatically clear how everything needs to work.

This Ionic 4 images guide aims to clarify how you can:

  • Capture images using camera or photo library
  • Store image files inside your Ionic 4 app
  • Upload files from their local path using a POST request

The result will be a simple app like you can see below.

ionic-4-image-upload

For this tutorial I used the Ionic 4 beta so perhaps some details might slightly change over the next time!

Starting our Ionic 4 Image Upload App

To get started we create a blank new Ionic 4 app and install all the plugins we need. I used the beta version of the Ionic Native plugins to get version 5.x which work best with our Ionic 4 app.

Of course we need the camera and file plugin, but we also need the new Webview package for a reason we will see later. For now go ahead and run:

ionic start devdacticImages blank --type=angular
cd devdacticImages

# Ionic Native Packages
npm i @ionic-native/camera@beta
npm i @ionic-native/file@beta
npm i @ionic-native/ionic-webview@beta

# Cordova Packages
ionic cordova plugin add cordova-plugin-camera
ionic cordova plugin add cordova-plugin-file
ionic cordova plugin add cordova-plugin-ionic-webview
ionic cordova plugin add cordova-sqlite-storage

Now we need to hook up everything inside our module so we can use the plugins later. We also make use of the Ionic Storage not to store the files but the path to a file later on.

Go ahead and change your src/app/app.module.ts to:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule, RouteReuseStrategy, Routes } from '@angular/router';

import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';

import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';

import { HttpClientModule } from '@angular/common/http';

import { Camera } from '@ionic-native/Camera/ngx';
import { File } from '@ionic-native/File/ngx';
import { WebView } from '@ionic-native/ionic-webview/ngx';

import { IonicStorageModule } from '@ionic/storage';

@NgModule({
  declarations: [AppComponent],
  entryComponents: [],
  imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule,
    HttpClientModule,
    IonicStorageModule.forRoot()
    ],
  providers: [
    StatusBar,
    SplashScreen,
    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
    Camera,
    File,
    WebView
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

We don’t need any special routing so our image upload app is prepared for some action!

Fix the WkWebView Plugin

At the time writing this tutorial there was also a bug within the webview plugin for iOS which leads to an app crash. You can see the details in this issue which might already be fixed later.

UPDATE: The issue is closed, so you shouldn’t get into any trouble.

If you still encounter problems, you can open your platforms/ios/devdacticImages/Plugins/cordova-plugin-ionic-webview/CDVWKWebViewEngine.m and replace it with the contents of the fixed file.

The View for Our Image Upload & Management App

Let’s start with the easiest part which is the view in our Ionic 4 image upload app. We need to display a button so users can select am image to upload. This will trigger an action sheet, and once the user has finished the image dialog a new image will be displayed inside the list.

The list itself shows all of our locally stored files (more on the logic later) with a button to upload the image and to delete the file and all references.

There isn’t really much about this so first of all now change your src/app/home/home.page.html to this:

<ion-header>
  <ion-toolbar color="primary">
    <ion-title>
      Ionic Image Upload
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content padding>
  <h3 *ngIf="images.length == 0" text-center>Please Select Image!</h3>

  <ion-list>
    <ion-item *ngFor="let img of images; index as pos" text-wrap>
      <ion-thumbnail slot="start">
        <ion-img [src]="img.path"></ion-img>
      </ion-thumbnail>
      <ion-label>
        {{ img.name }}
      </ion-label>
      <ion-button slot="end" fill="clear" (click)="startUpload(img)">
        <ion-icon slot="icon-only" name="cloud-upload"></ion-icon>
      </ion-button>
      <ion-button slot="end" fill="clear" (click)="deleteImage(img, pos)">
        <ion-icon slot="icon-only" name="trash"></ion-icon>
      </ion-button>
    </ion-item>
  </ion-list>
</ion-content>

<ion-footer>
  <ion-toolbar color="primary">
    <ion-button fill="clear" expand="full" color="light" (click)="selectImage()">
      <ion-icon slot="start" name="camera"></ion-icon>
      Select Image</ion-button>
  </ion-toolbar>
</ion-footer>

The Basics for Our Image Upload

Let’s dive into the real action. I’ll split the code for the class in multiple sections so we can go over them one by one. To start, let’s add all the dependencies our class needs (which are quite a few) and the first function that will be called once the app starts.

In the beginning we will look inside our storage to see if we have any stored information about images already captured. This array will only contain the name of a file like “1234.png”, so for each entry we need to resolve the name to the local path of our app which we add to the object as filePath.

To display the image we need another path, and here we can make use of the webview.convertFileSr()
which resolves a file:// path to a path that the WebView understands. All of this information goes to the local array which our previous view can then iterate.

Now make the first changes inside your src/app/home/home.page.ts to get started:

import { Component, OnInit, ChangeDetectorRef } from '@angular/core';
import { Camera, CameraOptions, PictureSourceType } from '@ionic-native/Camera/ngx';
import { ActionSheetController, ToastController, Platform, LoadingController } from '@ionic/angular';
import { File, FileEntry } from '@ionic-native/File/ngx';
import { HttpClient } from '@angular/common/http';
import { WebView } from '@ionic-native/ionic-webview/ngx';
import { Storage } from '@ionic/storage';

import { finalize } from 'rxjs/operators';

const STORAGE_KEY = 'my_images';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage implements OnInit {

  images = [];

  constructor(private camera: Camera, private file: File, private http: HttpClient, private webview: WebView,
    private actionSheetController: ActionSheetController, private toastController: ToastController,
    private storage: Storage, private plt: Platform, private loadingController: LoadingController,
    private ref: ChangeDetectorRef) { }

  ngOnInit() {
    this.plt.ready().then(() => {
      this.loadStoredImages();
    });
  }

  loadStoredImages() {
    this.storage.get(STORAGE_KEY).then(images => {
      if (images) {
        let arr = JSON.parse(images);
        this.images = [];
        for (let img of arr) {
          let filePath = this.file.dataDirectory + img;
          let resPath = this.pathForImage(filePath);
          this.images.push({ name: img, path: resPath, filePath: filePath });
        }
      }
    });
  }

  pathForImage(img) {
    if (img === null) {
      return '';
    } else {
      let converted = this.webview.convertFileSrc(img);
      return converted;
    }
  }

  async presentToast(text) {
    const toast = await this.toastController.create({
        message: text,
        position: 'bottom',
        duration: 3000
    });
    toast.present();
  }

  // Next functions follow here...

}

Adding New Images

The next step is to add new images. We start this process by displaying an action sheet from which the user can either select the camera or photo library as a source.

Once the source is defined we use the camera like always and we are not using a base64 as a result but the real FILE_URI of the image. Otherwise we would have to store those super big strings inside the storage which is not really considered best practice.

After the image was selected we want to copy the file over to our apps data directory with a new name so we are not more dependent on where the file really exists as we have our own copy.

Go ahead and add the 2 functions below what you already got:

async selectImage() {
    const actionSheet = await this.actionSheetController.create({
        header: "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'
            }
        ]
    });
    await actionSheet.present();
}

takePicture(sourceType: PictureSourceType) {
    var options: CameraOptions = {
        quality: 100,
        sourceType: sourceType,
        saveToPhotoAlbum: false,
        correctOrientation: true
    };

    this.camera.getPicture(options).then(imagePath => {
        var currentName = imagePath.substr(imagePath.lastIndexOf('/') + 1);
        var correctPath = imagePath.substr(0, imagePath.lastIndexOf('/') + 1);
        this.copyFileToLocalDir(correctPath, currentName, this.createFileName());
    });
}

Copy Files & Store Local Reference

So we got the image file and want to copy it to our app. For the copy function we need the path to the original file, the name, the new path and the new name.

This function will then copy over the original file under the new name to our data directory.

Now it’s not really a good idea to list only the files inside our app to keep track of them, as you might perhaps also need to store additional information.

Therefore, we update our Storage and store the information with the JSON.stringify() as an array back. Also, we create one additional object that we add to our local array with the according information and resolved paths like we also do when our app starts.

Here’s the logic to copy the file and store the information:

createFileName() {
    var d = new Date(),
        n = d.getTime(),
        newFileName = n + ".jpg";
    return newFileName;
}

copyFileToLocalDir(namePath, currentName, newFileName) {
    this.file.copyFile(namePath, currentName, this.file.dataDirectory, newFileName).then(success => {
        this.updateStoredImages(newFileName);
    }, error => {
        this.presentToast('Error while storing file.');
    });
}

updateStoredImages(name) {
    this.storage.get(STORAGE_KEY).then(images => {
        let arr = JSON.parse(images);
        if (!arr) {
            let newImages = [name];
            this.storage.set(STORAGE_KEY, JSON.stringify(newImages));
        } else {
            arr.push(name);
            this.storage.set(STORAGE_KEY, JSON.stringify(arr));
        }

        let filePath = this.file.dataDirectory + name;
        let resPath = this.pathForImage(filePath);

        let newEntry = {
            name: name,
            path: resPath,
            filePath: filePath
        };

        this.images = [newEntry, ...this.images];
        this.ref.detectChanges(); // trigger change detection cycle
    });
}

Delete Local Files

We got almost all important parts in place, but if we want to finish this example we also need the delete function. At this point you need to take care of 3 elements:

  • Remove the object from our local images array
  • Remove the item from the Ionic Storage
  • Remove the actual file from our app folder

But don’t worry, all of those actions can be performed with just one simple function:

deleteImage(imgEntry, position) {
    this.images.splice(position, 1);

    this.storage.get(STORAGE_KEY).then(images => {
        let arr = JSON.parse(images);
        let filtered = arr.filter(name => name != imgEntry.name);
        this.storage.set(STORAGE_KEY, JSON.stringify(filtered));

        var correctPath = imgEntry.filePath.substr(0, imgEntry.filePath.lastIndexOf('/') + 1);

        this.file.removeFile(correctPath, imgEntry.name).then(res => {
            this.presentToast('File removed.');
        });
    });
}

Upload Files with POST and Form Data

We got files, we got the storage, we got all information we need but still the upload process for files isn’t that easy. In our example we will have a PHP backend that accepts our file (we’ll build it in the last step) and we need to post it somehow. Also, we don’t need to use the old file transfer plugin anymore, all of this also works with the general POST of Angular HTTP.

Some of the following logic for reading the local file comes from this great tutorial.

The idea is that we first need to resolve the path of our local file which results in a FileEntry object. On this object we can than call the file() function and use a FileReader to read in the data of a file object.

The result is a blob the we can add as FormData to our request which is in the end a standard HTTP POST call with the form data.

Now go ahead and add the last functions to your class:

// Inspired by https://golb.hplar.ch/2017/02/Uploading-pictures-from-Ionic-2-to-Spring-Boot.html

startUpload(imgEntry) {
    this.file.resolveLocalFilesystemUrl(imgEntry.filePath)
        .then(entry => {
            ( < FileEntry > entry).file(file => this.readFile(file))
        })
        .catch(err => {
            this.presentToast('Error while reading file.');
        });
}

readFile(file: any) {
    const reader = new FileReader();
    reader.onloadend = () => {
        const formData = new FormData();
        const imgBlob = new Blob([reader.result], {
            type: file.type
        });
        formData.append('file', imgBlob, file.name);
        this.uploadImageData(formData);
    };
    reader.readAsArrayBuffer(file);
}

async uploadImageData(formData: FormData) {
    const loading = await this.loadingController.create({
        content: 'Uploading image...',
    });
    await loading.present();

    this.http.post("http://localhost:8888/upload.php", formData)
        .pipe(
            finalize(() => {
                loading.dismiss();
            })
        )
        .subscribe(res => {
            if (res['success']) {
                this.presentToast('File upload complete.')
            } else {
                this.presentToast('File upload failed.')
            }
        });
}

That’s all for the Ionic image upload app, the app will work now besides the upload part but you can run it already now.

Make sure that you are running the app on the simulator/device as we use the camera and filesystem so it will only work where Cordova is available!

The PHP Upload Logic

Now I’m not a PHP expert so I’ll make this as quick as possible.

If you have a server you can use that one, otherwise I simply recommend to download XAMPP and install it local.

I’m not going to cover that process since this is about Ionic image upload and not how to configure PHP. If you have set it up, you can first of all create a upload.php to accept uploads:

<?php
header('Access-Control-Allow-Origin: *');
$target_path = "uploads/";
 
$target_path = $target_path . basename( $_FILES['file']['name']);
 
if(move_uploaded_file($_FILES['file']['tmp_name'], $target_path)) {
    header('Content-type: application/json');
    $data = ['success' => true, 'message' => 'Upload and move success'];
    echo json_encode( $data );
} else{
    header('Content-type: application/json');
    $data = ['success' => false, 'message' => 'There was an error uploading the file, please try again!'];
    echo json_encode( $data );
}

?>

Also, make sure to create a uploads folder next to this file, as it will copy the images into that folder.

Additionally, to see the results of our hard work, I created a little HTML file that will scan the uploads folder and show them so we can directly see if our upload worked, create this as index.php next to the previous file and insert:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
  <title>Devdactic Image Upload</title>
</head>
<body>
<h1>Ionic Image Upload</h1>
  <?php
  $scan = scandir('uploads');
  foreach($scan as $file)
  {
    if (!is_dir($file))
    {
        echo '<h3>'.$file.'</h3>';
      echo '<img src="uploads/'.$file.'" style="width: 400px;"/><br />';
    }
  }
  ?>
</body>
</html>

You can now start your local MAMP server and navigate to http://localhost:8888 which will display your Ionic Images overview.

In our example we used this URL for the upload, but this will only work if you run the app on the simulator. If you deploy the app to your iOS device you need to change the URL to the IP of your computer!

Conclusion

The process of capturing image with Ionic 4 is still the same, but storing local files and uploading works actually a bit easier then with previous versions.

If you don’t see your files it’s most likely an issue with the WKWebView and how local files can be displayed, so if you encounter any problems please provide some further logs!

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

The post The Ionic 4 Images Guide (Capture, Store & Upload with POST) appeared first on Devdactic.

Dynamic Ionic 4 Slides with Shopping Cart

$
0
0

When you work with structured data that is distributed in different categories and elements, chances are high you have looked out for a nice interface that allows to open/collapse categories while also allows to slide items horizontal in a visual appealing way.

In this tutorial we will build a simple app that shows data in different categories with expandable views and dynamic slides based on the amount of items in each category.

ionic-4-dynamic-slides

Additionally we will also add a little shopping cart logic so we can add items to a cart and finally see how many Pizzas a user wants to order and how much it will cost!

Note: This tutorial was created with Ionic 4.0.0 Beta 7!

Starting Our Shopping App

For our example we don’t need any special packages, Ionic already has all the tools we need so go ahead and create a new app like this:

ionic start devdacticCategories blank --type=angular
ionic g page cart
ionic g service cart

We will use the new page to display the list of our cart and the service to keep track of the items, so let’s do this!

Creating a Cart Service

First we need some data because otherwise the example would be so static and you don’t really want this, right?

Therefore, our service will hold some dummy information for categories and products that you would typically get from your API.

Besides this information the service also keeps track of the cart where users can add products. For your own exercise you can also implement a remove function so users can edit their selection!

Because there’s nothing fancy inside go ahead and change your app/cart.service.ts to:

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class CartService {

  private data = [
    {
      category: 'Pizza',
      expanded: true,
      products: [
        { id: 0, name: 'Salami', price: '8' },
        { id: 1, name: 'Classic', price: '5' },
        { id: 2, name: 'Tuna', price: '9' },
        { id: 3, name: 'Hawai', price: '7' }
      ]
    },
    {
      category: 'Pasta',
      products: [
        { id: 4, name: 'Mac & Cheese', price: '8' },
        { id: 5, name: 'Bolognese', price: '6' }
      ]
    },
    {
      category: 'Salad',
      products: [
        { id: 6, name: 'Ham & Egg', price: '8' },
        { id: 7, name: 'Basic', price: '5' },
        { id: 8, name: 'Ceaser', price: '9' }
      ]
    }
  ];

  private cart = [];

  constructor() { }

  getProducts() {
    return this.data;
  }

  getCart() {
    return this.cart;
  }

  addProduct(product) {
    this.cart.push(product);
  }
}

Alright, now we can make use of this service from our overview and cart page!

Building The Ion-Slides Category View

As you have seen above we want to implement a view with categories and sliding elements inside each category. But before we get to that, we need to load the data from our service.

Also, we make use of the Angular router to navigate to our cart page which is the Ionic 4 way to navigate (or the general Angular way)!

Perhaps the most interesting part is the sliderConfig which will be passed to our slides inside the view. You can find the possible list of parameters on the Swiper documentation which is the underlying component for Ionic slides!

With that in mind you can now prepare your app/home/home.page.ts like this:

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { CartService } from '../cart.service';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage implements OnInit {

  cart = [];
  items = [];

  sliderConfig = {
    slidesPerView: 1.6,
    spaceBetween: 10,
    centeredSlides: true
  };

  constructor(private router: Router, private cartService: CartService) { }

  ngOnInit() {
    this.items = this.cartService.getProducts();
    this.cart = this.cartService.getCart();
  }

  addToCart(product) {
    this.cartService.addProduct(product);
  }

  openCart() {
    this.router.navigate(['cart']);
  }
}

Now we just need to display the information we got in an appropriate way. But easier said then done.

Basically, you iterate over the top level of your data (where each object has a category and items) and create a container for this information.

Then we create a new ion-slides component for each category which dynamically creates slides based on the products of that category.

Also, the first element in each category block is a row which is tappable so we can set the expanded variable that is checked later and is used to show or hide the content of the slides!

It’s a bit like the logic for the Accordion List I’ve shown you before!

Now go ahead and add all of this to your app/home/homepage.html:

<ion-header>
  <ion-toolbar>
    <ion-title>
      Ionic Shopping
    </ion-title>
    <ion-buttons slot="end">
      <ion-button (click)="openCart()">
        <ion-badge *ngIf="cart.length > 0">{{ cart.length }}</ion-badge>
        <ion-icon slot="icon-only" name="cart"></ion-icon>
      </ion-button>
    </ion-buttons>
  </ion-toolbar>
</ion-header>

<ion-content>
  <div *ngFor="let cat of items" class="category-block">

    <ion-row no-padding class="category-banner">
      <ion-col text-left button tappable (click)="cat.expanded = !cat.expanded" align-self-center>
        {{ cat.category }}
      </ion-col>
    </ion-row>

    <ion-slides [options]="sliderConfig">
      <ion-slide *ngFor="let product of cat.products">
        <div *ngIf="cat.expanded">
          <ion-card>
            <ion-card-header>
              <ion-card-title>
                {{ product.name }} - ${{ product.price }}
              </ion-card-title>
              <ion-card-content>
                <img src="https://via.placeholder.com/300x300">
                <ion-button expand="full" (click)="addToCart(product)">Add to Cart</ion-button>
              </ion-card-content>
            </ion-card-header>
          </ion-card>
        </div>
      </ion-slide>
    </ion-slides>

  </div>
</ion-content>

Besides our slides logic we have also added the cart button at the top to display the current count of the cart using a badge icon.

To make everything look a bit nice we add some CSS, but it’s not used to affect how the slides are presented, this is all done through our previous configuration!

If you want to make it look good, add this to the app/home/home.page.scss:

ion-badge {
    color: #fff;
    position: absolute;
    top: 0px;
    right: 0px;
    border-radius: 100%;
}

.category-block {
    margin-bottom: 4px;
}

.category-banner {
    border-left: 8px solid var(--ion-color-secondary);
    background: var(--ion-color-light);
    height: 40px;
    padding: 10px;
    font-weight: 500;
}

Now we can browse our categories and products but we still need to wrap things up with our cart page.

Creating the Cart Page

Inside this page we only need to retrieve the information from our service and transform it a bit so we can display it more easily.

You could also have this logic inside the service already, so whenever you add or remove a product you change a variable on that object. But I wanted to keep the service easy and only show this cart as one solution!

Therefore we have to iterate over the data and count the same products so we can finally calculate the total price based on the count and price of each product!

There’s not much more to it so go ahead and change your app/cart/cart.page.ts to:

import { CartService } from './../cart.service';
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-cart',
  templateUrl: './cart.page.html',
  styleUrls: ['./cart.page.scss'],
})
export class CartPage implements OnInit {

  selectedItems = [];

  total = 0;

  constructor(private cartService: CartService) { }

  ngOnInit() {
    let items = this.cartService.getCart();
    let selected = {};
    for (let obj of items) {
      if (selected[obj.id]) {
        selected[obj.id].count++;
      } else {
        selected[obj.id] = {...obj, count: 1};
      }
    }
    this.selectedItems = Object.keys(selected).map(key => selected[key])
    this.total = this.selectedItems.reduce((a, b) => a + (b.count * b.price), 0);
  }

}

The last missing piece is now the view for our cart which is pretty basic.

As we have prepared the data we can now loop over the entries and simply display the relevant information for each item. If you want to display a price you can also make use of the currency pipe which is build in, so finish the tutorial by changing the app/cart/cart.page.html to:

<ion-header>
  <ion-toolbar>
    <ion-buttons slot="start">
      <ion-back-button defaultHref="/"></ion-back-button>
    </ion-buttons>
    <ion-title>My Cart</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  <ion-list>
    <ion-item *ngFor="let item of selectedItems" lines="inset">
      {{ item.count }}x {{ item.name }} - {{ item.price | currency:'USD':'symbol' }}
      <ion-label slot="end" text-right>{{ (item.price * item.count) | currency:'USD':'symbol' }}</ion-label>
    </ion-item>
    <ion-item>
      Total: <span slot="end">{{ total | currency:'USD':'symbol' }}</span>
    </ion-item>
  </ion-list>
</ion-content>

And now you got a nice dynamic sliding list view with expandable components plus simple shopping cart logic!

Of course there are many areas to improve here, also some animations could make everything look even nicer so if you are interested in anything particular, please share your thoughts below!

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

The post Dynamic Ionic 4 Slides with Shopping Cart appeared first on Devdactic.

How to Build an Ionic 4 App with Offline Mode

$
0
0

When one of your project requirements is to have an offline mode where users can still use the app and information even without a connection, there are different ways to go about this.

In this article we will build an Ionic 4 app with offline mode that caches API data so it can be used as a fallback later. Also, we create an offline manager which stores requests made during that time so we can later send out the calls one by one when we are online again.

While there are caching plugins (that don’t work well with v4 yet) and PWA with service workers you sometimes have specific requirements and therefore this post aims to show how to build your own offline mode system – not 100% like described here perhaps but it will give you a good start in the right direction.

This post was created with the Ionic 4 Beta 13!

Starting our Offline Caching App

Let’s start the fun by creating a new Ionic 4 app, generating a bunch of services that we will need for the different features and finally the Ionic native and Cordova plugins for checking the network and using Ionic Storage:

ionic start offlineMode blank --type=angular
cd offlineMode

ionic g service services/api
ionic g service services/network
ionic g service services/offlineManager

npm install @ionic/storage @ionic-native/network@beta
ionic cordova plugin add cordova-sqlite-storage
ionic cordova plugin add cordova-plugin-network-information

To make use of all the great things we need to import them inside our app/app.module.ts like always:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule, RouteReuseStrategy, Routes } from '@angular/router';

import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';

import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';

import { IonicStorageModule } from '@ionic/storage';
import { HttpClientModule } from '@angular/common/http';
import { Network } from '@ionic-native/network/ngx';

@NgModule({
  declarations: [AppComponent],
  entryComponents: [],
  imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule,
    IonicStorageModule.forRoot(),
    HttpClientModule],
  providers: [
    StatusBar,
    SplashScreen,
    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
    Network
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

To test our API calls we will simply use the service reqres. It’s a simple mocking tool, and the data we send to the API won’t be used there but we’ll still get a response so it’s the perfect way to test our offline features!

Handling Network Changes

First of all we need to know whether we are currently connected to a network or not. For this we’ll use a simple service that informs us about all the changes happening to the network by subscribing to the onConnect() / onDisconnect() events and then emitting a new value.

At this point you can also show a toast like we do or use any other mechanism to inform the user that the connection has changed.

The base for this service comes actually from one old Ionic Forum thread and I just made some additions to it!

This code is not specific to offline mode apps but could be used in general for all Ionic apps where connectivity is important, so go ahead and change your app/services/network.service.ts to:

import { Injectable } from '@angular/core';
import { Network } from '@ionic-native/network/ngx'
import { BehaviorSubject, Observable } from 'rxjs';
import { ToastController, Platform } from '@ionic/angular';

export enum ConnectionStatus {
  Online,
  Offline
}

@Injectable({
  providedIn: 'root'
})
export class NetworkService {

  private status: BehaviorSubject<ConnectionStatus> = new BehaviorSubject(ConnectionStatus.Offline);

  constructor(private network: Network, private toastController: ToastController, private plt: Platform) {
    this.plt.ready().then(() => {
      this.initializeNetworkEvents();
      let status =  this.network.type !== 'none' ? ConnectionStatus.Online : ConnectionStatus.Offline;
      this.status.next(status);
    });
  }

  public initializeNetworkEvents() {

    this.network.onDisconnect().subscribe(() => {
      if (this.status.getValue() === ConnectionStatus.Online) {
        console.log('WE ARE OFFLINE');
        this.updateNetworkStatus(ConnectionStatus.Offline);
      }
    });

    this.network.onConnect().subscribe(() => {
      if (this.status.getValue() === ConnectionStatus.Offline) {
        console.log('WE ARE ONLINE');
        this.updateNetworkStatus(ConnectionStatus.Online);
      }
    });
  }

  private async updateNetworkStatus(status: ConnectionStatus) {
    this.status.next(status);

    let connection = status == ConnectionStatus.Offline ? 'Offline' : 'Online';
    let toast = this.toastController.create({
      message: `You are now ${connection}`,
      duration: 3000,
      position: 'bottom'
    });
    toast.then(toast => toast.present());
  }

  public onNetworkChange(): Observable<ConnectionStatus> {
    return this.status.asObservable();
  }

  public getCurrentNetworkStatus(): ConnectionStatus {
    return this.status.getValue();
  }
}

Storing API Requests Locally

Now we can see if we are online right now or not, and we will use this information to store important requests made during the time when we are offline.

In most cases there are calls that you don’t really need to send out again when the user is back online like any GET request. But if the user wants to perform other CRUD like operations you might want to keep track of that.

The difficulty here is of course that you would not only store the request but also perform the operation locally already upfront which might or might not work for your. We will skip this part as we don’t really have any logic in our app but it’s something you should keep in mind.

Our next service can store a request (which will be called from another service later) as an object with some information that you decide right to our Ionic storage. This means, while we are offline an array of API calls is created that we need to work off later.

This task is done by the checkForEvents() function that will later be called whenever the user is back online. I tried to keep this service as general as possible you can stick it into your own projects!

Within our check function we basically get the array from the storage and send out all the requests one by one and finally delete the stored requests again. The function will return a result once every operation is finished so if you need to wait for the historic data to be sent, you can subscribe to the check and be sure that everything is finished when you continue!

Now let’s add the magic to your app/services/offline-manager.service.ts:

import { Injectable } from '@angular/core';
import { Storage } from '@ionic/storage';
import { from, of, forkJoin } from 'rxjs';
import { switchMap, finalize } from 'rxjs/operators';
import { HttpClient } from '@angular/common/http';
import { ToastController } from '@ionic/angular';

const STORAGE_REQ_KEY = 'storedreq';

interface StoredRequest {
  url: string,
  type: string,
  data: any,
  time: number,
  id: string
}

@Injectable({
  providedIn: 'root'
})
export class OfflineManagerService {

  constructor(private storage: Storage, private http: HttpClient, private toastController: ToastController) { }

  checkForEvents() {
    return from(this.storage.get(STORAGE_REQ_KEY)).pipe(
      switchMap(storedOperations => {
        let storedObj = JSON.parse(storedOperations);
        if (storedObj && storedObj.length > 0) {
          return this.sendRequests(storedObj).pipe(
            finalize(() => {
              let toast = this.toastController.create({
                message: `Local data succesfully synced to API!`,
                duration: 3000,
                position: 'bottom'
              });
              toast.then(toast => toast.present());

              this.storage.remove(STORAGE_REQ_KEY);
            })
          );
        } else {
          console.log('no local events to sync');
          return of(false);
        }
      })
    )
  }

  storeRequest(url, type, data) {
    let toast = this.toastController.create({
      message: `Your data is stored locally because you seem to be offline.`,
      duration: 3000,
      position: 'bottom'
    });
    toast.then(toast => toast.present());

    let action: StoredRequest = {
      url: url,
      type: type,
      data: data,
      time: new Date().getTime(),
      id: Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 5)
    };
    // https://stackoverflow.com/questions/1349404/generate-random-string-characters-in-javascript

    return this.storage.get(STORAGE_REQ_KEY).then(storedOperations => {
      let storedObj = JSON.parse(storedOperations);

      if (storedObj) {
        storedObj.push(action);
      } else {
        storedObj = [action];
      }
      // Save old & new local transactions back to Storage
      return this.storage.set(STORAGE_REQ_KEY, JSON.stringify(storedObj));
    });
  }

  sendRequests(operations: StoredRequest[]) {
    let obs = [];

    for (let op of operations) {
      console.log('Make one request: ', op);
      let oneObs = this.http.request(op.type, op.url, op.data);
      obs.push(oneObs);
    }

    // Send out all local events and return once they are finished
    return forkJoin(obs);
  }
}

Right now the app does not really make any sense so let’s move into the right direction now.

Making API Requests with Local Caching

The goal was to make API calls, return locally cached data when we are offline and also store some requests (that you identify as important) locally with our offline manager and requests queue until we are back online.

So far so good.

There’s also a great caching plugin I described within the Ionic Academy but it’s not yet working for v4 and also with the approach of this article you can build the system just like your app needs it so you control everything.

Anyway, we continue with the class that actually performs the API calls and you’ll most likely already have something like this.

At this point you can now make use of both of our previous services.

First of all we can check the internet connection inside our functions to see if we are maybe offline in which case we either directly return the cached result from our storage or otherwise store the PUT request within our offline manager so it can be performed later on.

Along this and the other classes you have seen quite a few RxJS operators, and if they scare you I can understand you 100%. I’ve slowly made my way through different operators and how to use them, and I’m far from an expert.

They basically help us to transform our Observable data because we are often working with the Ionic Storage which returns a Promise and not an Observable. It’s always good to know what the functions return so you can map/change the result according to what you need.

If you want a little course / intro on different RxJS functionalities just let me know below in the comments!

Back to topic. If we are online, our requests run through as desired and for a GET we store that information to our storage, for the important PUT we might want to catch any error case and store it locally then. This is a general assumption and of course you should not store any failed request!

Perhaps check a status or flag to determine if it’s worth caching or if the request was just bad.

All of this looks like the code below in action which goes to your app/services/api.service.ts:

import { OfflineManagerService } from './offline-manager.service';
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { NetworkService, ConnectionStatus } from './network.service';
import { Storage } from '@ionic/storage';
import { Observable, from } from 'rxjs';
import { tap, map, catchError } from "rxjs/operators";

const API_STORAGE_KEY = 'specialkey';
const API_URL = 'https://reqres.in/api';

@Injectable({
  providedIn: 'root'
})
export class ApiService {

  constructor(private http: HttpClient, private networkService: NetworkService, private storage: Storage, private offlineManager: OfflineManagerService) { }

  getUsers(forceRefresh: boolean = false): Observable<any[]> {
    if (this.networkService.getCurrentNetworkStatus() == ConnectionStatus.Offline || !forceRefresh) {
      // Return the cached data from Storage
      return from(this.getLocalData('users'));
    } else {
      // Just to get some "random" data
      let page = Math.floor(Math.random() * Math.floor(6));
      
      // Return real API data and store it locally
      return this.http.get(`${API_URL}/users?per_page=2&page=${page}`).pipe(
        map(res => res['data']),
        tap(res => {
          this.setLocalData('users', res);
        })
      )
    }
  }

  updateUser(user, data): Observable<any> {
    let url = `${API_URL}/users/${user}`;
    if (this.networkService.getCurrentNetworkStatus() == ConnectionStatus.Offline) {
      return from(this.offlineManager.storeRequest(url, 'PUT', data));
    } else {
      return this.http.put(url, data).pipe(
        catchError(err => {
          this.offlineManager.storeRequest(url, 'PUT', data);
          throw new Error(err);
        })
      );
    }
  }

  // Save result of API requests
  private setLocalData(key, data) {
    this.storage.set(`${API_STORAGE_KEY}-${key}`, data);
  }

  // Get cached API result
  private getLocalData(key) {
    return this.storage.get(`${API_STORAGE_KEY}-${key}`);
  }
}

Quite a lot of code for not seeing any results on the screen. But those 3 services are more or less a blueprint for your offline mode app so you can plug it in as easy as possible.

Using all Local & Cache Functionalities

Let’s wrap things up by using the different features that we have implemented previously.

First of all, let’s subscribe to our network connection state at the top level of our app so we can automatically run all stored requests when the user is back online. Again, once the Observable of checkForEvents() returns we can be sure that all local requests have been processed.

To add the check, simply change your app/app.component.ts to:

import { NetworkService, ConnectionStatus } from './services/network.service';
import { Component } from '@angular/core';

import { Platform } from '@ionic/angular';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';
import { OfflineManagerService } from './services/offline-manager.service';

@Component({
  selector: 'app-root',
  templateUrl: 'app.component.html'
})
export class AppComponent {
  constructor(
    private platform: Platform,
    private splashScreen: SplashScreen,
    private statusBar: StatusBar,
    private offlineManager: OfflineManagerService,
    private networkService: NetworkService
  ) {
    this.initializeApp();
  }

  initializeApp() {
    this.platform.ready().then(() => {
      this.statusBar.styleDefault();
      this.splashScreen.hide();

      this.networkService.onNetworkChange().subscribe((status: ConnectionStatus) => {
        if (status == ConnectionStatus.Online) {
          this.offlineManager.checkForEvents().subscribe();
        }
      });
    });
  }
}

Now we just need to get the real data by building tiny view and a simple page that makes the API calls.

In our case, the app/home/home.page.ts could look like this:

import { Platform } from '@ionic/angular';
import { Component, OnInit } from '@angular/core';
import { ApiService } from '../services/api.service';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage implements OnInit {

  users = [];

  constructor(private apiService: ApiService, private plt: Platform) { }

  ngOnInit() {
    this.plt.ready().then(() => {
      this.loadData(true);
    });
  }

  loadData(refresh = false, refresher?) {
    this.apiService.getUsers(refresh).subscribe(res => {
      this.users = res;
      if (refresher) {
        refresher.target.complete();
      }
    });
  }

  updateUser(id) {
    this.apiService.updateUser(id, {name: 'Simon', job: 'CEO'}).subscribe();
  }
}

There are not really any more words needed, we just use our service to get data or update data. And that’s the beauty of a good caching solution – the magic happens inside the service, not in your pages!

Here we can simply focus on the operations, so let’s finish everything by adding a simply dummy view inside our app/home/home.page.html:

<ion-header>
  <ion-toolbar>
    <ion-title>
      Ionic Offline Mode
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  <ion-refresher slot="fixed" (ionRefresh)="loadData(true, $event)">
    <ion-refresher-content>
    </ion-refresher-content>
  </ion-refresher>

  <ion-list>
    <ion-item *ngFor="let user of users" tappable (click)="updateUser(user.id)">
      <ion-thumbnail slot="start">
        <img [src]="user.avatar">
      </ion-thumbnail>
      <ion-label>
        <h3>{{ user.first_name }} {{ user.last_name }}</h3>
      </ion-label>
    </ion-item>
  </ion-list>
</ion-content>

We add a refresher to have some force refresh mechanism in place (still, returning cached data when offline!) and a list of the users with a button to update them.

This app won’t get an award for it’s design nor functionality, but it definitely works when you once got data and then turn on your airplane mode! You can kill the app and start it again, the results will be retrieved from the storage and your offline manager requests queue just grows whenever you try to make the PUT request when your are offline until you come back online again.

Conclusion

In general caching is a bit tricky and there are not super many out of the box plugins that work for everyone. This post is one way to tackle the problem and hopefully shows that it’s definitely possible on one or another way depending on your requirements.

If you also want to store images, there’s a way of downloading them to your device and using them when you are offline. One way is using an image caching plugin like described here (again, not yet really working for v4 afaik), but I’ve recently come up with my own implementation of preloading all images of a request so let me know if that’s something you would be interested in!

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

The post How to Build an Ionic 4 App with Offline Mode appeared first on Devdactic.

JWT Authentication with Ionic & Node.js – Part 1: The Auth Server

$
0
0

There’s almost no topic that has appeared more often on this blog than user authentication and this new and updated guide will hopefully enable you to build your own Ionic app with user authentication!

In this two-part series we will build an Ionic App (so if you came here without prior knowledge you can check out my Ionic Academy!) in which users can create a new account, log in and access protected data.

We will achieve this by creating an API that we build in this first part using Node.js and MongoDB. We’ll not cover how to install MongoDB but I’m sure you’ll get it done in a minute on your own.

This server will use JSON Web Tokens for authentication which is one of the most common forms these days, and it’s also super easy to implement in our Angular app later. At the end of this first part your server will be up and running with some nice API routes!

Part 2: The Ionic App – COMING SOON

Setting up the Server

We begin the adventure by creating our server inside an empty folder. The npm init will create a new package.json file for us and we then install all the needed packages so start your server with this:

mkdir api
cd api
npm init -y
npm install express body-parser jsonwebtoken passport passport-jwt mongoose bcrypt cors

After the packages are installed your package.json will look more or less like this:

{
  "name": "api",
  "version": "1.0.0",
  "description": "",
  "main": "src/server.js",
  "scripts": {
    "start": "node src/server.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "Simon Grimm",
  "license": "ISC",
  "dependencies": {
    "bcrypt": "^3.0.2",
    "body-parser": "^1.18.3",
    "express": "^4.16.4",
    "jsonwebtoken": "^8.3.0",
    "mongoose": "^5.3.7",
    "passport": "^0.4.0",
    "passport-jwt": "^4.0.0"
  }
}

I’ve only changed the main entry point and also added the start script which will call our initial server file. For testing I also highly recommend that you install nodemon which helps to get live reload for your server while developing it!

Next we need a bunch of files so go ahead and create the folder structure and files like in the image below.

special

We will go through each of those files now and fill our app with live. The first thing we can change is the config/config.js which holds some general values:

module.exports = {
    jwtSecret: 'long-live-the-ionic-academy',
    db: 'mongodb://localhost/ionic-jwt'
};

The secret will be needed for signing the JWT later and the database URI will be used for making a connection. Make sure your MongoDB is running when you test the up, but you don’t need to create the database upfront (like here with the name “ionic-jwt”) because it will be created for you.

You could also use a service like Heroku for hosting your server and having a MongoDB connected directly of course!

MongoDB Database Schema

To access documents of our database we will use an Object Database Modelling library called Mongoose. Therefore, we need to define our model upfront and later our app can easily store objects of that type to the database (or find, update…).

In our case we need to create a model for a user which has an email and password. Also, we need to make sure that passwords are never stored in clear text so we add a function that will be called before save and simply transforms the plain password into a hash value.

We also add the function to compare a password to the hashed password of a user which will be needed once we implement the login. For now open your models/user.js and insert:

var mongoose = require('mongoose');
var bcrypt = require('bcrypt');
  
var UserSchema = new mongoose.Schema({
  email: {
        type: String,
        unique: true,
        required: true,
        lowercase: true,
        trim: true
    },
  password: {
        type: String,
        required: true
    }
});
 
UserSchema.pre('save',  function(next) {
    var user = this;

     if (!user.isModified('password')) return next();

     bcrypt.genSalt(10, function(err, salt) {
         if (err) return next(err);
 
         bcrypt.hash(user.password, salt, function(err, hash) {
             if (err) return next(err);
 
             user.password = hash;
             next();
         });
     });
});
 
UserSchema.methods.comparePassword = function (candidatePassword, cb) {
    bcrypt.compare(candidatePassword, this.password, (err, isMatch) => {
        if (err) return cb(err);
        cb(null, isMatch);
    });
};
 
module.exports = mongoose.model('User', UserSchema);

Now we could store users to the database, so let’s focus a bit on authentication.

Passport JWT Middleware

For our JWT authentication we will use an additional package called passport which works with so-called strategies. In our case we need a JWT strategy and there’s also an additional package we use.

The idea is: If an endpoint is protected inside the server, we have to check for the Authorisation header field and see if it contains a valid JWT. And exactly this will be handled by our strategy.

After grabbing the token by using the fromAuthHeaderAsBearerToken() function we will try to find the user inside our database by calling User.findById(..) because our JWT payload will always contain the ID of a user.

All of this will be handled in the background as a middleware which is basically an additional handler of the incoming requests that can perform actions. You could have as many middleware functions as you want!

So add the code for our JWT middleware to your middleware/passport.js:

var User        = require('../models/user');
var JwtStrategy = require('passport-jwt').Strategy,
    ExtractJwt  = require('passport-jwt').ExtractJwt;
var config      = require('../config/config');

var opts = {
    jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
    secretOrKey: config.jwtSecret
}

module.exports = new JwtStrategy(opts, function (jwt_payload, done) {
    User.findById(jwt_payload.id, function (err, user) {
        if (err) {
            return done(err, false);
        }
        if (user) {
            return done(null, user);
        } else {
            return done(null, false);
        }
    });
});

We will later tell our app and especially passport to use this strategy, for now let’s work on the login part.

Creating the Routes

As you might have noticed we are splitting up our code over multiple files to keep a nice structure in our project. A clean architecture is important to establish from the beginning!

Therefore we will now build the register and login function, which we can then add to the actual routes. You’ll see how in a second.

If a user wants to register we need to check if the request contains both email and password, and also if the email is not already in use. If all of that is fine we create a new user by simply passing the request body to the constructor of our user model. If we then call save() on the object it will be written to our database and a document is created like in the image below.

ionic-4-jwt-database

To inspect your database you can use a great tool like Robo 3T!

If a user want’s to login, we perform the same checks but now also compare the password he has sent with the stored one using the comparePassword function we have added to our model in the beginning. When the login is successful we will create a new JSON Web Token that we send back to the client. This token consists of the user id and email in our case but could contain more information. You can add an expiry time here so if you want to test things out either make it pretty long or short!

Now go ahead by adding the functions to our controller/user-controller.js:

var User = require('../models/user');
var jwt = require('jsonwebtoken');
var config = require('../config/config');

function createToken(user) {
    return jwt.sign({ id: user.id, email: user.email }, config.jwtSecret, {
        expiresIn: 200 // 86400 expires in 24 hours
      });
}

exports.registerUser = (req, res) => {
    if (!req.body.email || !req.body.password) {
        return res.status(400).json({ 'msg': 'You need to send email and password' });
    }

    User.findOne({ email: req.body.email }, (err, user) => {
        if (err) {
            return res.status(400).json({ 'msg': err });
        }

        if (user) {
            return res.status(400).json({ 'msg': 'The user already exists' });
        }

        let newUser = User(req.body);
        newUser.save((err, user) => {
            if (err) {
                return res.status(400).json({ 'msg': err });
            }
            return res.status(201).json(user);
        });
    });
};

exports.loginUser = (req, res) => {
    if (!req.body.email || !req.body.password) {
        return res.status(400).send({ 'msg': 'You need to send email and password' });
    }

    User.findOne({ email: req.body.email }, (err, user) => {
        if (err) {
            return res.status(400).send({ 'msg': err });
        }

        if (!user) {
            return res.status(400).json({ 'msg': 'The user does not exist' });
        }

        user.comparePassword(req.body.password, (err, isMatch) => {
            if (isMatch && !err) {
                return res.status(200).json({
                    token: createToken(user)
                });
            } else {
                return res.status(400).json({ msg: 'The email and password don\'t match.' });
            }
        });
    });
};

Always keep in mind that you don’t add sensitive data to this token as others might intercept it. Below you can see what it looks like and how the payload is easily decoded without any additional knowledge, yet it’s invalid because attackers will (or should) never know about the secret used to sign it!


Because we have separated the actual functionality from the routes of our app we now need to establish some routing for the API. We will have 3 important routes in our case:

  • POST /register: Create a new user document inside the database
  • POST /login: Login a user to generate a new JWT
  • GET /special: A JWT protected route that only users with a valid token can access

For the first 2 routes we can use both of the functions of our previous controller. For the third one we don’t need a special function as we just return some plain data, but here we will use our Passport middleware as the second argument of the chain!

All routes that we want to protect need this middleware so if you extend this tutorial simply add it to other routes as well or see how the basic / route behaves with and without the middleware.

Because we did a lot of the work upfront our routing is now pretty simple so change the src/routes.js to:

var express         = require('express'),
    routes          = express.Router();
var userController  = require('./controller/user-controller');
var passport	    = require('passport');

routes.get('/', (req, res) => {
    return res.send('Hello, this is the API!');
});

routes.post('/register', userController.registerUser);
routes.post('/login', userController.loginUser);

routes.get('/special', passport.authenticate('jwt', { session: false }), (req, res) => {
    return res.json({ msg: `Hey ${req.user.email}! I open at the close.` });
});

module.exports = routes;

Alright, we got all the function, routing and JWT authentication in place now we just need to connect everything and start the server.

Starting the Server with Everything we Have

To start our server we use Express and we have already used the router in the previous snippet from Express as well.

What’s important before we start our server is to tell it to use() our Passport package and also the middleware that we have created.

Also, the app will be available at http://localhost:5000 if you test it locally and all of our API routes that we added in the previous file will be available under http://localhost:5000/api because we tell our app to use the routes for /api.

By doing this you can structure your domains inside the server pretty great and have the code separated across multiple routing files and additional controllers!

Of course we finally need to create a connection to our MongoDB through mongoose and then we can finally tell our app to listen on the specified port and the magic begins.

All of the following goes into your src/server.js:

var express     = require('express');
var bodyParser  = require('body-parser');
var passport	= require('passport');
var mongoose    = require('mongoose');
var config      = require('./config/config');
var port        = process.env.PORT || 5000; 
var cors        = require('cors');

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

// get our request parameters
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

// Use the passport package in our application
app.use(passport.initialize());
var passportMiddleware = require('./middleware/passport');
passport.use(passportMiddleware);
 
// Demo Route (GET http://localhost:5000)
app.get('/', function(req, res) {
  return res.send('Hello! The API is at http://localhost:' + port + '/api');
});

var routes = require('./routes');
app.use('/api', routes);

mongoose.connect(config.db, { useNewUrlParser: true , useCreateIndex: true});

const connection = mongoose.connection;

connection.once('open', () => {
    console.log('MongoDB database connection established successfully!');
});

connection.on('error', (err) => {
    console.log("MongoDB connection error. Please make sure MongoDB is running. " + err);
    process.exit();
});
 
// Start the server
app.listen(port);
console.log('There will be dragons: http://localhost:' + port);

You can now run your server with multiple start commands whatever you enjoy the most (I’d recommend nodemon!):

  1. nodemon src/server.js
  2. node src/server.js
  3. npm start

Conclusion

We have now built a simple server with the basic functionality for a user authentication system. In the second part we will start a new Ionic app and implement the JWT authentication on the frontend with Angular.

All of the code in here was now magic and hopefully encourages you to play around a bit with Node.js as an alternative to something like Firebase. Both are great but sometimes your own server just rocks!

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

The post JWT Authentication with Ionic & Node.js – Part 1: The Auth Server appeared first on Devdactic.

JWT Authentication with Ionic & Node.js – Part 2: The Ionic App

$
0
0

Authentication for Ionic apps is mandatory in a great amount of apps so we can’t talk enough about the topic. Also, this approach works almost the same for pure Angular apps without Ionic so it’s definitely something you should know about!

We are already at the second part where we will develop the actual Ionic app. Inside the first part we have built the server for our JSON Web Token authentication so make sure you got that server up and running by now!

Part 1: The Node.js Server
Part 2: The Ionic App <- You are here

ionic-jwt-auth-app

Once we are done with this part you have a fully working authentication system working where users can signup, register and login to pages that only logged in users can see!

Starting our JWT Auth Ionic App

We start our Ionic app like always with a blank project and add a few pages and services that we will need for the authentication. Also, we install the Ionic storage package where we will store the JWT (for pure Angular we would simply use localstorage) and finally add another helping package called angular-jwt, so go ahead and run:

ionic start devdacticAuth blank --type=angular
cd devdacticAuth

ionic g page pages/login
ionic g page pages/inside

ionic g service services/auth
ionic g service services/authGuard

npm i @ionic/storage
npm i @auth0/angular-jwt

ionic cordova plugin add cordova-sqlite-storage

Once your app is ready we need to configure our JWT package for our module: We need to let it know where our access token is stored and also since a later version we have to supply an array of whitelistedDomains inside the jwtOptionsFactory.

The trick is: For the domains listed in that array the package will automatically add the “Authorization: Bearer xyz” header with our token to every request of the Angular HttpClient!

Besides that we also need to include the HttpClientModule inside our module and the Ionic Storage, so change your app/app.module.ts to:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router';

import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';

import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';

import { HttpClientModule } from '@angular/common/http';
import { Storage, IonicStorageModule } from '@ionic/storage';
import { JwtModule, JWT_OPTIONS } from '@auth0/angular-jwt';

export function jwtOptionsFactory(storage) {
  return {
    tokenGetter: () => {
      return storage.get('access_token');
    },
    whitelistedDomains: ['localhost:5000']
  }
}

@NgModule({
  declarations: [AppComponent],
  entryComponents: [],
  imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule,
  HttpClientModule,
  IonicStorageModule.forRoot(),
  JwtModule.forRoot({
    jwtOptionsProvider: {
      provide: JWT_OPTIONS,
      useFactory: jwtOptionsFactory,
      deps: [Storage],
    }
  })],
  providers: [
    StatusBar,
    SplashScreen,
    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

Since Ionic 4 is a lot closer to Angular we can now also store our environment variables in a file so we can autopmatically switch between dev and prod variables. It’s not really needed in our tutorial but still we want to follow a good coding style so add the URL of your server to the src/environments/environment.ts:

export const environment = {
  production: false,
  url: 'http://localhost:5000'
};

In our case the server should run exactly there, it’s also the whitelisted domain we passed to the JWT helper. If you have deployed the server or run it on a different port make sure to change those values.

Creating the Authentication Logic

We start with the core functionality of our app by implementing the authentication service. There are quite a few functions but I don’t want to split up the code so here’s the explanation for the whole service that follows afterwards:

  • We have an authenticationState Behaviour Subject which is a special type of Observable where we can emit new values to all subscribers
  • Inside the constructor we always check for an existing token so we can automatically log in a user
  • checkToken looks up our storage for a valid JWT and if found, changes our authenticationState
  • Register and login do exactly what they sound like and also upon successful login we will decode the token and save it to our storage so it can be used for our authenticated requests later
  • Logout reverts the login and clears the token from the storage plus changes our current authentication state
  • The special data route is the only protected route inside our API so the call only works once we are authenticated. Also, if we get back an unauthorised error our token seems to be invalid so we can throw an error and change our auth state again
  • To get the current value of our authentication state we can call the isAuthenticated function. Subjects are pretty cool!

Ok that’s the idea behind our service, of course it’s not enough to make our app secure but for now go ahead and change your app/services/auth.service.ts to this:

import { Platform, AlertController } from '@ionic/angular';
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { JwtHelperService } from '@auth0/angular-jwt';
import { Storage } from '@ionic/storage';
import { environment } from '../../environments/environment';
import { tap, catchError } from 'rxjs/operators';
import { BehaviorSubject } from 'rxjs';

const TOKEN_KEY = 'access_token';

@Injectable({
  providedIn: 'root'
})
export class AuthService {

  url = environment.url;
  user = null;
  authenticationState = new BehaviorSubject(false);

  constructor(private http: HttpClient, private helper: JwtHelperService, private storage: Storage,
    private plt: Platform, private alertController: AlertController) {
    this.plt.ready().then(() => {
      this.checkToken();
    });
  }

  checkToken() {
    this.storage.get(TOKEN_KEY).then(token => {
      if (token) {
        let decoded = this.helper.decodeToken(token);
        let isExpired = this.helper.isTokenExpired(token);

        if (!isExpired) {
          this.user = decoded;
          this.authenticationState.next(true);
        } else {
          this.storage.remove(TOKEN_KEY);
        }
      }
    });
  }

  register(credentials) {
    return this.http.post(`${this.url}/api/register`, credentials).pipe(
      catchError(e => {
        this.showAlert(e.error.msg);
        throw new Error(e);
      })
    );
  }

  login(credentials) {
    return this.http.post(`${this.url}/api/login`, credentials)
      .pipe(
        tap(res => {
          this.storage.set(TOKEN_KEY, res['token']);
          this.user = this.helper.decodeToken(res['token']);
          this.authenticationState.next(true);
        }),
        catchError(e => {
          this.showAlert(e.error.msg);
          throw new Error(e);
        })
      );
  }

  logout() {
    this.storage.remove(TOKEN_KEY).then(() => {
      this.authenticationState.next(false);
    });
  }

  getSpecialData() {
    return this.http.get(`${this.url}/api/special`).pipe(
      catchError(e => {
        let status = e.status;
        if (status === 401) {
          this.showAlert('You are not authorized for this!');
          this.logout();
        }
        throw new Error(e);
      })
    )
  }

  isAuthenticated() {
    return this.authenticationState.value;
  }

  showAlert(msg) {
    let alert = this.alertController.create({
      message: msg,
      header: 'Error',
      buttons: ['OK']
    });
    alert.then(alert => alert.present());
  }
}

It’s not yet enough because if you want to make your app more secure you have to prevent access to certain pages if the users are not logged in.

To do so we have already created a new Route Guard with the CLI in the beginning in which we can implement the canActivate function.

Inside that function we can now simply check for the current state of our authentication and later apply this guard to the routes that we want to protect! Simply open your app/services/auth-guard.service.ts and change it to:

import { Injectable } from '@angular/core';
import { CanActivate } from '@angular/router';
import { AuthService } from './auth.service';
 
@Injectable({
  providedIn: 'root'
})
export class AuthGuardService implements CanActivate {
 
  constructor(public auth: AuthService) {}
 
  canActivate(): boolean {
    return this.auth.isAuthenticated();
  }
}

Route Guards & Automatic Routing

Because we use Ionic 4 we have to talk about routing. To navigate through our app we have to define the different paths and components for them and also we want to apply the guard from before to the inside page of course.

In our case we need to wire up the login page and the inside page accordingly so open your general routing at app/app-routing.module.ts and change it to:

import { AuthGuardService } from './services/auth-guard.service';
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

const routes: Routes = [
  { path: '', redirectTo: 'login', pathMatch: 'full' },
  { path: 'login', loadChildren: './pages/login/login.module#LoginPageModule' },
  {
    path: 'inside',
    loadChildren: './pages/inside/inside.module#InsidePageModule',
    canActivate: [AuthGuardService]
  },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Now our inside route is protected using the guard and whenever a user tries to navigate to that URL, the check will be called and access is prevented if he’s not authenticated!

This is a basic example, and you can also find examples for creating a tab bar or building a side menu on the Ionic Academy for free!

To make everything a bit more comfortable for the user we can subscribe to the authentication state at the top level of our app so we immediately notice any changes and react accordingly.

This means, once we are authenticated we can automatically navigate to the inside page and for the opposite throw the user back to the login page. To do so simply change your app/app.component.ts top include the subscription check:

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

import { Platform } from '@ionic/angular';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';
import { AuthService } from './services/auth.service';
import { Router } from '@angular/router';

@Component({
  selector: 'app-root',
  templateUrl: 'app.component.html'
})
export class AppComponent {
  constructor(
    private platform: Platform,
    private splashScreen: SplashScreen,
    private statusBar: StatusBar,
    private authService: AuthService,
    private router: Router
  ) {
    this.initializeApp();
  }

  initializeApp() {
    this.platform.ready().then(() => {
      this.statusBar.styleDefault();
      this.splashScreen.hide();

      this.authService.authenticationState.subscribe(state => {
        if (state) {
          this.router.navigate(['inside']);
        } else {
          this.router.navigate(['login']);
        }
      });

    });
  }
}

Alright, all services and logic is in place, now we just need to implement the 2 pages of our app and we are done!

Building the Login

To keep things simple our login is also the register page so we can submit the same form for different actions. Because we want to use a Reactive Form we need to make sure to include it inside the module of the page, so in our case add it to the app/pages/login/login.module.ts like this:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { Routes, RouterModule } from '@angular/router';

import { IonicModule } from '@ionic/angular';

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

const routes: Routes = [
  {
    path: '',
    component: LoginPage
  }
];

@NgModule({
  imports: [
    CommonModule,
    FormsModule,
    IonicModule,
    ReactiveFormsModule,
    RouterModule.forChild(routes)
  ],
  declarations: [LoginPage]
})
export class LoginPageModule {}

Inside our page we define our form to consist of the email and password, exactly what our server from the first part expects!

Because we have implemented the important functionality in our service the onSubmit function that is called for the login only needs to call our service and wait. Remember, inside the service we sate the state after a successful login, and our app component at the top will notice the change and perform the needed routing for us!

Same counts for the register function but here we can also automatically call the login so when the user registers he will also be logged in directly afterwards which is quite common these days.

Simply change your app/pages/login/login.page.ts to this now:

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { AuthService } from '../../services/auth.service';

@Component({
  selector: 'app-login',
  templateUrl: './login.page.html',
  styleUrls: ['./login.page.scss'],
})
export class LoginPage implements OnInit {

  credentialsForm: FormGroup;

  constructor(private formBuilder: FormBuilder, private authService: AuthService) { }

  ngOnInit() {
    this.credentialsForm = this.formBuilder.group({
      email: ['', [Validators.required, Validators.email]],
      password: ['', [Validators.required, Validators.minLength(6)]]
    });
  }

  onSubmit() {
    this.authService.login(this.credentialsForm.value).subscribe();
  }

  register() {
    this.authService.register(this.credentialsForm.value).subscribe(res => {
      // Call Login to automatically login the new user
      this.authService.login(this.credentialsForm.value).subscribe();
    });
  }

}

Our view is not really spectacular, we simply need to wire up our input fields with the appropriate names from our reactive form and add a check to our buttons if the form values are actually all valid. The rules for this check have been added to the form in the step before!

Also, if you want to have another button inside your form that will not trigger the form action, simply add type="button" to have a standard button with it’s own click handler.

Open your app/pages/login/login.page.html and change it to:

<ion-header>
  <ion-toolbar color="primary">
    <ion-title>Login</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content padding>
  <form [formGroup]="credentialsForm" (ngSubmit)="onSubmit()">

    <ion-item>
      <ion-label position="floating">Email</ion-label>
      <ion-input type="email" formControlName="email"></ion-input>
    </ion-item>

    <ion-item>
      <ion-label position="floating">Password</ion-label>
      <ion-input type="password" formControlName="password"></ion-input>
    </ion-item>

    <ion-button expand="full" type="submit" [disabled]="!credentialsForm.valid">Login</ion-button>
    <ion-button expand="full" type="button" (click)="register()" [disabled]="!credentialsForm.valid">Register</ion-button>

  </form>
</ion-content>

Alright, now you can already fire up your app and see the login but we need one more thing.

Adding the Inside Page

To see if our whole authentication system actually works we need a page that is only available to users that are logged in.

On this inside page we only perform some dummy operations but they’ll help us to further understand the JWT auth concept. So our page can load the special information (adding auth headers is handled by the service/package automatically) that we have defined inside our server as a route that can only be accessed with a valid token.

The logout is pretty self explanatory, but we have another dummy function in which we can forcefully remove our JWT from the storage to see what happens afterwards. So go ahead and change your app/pages/inside/inside.page.ts to:

import { AuthService } from './../../services/auth.service';
import { Component, OnInit } from '@angular/core';
import { Storage } from '@ionic/storage';
import { ToastController } from '@ionic/angular';

@Component({
  selector: 'app-inside',
  templateUrl: './inside.page.html',
  styleUrls: ['./inside.page.scss'],
})
export class InsidePage implements OnInit {

  data = '';

  constructor(private authService: AuthService, private storage: Storage, private toastController: ToastController) { }

  ngOnInit() {
  }

  loadSpecialInfo() {
    this.authService.getSpecialData().subscribe(res => {
      this.data = res['msg'];
    });
  }

  logout() {
    this.authService.logout();
  }

  clearToken() {
    // ONLY FOR TESTING!
    this.storage.remove('access_token');

    let toast = this.toastController.create({
      message: 'JWT removed',
      duration: 3000
    });
    toast.then(toast => toast.present());
  }

}

Of course the last function is not needed for our auth system, it just shows: Once we clear the token from the storage we are still on the same page, but when we make the next request (here loading the special info again) we receive an error back from the server and because it means we are not authenticated, our service in the background will clear the token and throw us back to the login page!

To get the functions working simply add the needed buttons to your app/pages/inside/inside.page.html:

<ion-header>
  <ion-toolbar color="primary">
    <ion-title>Inside</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content padding>
  Welcome to the members only area!

  <p>{{ data }}</p>

  <ion-button expand="full" (click)="loadSpecialInfo()">Load Special Info</ion-button>
  <ion-button expand="full" (click)="logout()">Logout</ion-button>
  <ion-button expand="full" (click)="clearToken()">Clear JWT</ion-button>

</ion-content>

That’s it for the Ionic JWT app and authentication system!

Conclusion

It doesn’t really matter in which language your server is written if the system uses JWT auth, you can very easily build an Ionic app that works with it without any problems.

Of course this tutorial only shows one way to achieve the desired results, there might be many more different solutions but the concept of the JWT auth will be pretty much the same across all of them.

If you enjoyed the series, leave a comment and don’t forget to share it on your preferred social media.

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

The post JWT Authentication with Ionic & Node.js – Part 2: The Ionic App appeared first on Devdactic.

Viewing all 183 articles
Browse latest View live