Combined Web App, Mobile App + PWA with Angular 2 and Ionic 2

I’m really interested in what is the best (current) approach to building an app in Angular 2 and Ionic 2 that provides:

  1. A Web App (i.e good experience in a desktop browser)
  2. A Mobile App (i.e in the App Store)
  3. A Progressive Web App

 

It’s obviously something other people are interested in too:

https://forum.ionicframework.com/t/using-ionic-2-app-as-hybrid-desktop-mobile-app/73283/3

https://forum.ionicframework.com/t/web-app-mobile-app-pwa-using-angular-2-ionic-2/76910

https://forum.ionicframework.com/t/web-apps-for-desktop-with-ionic-2-is-it-possible/67552

I’d be really interested in any best-practice guidance documentation on doing something like this – I’ve looked but haven’t found much.  Interestingly, Ionic themselves tweeted something this week implying help with this sort of thing is on their roadmap.

ionic-tweet

 


I’m a mobile applications developer based in the UK, concentrating primarily on hybrid application development with Ionic and Ionic 2 but also with native development skills. Please visit www.crossplatformsolutions.co.uk for more information about me and how I may be able to help you with mobile application development, particularly with Ionic 2 but also with other mobile frameworks and technologies.  Thanks for visiting.

 

 

Typescript Interfaces

One of my favourite features of Typescript is the Interfaces feature which provides us with a really easy way of type-checking our custom objects.  You can think of Interfaces as a type of contract that must be fulfilled by your code.

For example, let’s say you have an Ionic 2 component which displays some sort of membership list showing name, age and registered (Y/N).  You could come up with something like this (this is a very simplistic example with hard-coded data):

import { Component } from ‘@angular/core’;

@Component({
selector: ‘page-members’,
templateUrl: ‘members.html’
})

export class MembersPage {

members: any[];

constructor() {
this.members = [
{
name:”Sally Taylor”,
age:21,
registered:true
},
{
name:”Mike Smith”,
age:25,
registered:false
}
]
}

}

This is ok and will work but if you were to change one of those data objects so that, for example, instead of

registered: true

you had

registered: “yes”

your editor won’t complain about the error.  Even though you have supplied an invalid object, the editor does not know that each object in the array should contain a string (name), a number (age) and a boolean (registered).

This is where interfaces help you.

First, we define an interface for our object type (which we will call member)

interface member {
name:string,
age: number,
registered:boolean
}

Then we simply state that the members array must be an array of objects of type member:

members: member[];

that’s all there is to it.  So the component would then look like this>

import { Component } from ‘@angular/core’;

interface member {
name:string,
age: number,
registered:boolean
}

@Component({
selector: ‘page-members’,
templateUrl: ‘members.html’
})

export class MembersPage {

members: member[];

constructor() {
this.members = [
{
name:”Sally Taylor”,
age:21,
registered:true
},
{
name:”Mike Smith”,
age:25,
registered:false
}
]
}

}

Now if you were to change either of the objects so that they did not match the contract of name (string), age (number) and registered(boolean) your editor will produce an error.  This type of type checking can save you a lot of debugging time later on.

Usually, I put my interfaces in a separate file (in a models folder) and import them.  That way you can easily use your interfaces in multiple components.

Ionic 2 UI not updating after change to model

When I was creating my beacon-starter application that I blogged about here, I came across a problem that I had not encountered before in any of my Ionic 2 apps.

In my home.ts component I subscribe to an event which is fired when beacons are discovered.  My code was (originally):

listenToBeaconEvents() {
this.events.subscribe(‘didRangeBeaconsInRegion’, (data) => {

this.beacons = [];

let beaconList = data.beacons;
beaconList.forEach((beacon) => {
let beaconObject = new BeaconModel(beacon);
this.beacons.push(beaconObject);
});

});
}

What I expected to happen here was that my beacons array (which was represented on my template using *ngFor in the standard way) would be refreshed with the values from my beacon objects.  Unfortunately, nothing was displayed on screen despite the objects being created successfully.

What I discovered was that for the first time (for me) I needed to look at Angular Zone Change Detection which you can read about here.  In my case I needed to make three small changes to my component to get it to work:

first, import the NgZone module:

import { NgZone } from @angular/core;

second, create an NgZone:

this.zone = new NgZone({ enableLongStackTrace: false });

third, wrap my update code inside of this.zone.run() :

listenToBeaconEvents() {
this.events.subscribe(‘didRangeBeaconsInRegion’, (data) => {
this.zone.run(() => {

this.beacons = [];

let beaconList = data.beacons;
beaconList.forEach((beacon) => {
let beaconObject = new BeaconModel(beacon);
this.beacons.push(beaconObject);
});

});

});
}

Once these changes were in place the model changes were detected successfully and the UI updated.

I need to read up on Angular Zones and Change Detection I think because this was a new problem for me.  Hopefully, this may help someone else who has a similar issue.

 

 

 

 

 

 

 

 

Creating a Beacon application with Ionic 2

This is a very quick “get going with beacons and Ionic 2” post.

The code can be found in a github repository here.

First create an Ionic 2 app:

ionic start –v2 beacon-starter blank

Now, from the newly created application directory add the cordova beacon plugin:

ionic plugin add cordova-plugin-ibeacon

Next, create a provider (beacon-provider.ts) in a providers folder:

import { Injectable } from ‘@angular/core’;
import { Platform, Events } from ‘ionic-angular’;
import { IBeacon } from ‘ionic-native’;
/*
Generated class for the BeaconProvider provider.

See https://angular.io/docs/ts/latest/guide/dependency-injection.html
for more info on providers and Angular 2 DI.
*/
@Injectable()
export class BeaconProvider {

delegate: any;
region: any;

constructor(public platform: Platform, public events: Events) {
}

initialise(): any {
let promise = new Promise((resolve, reject) => {
// we need to be running on a device
if (this.platform.is(‘cordova’)) {

// Request permission to use location on iOS
IBeacon.requestAlwaysAuthorization();

// create a new delegate and register it with the native layer
this.delegate = IBeacon.Delegate();

// Subscribe to some of the delegate’s event handlers
this.delegate.didRangeBeaconsInRegion()
.subscribe(
data => {
this.events.publish(‘didRangeBeaconsInRegion’, data);
},
error => console.error()
);

// setup a beacon region – CHANGE THIS TO YOUR OWN UUID
this.region = IBeacon.BeaconRegion(‘deskBeacon’, ‘F7826DA6-4FA2-4E98-8024-BC5B71E0893E’);

// start ranging
IBeacon.startRangingBeaconsInRegion(this.region)
.then(
() => {
resolve(true);
},
error => {
console.error(‘Failed to begin monitoring: ‘, error);
resolve(false);
}
);
} else {
console.error(“This application needs to be running on a device”);
resolve(false);
}
});

return promise;
}
}

 

Now import this provider into app.module.ts and add it to the providers folder.  Your app.module.ts file should then look like this:

 

import { NgModule } from ‘@angular/core’;
import { IonicApp, IonicModule } from ‘ionic-angular’;
import { MyApp } from ‘./app.component’;
import { HomePage } from ‘../pages/home/home’;
import { BeaconProvider } from ‘../providers/beacon-provider’

@NgModule({
declarations: [
MyApp,
HomePage
],
imports: [
IonicModule.forRoot(MyApp)
],
bootstrap: [IonicApp],
entryComponents: [
MyApp,
HomePage
],
providers: [BeaconProvider]
})
export class AppModule { }

 

Next, create a model called beacon-module.ts in a models folder:

export class BeaconModel {

uuid: string;
major: number;
minor: number;
rssi: number;

constructor(public beacon: any) {
this.uuid = beacon.uuid;
this.major = beacon.major;
this.minor = beacon.minor;
this.rssi = beacon.rssi;
}
}

(we don’t really need a model for this app but creating one makes me feel more grown up)

Now change home.ts so it looks like this:

// core stuff
import { Component } from ‘@angular/core’;
import { NavController, Platform, Events } from ‘ionic-angular’;
import { NgZone } from “@angular/core”;

// plugins
import { IBeacon } from ‘ionic-native’;

// providers
import { BeaconProvider } from ‘../../providers/beacon-provider’

// models
import { BeaconModel } from ‘../../models/beacon-model’;

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

beacons: BeaconModel[] = [];
zone: any;

constructor(public navCtrl: NavController, public platform: Platform, public beaconProvider: BeaconProvider, public events: Events) {
// required for UI update
this.zone = new NgZone({ enableLongStackTrace: false });
}

ionViewDidLoad() {
this.platform.ready().then(() => {
this.beaconProvider.initialise().then((isInitialised) => {
if (isInitialised) {
this.listenToBeaconEvents();
}
});
});
}

listenToBeaconEvents() {
this.events.subscribe(‘didRangeBeaconsInRegion’, (data) => {

// update the UI with the beacon list
this.zone.run(() => {

this.beacons = [];

let beaconList = data.beacons;
beaconList.forEach((beacon) => {
let beaconObject = new BeaconModel(beacon);
this.beacons.push(beaconObject);
});

});

});
}

}

and change home.html so it looks like this:

<ion-header>
<ion-navbar>
<ion-title>
My Beacons
</ion-title>
</ion-navbar>
</ion-header>

<ion-content>
<ion-list no-lines>

<ion-item *ngFor=”let beacon of beacons”>
<ion-grid>
<ion-row>
<ion-col>Major</ion-col>
<ion-col>{{beacon.major}}</ion-col>
</ion-row>
<ion-row>
<ion-col>Minor</ion-col>
<ion-col>{{beacon.minor}}</ion-col>
</ion-row>
<ion-row>
<ion-col>RSSI</ion-col>
<ion-col>{{beacon.rssi}}</ion-col>
</ion-row>
</ion-grid>
</ion-item>

</ion-list>
</ion-content>

That’s all there is to it.  You will need to change the UUID in the beacon provider to the UUID of your own beacons.   If you run the app it should detect your beacons and display a list of them on screen (showing major, minor and RSSI):

image1-1

 

Hopefully, that will get you started!  Good luck.

 


I’m a mobile applications developer based in the UK, concentrating primarily on hybrid application development with Ionic and Ionic 2 but also with native development skills. Please visit www.crossplatformsolutions.co.uk for more information about me and how I may be able to help you with mobile application development, particularly with Ionic 2 but also with other mobile frameworks and technologies.  Thanks for visiting.

 

Calling a function in app.component.ts from another component

Someone in the Ionic Slack technical-questions channel last night asked how you call functions in app.component.ts from another Page.  I was happy to help out.

The answer (well, the way I do this anyway) is to subscribe to an event in your app.component.ts and publish that event from the page you want to call the function from.

in app.component.ts :

import { Events } from 'ionic-angular';

constructor(public events: Events) {
events.subscribe('user:login', () => {
  this.loggedIn();
});
}

loggedIn() {
console.log("logged in");
}

 

in the calling Page:

import { Events } from 'ionic-angular';
constructor(public events: Events) {
}

logIn() {
events.publish('user:login');
}

 

The calling page fires the event which is subscribed to in app.component.  See the Ionic Conference app to see an example of event handling in app.component.ts.

Objects can also be passed as parameters with events like this which can be really handy.

For more info on Events see the official Ionic documentation here.

Ionic 2 scrollToBottom issue

I have been developing a chat application recently and when I open a chat page to show a current conversation I wanted the page to automatically scroll to the bottom so the user can see the last chat message entered.  So I added a call to the scrollToBottom method in the ionViewDidEnter() lifecycle event as follows:

export class ChatPage {
  @ViewChild(Content) content: Content;

  constructor() {
  }

  ionViewDidEnter() {
    this.content.scrollToBottom();
  }
}

Unfortunately, the scroll occasionally did not execute.  It seems to work most of the time but occasionally when you opened the page it would scroll but not all the way to the bottom of the chat content.    I found that this only ever happened when the conversation contained images (photos etc that the users had uploaded as part of the chat).  Occasionally the image loading would interfere with the scroll.  I solved it simply by adding a 300 ms delay before the scrollToBottom() is executed.

 

 



I’m a mobile applications developer based in the UK, concentrating primarily on hybrid application development with Ionic and Ionic 2 but also with native development skills. Please visit www.crossplatformsolutions.co.uk for more information about me and how I may be able to help you with mobile application development, particularly with Ionic 2 but also with other mobile frameworks and technologies.  Thanks for visiting.

AngularFire2 Cannot read property ‘indexOf’ of undefined

If you are using AngularFire in your project and you are getting the error:

Cannot read property ‘indexOf’ of undefined

you may have run into the same problem I did.  I previously had Firebase installed in my project as well as AngularFire.  The new version of AngularFire (AF2) already has the Firebase module pre-installed.  So to get your project compiling you will have to remove the standalone install of Firebase.  See more here.

 

 

 



I’m a mobile applications developer based in the UK, concentrating primarily on hybrid application development with Ionic and Ionic 2 but also with native development skills. Please visit www.crossplatformsolutions.co.uk for more information about me and how I may be able to help you with mobile application development, particularly with Ionic 2 but also with other mobile frameworks and technologies.  Thanks for visiting.