In 21 Schritten mit Angular 2 und NativeScript zur Native mobile App
Schritt 1: Ein neues NativeScript-Angular-Projekt anlegen
tns create wd-project --ngJede gewünschte Build-Plattform muss in dem Projekt über eine Befehlszeile wie
tns platform add iosergänzt werden. Nur Entwickler die unter Mac OS X arbeiten, können iOS als Build-Plattform nutzen.
tns platform add android
Schritt 2: Die Angular 2-HTTP-Komponente importieren
Das Projekt nutzt Remote-Data und benötigt daher die Angular 2-HTTP-Komponente [4] für den Zugriff auf RESTful-API-Endpunkte. Dazu ist das File app/main.ts zu öffnen und darüber erfolgt dann der Import des NativeScriptHttpModule, etwa so:import {Nach dem Import lässt sich das Module in die imports section des @NgModule block injizieren:
NativeScriptHttpModule
} from "nativescript-angular/http";
imports: [
NativeScriptModule,
NativeScriptHttpModule
]
Schritt 3: Die Pokémon Sprites für die Bildschirmausgabe abrufen
Schritt 4: Die TypeScript Component Foundation hinzufügen
Der größte Teil der TypeScript-Logik befindet im Projekt-File app/app.component.ts. Die Klasse muss restrukturiert werden, damit sie so aussieht:export class AppComponentZu beachten ist die OnInit-Implementierung. Das Interface benötigt eine ngOnInit-Methode, die nach der constructor-Methode ausgeführt wird. Es wird so importiert:
implements OnInit {
constructor() { }
ngOnInit() { }
showInformation(index: number) { }
showDialog(data: Array<any>) { }
}
import {
Component,
OnInit?
} from "@angular/core";
Schritt 5: Eine Liste der verfügbaren Pokémons einholen
Um die Liste der verfügbaren Pokémons einzuholen, muss die Angular 2-HTTP-Komponente injiziert werden. Der Import erfolgt wie folgt:
import { Http } from "@angular/http"; import "rxjs/Rx";
Anschließend kann das Ergebnis in die Methode constructor injiziert werden:
constructor(private http: Http) { }
Zur Speicherung der API-Daten wird ein Public Array benötigt, das beispielsweise so aussieht und mit der Methode constructor initialisiert wird:
public pokemon: Array<any>;
Abschließend sind die Daten in die Methode ngOnInit zu laden:
ngOnInit() { this.http.get("ht tps://pokeapi.co/api/v2/pokemon?limit=151") .map(result => result.json()) .flatMap(result => result.results) .subscribe(result => { this.pokemon.push (result); }, error => { console.error(error); }); }
Das Ergebnis des asynchronen HTTP-Requests wird in dem zuvor angelegten Public Array gespeichert. Die Angular 2-HTTP-Komponente [4] nutzt in großem Umfang RxJS-Technologie [6], wie sie auch der Streaming-Dienst Netflix einsetzt. RxJS ermöglicht die reaktive Programmierung mit JavaScript und Angular 2.
Schritt 6: Die UI-Grundlage aufbauen
Da die Applikation im Grunde genommen aus einer Liste besteht, ist es wichtig, deren Entstehung nachvollziehen zu können. Man nehme zum Beispiel das folgende XML-Markup:
<ActionBar title="WD NativeScript App"></ActionBar> <StackLayout> </StackLayout>
Das Ergebnis ist eine Action-Bar – manchmal auch als Navigation-Bar bezeichnet – und ein vertikales, sogenanntes Stack-Layout [7].
Schritt 7: Das Konzept des NativeScript GridLayout verstehen
<GridLayoutDamit wird eine Tabelle erzeugt, bei der jede Zeile nur so viel Platz belegt, wie sie tatsächlich benötigt. Bezüglich der Spalten, erhalten die erste und die dritte Spalte den erforderlichen Platz und die verbleibende Nutzfläche wird der zweiten Spalte zugewiesen.
rows="auto"
columns="auto * auto">
</GridLayout>
Die Anordnung der Spalten und Zeilen lässt sich über jede UI-Komponente vornehmen, etwa so:
<Label
text="Hello World"
row="0"?
col="1">
</Label>
Schritt 8: Die Pokémon-Daten in einer Liste ausgeben
Mit den Kenntnissen über den Aufbau des GridLayout lässt sich ein List View erzeugen. Dazu dient das folgende Markup im StackLayout:<ListViewDie Listenelemente stammen aus dem Public Array, das die Pokémon-Daten enthält. Jedes Element wird in einem monster-object gespeichert und der Index aufgezeichnet. Das GridLayout – es ist das gleiche wie in Schritt 7 – residiert im template. Die erste Spalte sieht so aus:
[items]="pokemon">
<template
let-monster="item"
let-index="index">
</template>
</ListView>
<Label?Die zweite Spalte enthält den Pokémon-Namen:
text="{{index + 1}}."
row="0"
col="0"
marginRight="10">
</Label>
<LabelDer nächste Schritt enthält Anmerkungen zur dritten Spalte.
[text]="monster.name"
row="0"
col="1">?
</Label>
Schritt 9: Die Sprite-Daten laden
<ImageDa die Sprite-Daten im app/images-Directory abgelegt wurden, ist über die Indexnummer ein Zugriff auf die Images möglich. Jedes Image ist ein numerischer Wert, der in einem Indexwert abgebildet ist, der wiederum die Pokémon-Nummer repräsentiert. Damit ist auch die dritte Spalte in der Listenzeile beschrieben.
src="~/images/{{index + 1}}.png"
row="0"
col="2">
</Image>
Schritt 10: Den List View mit CSS gestalten
Standardmäßig sieht die Liste nicht sehr attraktiv aus und sollte daher mit CSS aufbereitet werden. Im File app/app.css kommen daher folgende Zeilen hinzu:.pokemon-number {Die Pokémon-Nummer wird damit fett dargestellt.
font-weight: bold;
}
.pokemon-name {Das API liefert die Pokémon-Namen in Kleinbuchstaben. Die zuletzt aufgeführte Klasse wandelt das erste Zeichen in einen Großbuchstaben um. Zudem können diese Klassen ähnlich wie in Standard-HTML den <Label>-Elementen hinzugefügt werden.
text-transform: capitalize;
}
Schritt 11: Die Images mit CSS Keyframes animieren
@keyframes poke-img {Mit diesen Anweisungen wird das Element um 360 Grad gedreht und gleichzeitig eingeblendet.
from {
opacity: 0;
transform: rotate(0deg);
}
to {
opacity: 1;
transform: rotate(360deg);
}
}
.pokemon-image {Diese Klasse wird auf die <Image>-Komponente angewendet. Sie definiert die Animation und wie lange sie läuft. CSS Keyframes sind jedoch nicht die einzige Möglichkeit zur Animation in NativeScript. Angular 2 verfügt über ein eigenes Animation-Framework [8], mit dem Entwickler vergleichbare Ergebnisse erzielen können.
animation-name: poke-img;
animation-duration: 1s;
animation-delay: 1s;
opacity: 0;
}
Schritt 12: Die Listenelemente um Click Events erweitern
Im Schritt 4 wurde mit dem TypeScript-File die showInformation-Methode eingeführt. Sie wird jetzt über die Benutzeroberfläche aufgerufen. Tap-Events – manchmal auch als Click-Events bezeichnet – lassen sich auf UI-Elemente anwenden. So ist es beispielsweise sinnvoll, diese Events mit den Elementen der Benutzeroberfläche des GridLayouts einzusetzen:<GridLayoutDas Tap-Attribut ruft die Funktion direkt auf. Da es kein Pokémon mit der Nummer Null gibt, muss jedem Index eine Eins hinzugefügt werden.
rows="auto"
columns="auto * auto"
(tap)="showInformation(index+1)">
Schritt 13: Informationen zu einem speziellen Pokémon erfassen
Im Schritt 2 wurde der Zugriff auf Pokémon RESTful APIs erwähnt, um eine Liste aller verfügbaren Pokémons zu erhalten. Jetzt wird der Zugriff auf ein bestimmtes Pokémon benötigt und dazu dessen Nummer verwendet. In der showInformation-Methode finden sich folgende Zeilen:
this.http.get("https: //pokeapi.co/api/v2/pokemon/" + index) .map(result => result.json()) .flatMap(result => result.types) .map(result => (<any> result).type.name ) .toArray() .subscribe(result => { this.showDialog(result); });
Das Resultat dieser asynchronen Abfrage wird in ein Array von Pokémeon-Attributen konvertiert und an die showDialog-Methode weitergeleitet, die das Resultat auf einem Bildschirm darstellt.
Schritt 14: Native-Alert-Dialoge auf Anfrage anzeigen
Um Informationen in einem Dialog anzeigen zu können, muss die benötigte Komponente zunächst in das TypeScript-File importiert werden.
import dialogs = require("ui/dialogs");
Ist die Komponente importiert, sieht die showDialog-Methode etwa so aus:
dialogs.alert({ title: "Information", message: "Pokemon of type(s) " + data.join(", "), okButtonText: "OK" });
Die Methode nimmt das durch RxJS in der showInformation-Methode konstruierte Array und zeigt die Meldung – nach der Umwandlung des Arrays in einen String – an. Die Buttons können bei Bedarf weiter angepasst werden oder es können weitere Buttons für unterschiedliche Zwecke hinzukommen.
Schritt 15: Ein Native Platform Plugin hinzufügen
tns plugin add nativescript-couchbaseGenerische Plugins mit TypeScript-Definitionen sind etwa für autocomplete verfügbar und können mit folgender Zeile in das references.d.ts-File des Projekts eingefügt werden.
/// <reference path="./node_modules/nativescript-couchbase/couchbase.d.ts" />Diese Zeile ergänzt die Type-Definitionen des Couchbase-Plugins. Nativer Android- oder iOS-Code, der als Teil eines SDKs mit Java, Objective-C oder Swift erstellt wurde, kann zusammen mit NativeScript eingesetzt werden; die nativen APIs sind via JavaScript zugänglich. Damit ist der Weg frei zu einem großen Native-Plugin-Ökosystem, ohne dass Entwickler die nativen Sprachen beherrschen müssten. Wird beispielsweise Barcode-Scanning in einer Applikation benötigt, lässt sich die bekannte ZXing Library for iOS and Android in eine NativeScript-Anwendung einbinden. Darüber hinaus können Entwickler JavaScript Libraries, die eigentlich für Web-Applikationen gedacht sind, auch in NativeScript-Anwendungen einsetzen, ohne dass sie das Rad neu erfinden müssen.
Schritt 16: Den NoSQL Database Provider vorbereiten
Alle Datenbank-Interaktionen sollten mit einem einzelnen File, bekannt als Angular 2 Provider, erfolgen. Dazu sollte im Projekt ein app/database.ts-File mit diesen Programmzeilen angelegt werden:export class Database {Der Code erzeugt und öffnet eine lokale Datenbank und retourniert eine Kopie der Datenbank zur weiteren Bearbeitung. Das Couchbase-Plugin wird so importiert:
private db: any;
constructor() {
this.db = new Couchbase("db");
}
getDatabase() {
return this.db;
}
}
import {Als nächstes wird die Datenbank in den constructor des Projekt-Files app/app.component.ts injiziert:
Couchbase
} from 'nativescript-couchbase';
constructor(private database: DatabaseAbschließend muss die Datenbank-Komponente in die @Component des Provider Arrays eingefügt werden:
providers: [Database]
Schritt 17: Die Pokémon-API-Daten zwischenspeichern
.subscribe(result => {Die Daten werden sowohl gespeichert als auch in das Array gepusht. Durch diesen Caching-Schritt können alle Prozesse normal weiterlaufen.
this.database
.getDatabase()
.createDocument(result);
this.pokemon.push(result);
}
Schritt 18: Einen NoSQL MapReduce View erzeugen
Beim Caching geht es darum, die gespeicherten Daten abfragen zu können, anstatt einen HTTP-Request abzusetzen. In der constructor-Methode des Files kommen daher diese Zeilen hinzu:this.db.createView(In diesem View namens Pokémon, wird ein Key-Value-Paar aller Dokumente in der Datenbank ausgegeben. Sollten mehr Datentypen in der Datenbank vorliegen, lässt sich die Emitter-Funktion leicht erweitern.
"pokemon",
"1",
(document, emitter) => {
emitter.emit(document._id, document);
}
);
Schritt 19: Gecachte Daten abfragen
let rows = this.databaseWenn rows weniger als eins ergibt, ist der Cache leer und es muss ein Request ausgeführt werden. Ist der Cache nicht leer, kann eine Liste der gespeicherten Daten geladen werden.
.getDatabase()
.executeQuery("pokemon");
if(rows.length < 1) {Falls sich keine Daten im Cache befinden, werden auf jeden Fall die HTTP-Requests ausgeführt.
// Do previous HTTP
} else {
for(let i = 0; i < rows.length; i++) {
this.pokemon.push(rows[i]);
}
}
Schritt 20: Die iOS App Transport Security Policies anpassen
Apple hat mit iOS 9 die App Transport Security (ATS) Policies eingeführt [10]. Damit entstand eine Blacklist mit unsicheren Services. Zur Kommunikation mit HTTP- und nicht mit HTTPS-Endpunkten, sind diese Befehlszeilen in das Projekt-File app/App_Resources/iOS/Info.plist einzufügen:<key>NSAppTransportSecurity</key>Damit kommen alle unsicheren Endpunkte auf eine Blacklist. Auf der Whitelist sind nur solche, die tatsächlich benötigt werden.
<dict>
<key>NSAllowsArbitraryLoads</key>
<true />
</dict>
Schritt 21: Die Applikation mit einem Gerät oder per Simulation testen
Nachdem alle Arbeiten abgeschlossen sind, kann das Ergebnis per Simulation oder einem physikalischen Gerät getestet werde. Von der Kommandozeile aus wird dabei dieser Befehl ausgeführt:tns emulate [platform]Abhängig davon, mit welchem Simulator getestet werden soll, wird [platform] durch ios oder android ersetzt. Um mit Android zu testen, muss das Android SDK installiert und eingerichtet sein. Für den Test mit iOS wird Mac OS X mit X Code benötigt.