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/

 

 

 

Made with Slides.com