Web/Mobile/ Progressive
App Development - Part 3
ECMAscript 6 & Typescript
Super set di JavaScript, contiene funzionalità già proprie di ES6, con la possibilità di aggiungere variabili tipizzate.
Angular 2+
Angular è un framework per lo sviluppo di web app, basato sulla logica a componenti.
Creato in collaborazione tra Google e Microsoft.
Apache Cordova & IonicFramework 3+
Ionic Framework è un framework per la creazione di interfacce mobile/desktop, basato su TS / ng2+. Con l'aiuto di Apache Cordova, andremo a creare un app mobile ibrida.
App con i tabs
Ionic Templates
// tabs.html
<ion-tabs>
<ion-tab-bar slot="bottom">
<ion-tab-button tab="tab1">
<ion-icon name="flash"></ion-icon>
<ion-label>Tab One</ion-label>
</ion-tab-button>
</ion-tab-bar>
</ion-tabs>
// tabs-routing.ts
// Imports
const routes: Routes = [{
path: 'tabs',
component: TabsPage,
children: [
{
path: 'tab1',
children: [
{
path: '',
loadChildren: () => import('../tab1/tab1.module').then(m => m.Tab1PageModule)
}
]
},
]
}];
Ionic ci mette a disposizione anche una serie di template già pronti all'uso, quelli che useremo di più sono i tabs e il sidemenu.
Caricare dati con il servizio HTTP
ionic generate service planet
// planet.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { environment } from '../environments/environment';
import { Planet } from '../models/planet';
import { map } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class PlanetService {
public data: any = null;
constructor(public http: HttpClient) {
console.log('Hello Planet Service');
}
public getPlanets() {
return this.http
.get<Planet[]>(`${environment.apiUrl}/planets/?format=json`, { } )
.pipe( map( response => {
// @ts-ignore
if ( response ) {
// @ts-ignore
response.results.forEach( (el) => {
let url = el.url.replace('https://swapi.co/api/planets/', '');
url = url.replace('/', '');
el.id = url;
});
// @ts-ignore
return response.results;
}
}));
}
}
Creiamo il modello planet.ts
// app/models/planet.ts
export interface Planet {
id?: number;
name: string;
rotation_period: number;
orbital_period: number;
diameter: number;
climate: string;
gravity: string;
terrain: string;
surface_water: number;
population: number;
residents: any;
films: any;
created: string;
edited: string;
url: string;
}
Modifichiamo i tabs
// tabs.html
<ion-tab-button tab="tab1">
<ion-icon name="planet"></ion-icon>
<ion-label>Pianeti</ion-label>
</ion-tab-button>
// Modifichiamo tab1.ts
import { Component, OnInit } from '@angular/core';
import { Planet } from '../../models/planet';
import { PlanetService } from '../../services/planet.service';
@Component({
selector: 'app-tab1',
templateUrl: 'tab1.page.html',
styleUrls: ['tab1.page.scss']
})
export class Tab1Page implements OnInit {
public planets: Planet[] = [];
constructor(
private planetService: PlanetService
) { }
public ngOnInit() {
this.planetService
.getPlanets()
.subscribe( result => {
this.planets = result;
});
}
}
Rinominiamo il tab.
Il template di tab1.html
// tab1.html
<ion-header>
<ion-toolbar>
<ion-title>
Pianeti
</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>
<ion-item *ngFor="let planet of planets"
[href]="'/tabs/tab1/planet-detail/' + planet.id" [detail]="true">
<h2>{{ planet.name }}</h2>
</ion-item>
</ion-list>
</ion-content>
Aggiungiamo la pagina di dettaglio del pianeta.
ionic generate page PlanetDetail
// planet-detais.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Planet } from '../../models/planet';
import { PlanetService } from '../../services/planet.service';
@Component({
selector: 'app-planet-detail',
templateUrl: './planet-detail.page.html',
styleUrls: ['./planet-detail.page.scss'],
})
export class PlanetDetailPage implements OnInit {
planetId: number;
planet: Planet;
constructor(
private route: ActivatedRoute,
private planetService: PlanetService
) { }
ngOnInit() {
this.route.params.subscribe( d => {
this.planetId = d.planetId;
this.planetService.getPlanet( this.planetId )
.subscribe( d => { this.planet = d });
});
}
}
Template della pagina di dettaglio del pianeta.
<ion-header>
<ion-toolbar>
<ion-title>Dettaglio Pianeta</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-card>
<ion-card-header>
<ion-card-subtitle>{{ planet?.name }}</ion-card-subtitle>
</ion-card-header>
<ion-card-content>
{{ planet | json }}
</ion-card-content>
</ion-card>
</ion-content>
Aggiungiamo la ricerca
// tab1.html
<ion-searchbar [(ngModel)]="searchQuery"
showCancelButton="focus" cancelButtonText="Resetta"
(ionInput)="searchPlanets($event)"
(ionClear)="getPlanets()"></ion-searchbar>
// tab1.ts
export class Tab1Page {
public searchQuery: string;
public ngOnInit() {
this.searchQuery = '';
this.getPlanets();
}
public getPlanets() {
this.planetService
.getPlanets()
.subscribe( result => {
this.planets = result;
});
}
public searchPlanets(event) {
// la ricerca prende il valore dalla search bar
const queryString = event.target.value;
if ( queryString !== undefined ) {
// se il valore è vuoto, non fare la ricerca
if ( queryString.trim() === '' ) {
return;
}
this.planetService
.searchPlanets( queryString )
.subscribe( result => {
this.planets = result;
});
}
}
}
Modifichiamo il service
// planet.service.ts
export class PlanetService {
[...]
public searchPlanets(queryString) {
return this.http
.get<Planet[]>(`${environment.apiUrl}/planets/?search=${queryString}&format=json`, { } )
.pipe( map( response => {
// @ts-ignore
if ( response ) {
response = this._parseResults( response );
// @ts-ignore
return response.results;
}
}));
}
}
Virtual Scrolling
// tab1.html
<ion-virtual-scroll [items]="planets" approxItemHeight="60px">
<ion-item *virtualItem="let planet; let planetBounds = bounds;">
[...]
</ion-item>
</ion-virtual-scroll>
Uno dei problemi di performance che si verificano spesso, è dovuto a lunghe liste di contenuti (non è il caso dei pianeti in questione), ma quando i contenuti superano i 400, inizieremo a vedere rallentamenti e strani comportamenti.
Ci viene in aiuto il Virtual Scrolling, che rendererizza (cioè crea elementi html) solo degli elementi che dobbiamo visualizzare. Vediamo come si fa.
Titoli liste
// tab1.html
<ion-virtual-scroll [items]="planets" approxItemHeight="60px" [headerFn]="customHeaderFn">
<ion-item-divider *virtualHeader="let header">
{{ header }}
</ion-item-divider>
[...]
</ion-virtual-scroll>
// tab1.ts
public customHeaderFn(record, recordIndex, records) {
if ( recordIndex > 0 ) {
if ( record.name.charAt(0) !== records[recordIndex - 1].name.charAt(0) ) {
return record.name.charAt(0);
} else {
return null;
}
} else {
return record.name.charAt(0);
}
}
Il virtual scrolling permette inoltre di utilizzare header e footer dinamici.
Useremo il titolo separatore mostrando la prima lettera in ordine alfabetico per separare.
Debug & Testing
Chrome Inspector
Lo strumento DevTools di Chrome è il primo strumento che useremo per fare sviluppo e testing di quello che stiamo facendo.
https://github.com/FMRiggio/ionicWars
https://www.dev2qa.com/how-to-set-android-sdk-path-in-windows-and-mac/
Web/Mobile/Progressive Web App Development - Part 3
By Filippo Matteo Riggio
Web/Mobile/Progressive Web App Development - Part 3
- 383