Skip to content

Instantly share code, notes, and snippets.

@enricosoft
Last active February 13, 2019 16:33
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save enricosoft/793b9c4e0ba8f5aca3c197ef41986466 to your computer and use it in GitHub Desktop.
Save enricosoft/793b9c4e0ba8f5aca3c197ef41986466 to your computer and use it in GitHub Desktop.
Angular2 -Tutorial Base

QUICKSTART INTRO

Le applicazioni Angular2 sono sviluppate sul concetto di "componenti" che sono una combinazione di un template HTML ed una classe dedicata al componente. Un componente controlla una porzione della pagina.

Di seguito un esempio di implementazione di un componente:

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

@Component({
  selector: 'my-app',
  template: `<h1>Hello {{name}}</h1>`
})
export class AppComponent { name = 'Angular'; }

"my-app" sarà il nuovo DOM tag name che stiamo per creare.

Nel campo "template" c'è il codice html del template dove puoi includere anche del contenuto dinamico tramite l'utilizzo di segnaposto (eesempio la variabile "name"). Ovviamente il template può essere anche un file html esterno.

Quando esporti una classe passerai anche un dataSource (in questo caso andrà ad assegnare un valore a "name")

Utilizzo in pagina:

<my-app>Loading AppComponent content here ...</my-app>

Detto questo è giusto dire che con Angular2 puoi utilizzare sia Typescript sia Javascript ma è consigliabile utilizzare Typescript perchè si scrivere meno codice ed è più leggibile\pulito.


SETUP On Windows:

git clone https://github.com/angular/quickstart.git quickstart cd c:\ng2_quickstart\quickstart npm install npm start --> Avvia il webserver e il typescript compiler in watch mode

Il template di progetto scaricato è abbastanza "sporco" e quindi possiamo fare pulizia di alcuni files\cartelle non necessarie... per capire cosa possiamo pulire fare riferimento al file "non-essential-files.txt".


A livello di struttura di progetto:

  1. In genere i template di progetto hanno questi files di base: main.ts, app.component.ts, app.module.ts. app.component.ts --> Definisce il componente "root" che includerà tutti gli altri sub-componenti. Nel nostro esempio è "AppComponent" app.module.ts --> Definisce il modulo "root" (AppModule) che dice ad angular come assemblare l'applicazione. Nel nostro esempio viene dichiarato solo il componente "AppComponent". main.ts --> Compila l'applicazione con il JIT compiler (Just in Time) e fa il bootstrap del "AppModule" sul browser per visualizzare l'app.

  2. Files al di fuori della cartella "src/" riguardano building, deploying, e testing dell'app.

  3. Files dentro alla cartella "src/" riguardano la tua app vera e propria. I nuovi file Typescript, Html e Css per la maggiore finiranno dentro "src/app/"

  4. Node.js (v4.x.x) e npm (3.x.x) sono richiesti per far girare il tutto


Esempio di "Two-way binding":

export class Hero {
  id: number;
  name: string;
}

<input [(ngModel)]="hero.name" placeholder="name" />

Per far funzionare questa cosa è necessario importare il modulo Angular "FormsModule" in app.module.ts dentro l'array "imports" sotto BrowserModule.


Master\Detail structure (Esempi di: ngFor + evento click + ngIf + dynamic class + creazione\import di un component)

Codice della nostra Master page (Listato)

<li *ngFor="let hero of heroes" (click)="onSelect(hero)" [class.selected]="hero === selectedHero">
  <span class="badge">{{hero.id}}</span> {{hero.name}}
</li>

Dove in pratica "heroes" è una variabile typescript pubblica che contiene un array di oggetti "hero".

Al click viene richiamata la funzione "onSelect(hero)" che risiede in "AppComponent" passando la variabile "hero" che viene presa dall' ngFor.

La funzione in "AppComponent" sarà questa:

onSelect(hero: Hero): void {
  this.selectedHero = hero;
}

Sempre nell'esempio sopra andremo ad aggiungere una classe "selected" se il seguente Hero è stato selezionato.

Codice della nostra Detail page

Ora nel caso in cui avessimo una pagina di dettaglio, dobbiamo visualizzare il componente solo se è stato selezionato un Hero

<div *ngIf="selectedHero">
	....
</div>

OVVIAMENTE è possibile (e direi consigliato) dividere il codice in 2 Components: List e Detail.

Faccio un esempio su come creare il component di detail:

  1. Creare il file typescript del componente Il nome del file deve essere scritto in lower case e con parole suddivise da trattini e deve finire con ".component.ts" --> hero-detail.component.ts

  2. Scrivere il codice per il componente Il class name del componente deve essere scritto in upper camel case e devono finire con "Component" --> HeroDetailComponent Il tag name invece sempre lower case e con parole suddivise da trattini.

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

@Component({
 selector: 'hero-detail',
 templateUrl: '/app/static/js/hero-detail.component.html'
})
export class HeroDetailComponent {
}

ATTENZIONE: per poter utilizzare un template html esterno bisogna utilizzare una absolute URL! Se usi un percorso relativo Angular non trova il template. Esiste però un trucchetto per poterlo fare:

@Component({
  selector: 'hero-detail',
  moduleId: module.id,
  templateUrl: './hero-detail.component.html'
})

in pratica bisogna aggiugnere "moduleId: module.id". Angular internamente utilizzerà questo decorator di "@Component" per risolvere il percorso relativo.

CONSIGLIO: utilizzare "templateUrl" SOLO se l'html contiene molto codice! Se no preferire "template" perchè così evitiamo delle chiamate XHR per andarci a prendere l'html.

Ora è necessario aggiungere al file .ts la variabile "hero: Hero;". In pratica avremo una variabile "hero" che viene utilizzata nel nostro template html che come tipo fa riferimento alla classe "Hero" presente in hero.ts

La classe "Hero" non è altro che questo:

export class Hero {
  id: number;
  name: string;
}

Questa classe sarà ovviamente da importare nel nostro "hero-detail.component.ts" e nel nostro "app.component.ts" (dato che all'inizio era li dentro ma poi è stata portata fuori in un file .ts separato) in questo modo:

import { Hero } from './hero';

Aggiungere il componente all'html della nostra pagina:

<hero-detail [hero]="selectedHero"></hero-detail>

Input dinamico in Component Come ultima cosa, dato che il nostro detail aveva un input in binding, sarà necessario anche dichiarare questo input. Sarà quindi necessario modificare "hero-detail.component.ts" nel seguente modo:

import { Component, Input } from '@angular/core';  <-- includere Input

@Input() hero: Hero; <-- Dichiarare una proprietà input

export class HeroDetailComponent {  <-- Esportare questa proprietà input
  @Input() hero: Hero;
} 
  1. Import del componente in un Modulo Angular. Ogni componente deve essere dichiarato in un modulo Angular. Nel nostro caso lo includeremo in "app.module.ts"
import { HeroDetailComponent } from './hero-detail.component';

declarations: [
  AppComponent,
  HeroDetailComponent
],

SERVICES I services servono per richiamare i dati tramite api, socket, localStorage, mock data. Facciamo un esempio pratico basandoci sull'esempio sopra degli "Hero".

  1. Creare "hero.service.ts" che di base sarà strutturato nel seguente modo:
import { Injectable } from '@angular/core';

@Injectable()
export class HeroService {
}

Il decoratore "@Injectable()" segnala a typescript di emettere i metadata in riferimento a questo servizio. I metadata servono a specificare che questo servizio potrebbe richiedere l'inject di altre dipendenze.

  1. Esempio di service basato su mock data

Creare "mock-heroes.ts" che conterrà questo codice:

import { Hero } from './hero';
export const HEROES: Hero[] = [
  {id: 11, name: 'Mr. Nice'},
  {id: 12, name: 'Narco'},
  {id: 13, name: 'Bombasto'},
  {id: 14, name: 'Celeritas'},
  {id: 15, name: 'Magneta'},
  {id: 16, name: 'RubberMan'},
  {id: 17, name: 'Dynama'},
  {id: 18, name: 'Dr IQ'},
  {id: 19, name: 'Magma'},
  {id: 20, name: 'Tornado'}
];

Il file "hero.service.ts" diventerà:

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

import { Hero } from './hero';
import { HEROES } from './mock-heroes';

@Injectable()
export class HeroService {
  getHeroes(): Hero[] {
    return HEROES;
  }
}

Infine in "app.component.ts" dovrai scrivere il seguente codice:

heroes: Hero[]; <-- variabile contenitore

import { HeroService } from './hero.service'; <-- Import del file

constructor(private heroService: HeroService) { } <-- Inject nel costruttore

providers: [HeroService] <-- Dichiarare il provider

// HOW TO CONSUME this.heroes = this.heroService.getHeroes();

// DOVE RICHIAMARLO AppComponent (in questo caso, ma in generale tutti i componenti di riferimento) hanno il compito di recuperare e visualizzare i dati. Lo sviluppatore potrebbe essere tentato di richiamare le varie funzioni all'interno del costruttore MA in realtà il costruttore non dovrebbe contenere logiche complesse. Per questo il punto corretto dove richiamare questo tipo di funzioni è "ngOnInit" (si tratta di un lifecycle hook. Dettagli qui: https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html). Esempio:

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

export class AppComponent implements OnInit {
  ngOnInit(): void {
	this.heroes = this.heroService.getHeroes();
  }
}

L'unico "difetto" del codice scritto sopra è che è Sincrono! Ovviamente fino a che si interrogano dati di mock può andare ma con api\socket NO.

Per risolvere questo problema ci vengono incontro le "Promises". Una Promise essenzialmente non è altro la promessa che una callback verrà richiamata non appena i dati saranno pronti. Per implementarla basterà:

  1. Aggiornare il file "hero.service.ts" con
getHeroes(): Promise<Hero[]> {
  return Promise.resolve(HEROES);
}
  1. Aggiornare il file "app.component.ts" con
this.heroService.getHeroes().then(heroes => this.heroes = heroes);

ROUTING Il Routing corrisponde alla navigation. Il router è il meccanismo che permette di navigare view per view.

Il router Angualr è un modulo esterno chiamato "RouterModule" che quindi deve essere incluso.

Assicurarsi anche che nella pagina index.html sia prensente il meta tag in cima agli elementi dell' .

In "app.module.ts" per scrivere per esempio la route base per il listato degli Heros dovrai scrivere questo codice:

import { RouterModule, Routes } from '@angular/router';

const routes: Routes = [
  { path: '', redirectTo: '/heroes', pathMatch: 'full' },
  { path: 'heroes',     component: AppComponent }
  { path: 'hero/:id', component: HeroDetailComponent },  
];

@NgModule({
  imports: [ RouterModule.forRoot(routes) ],
  exports: [ RouterModule ]
})

In Routes il "component" è il componente associato alla view, mentre il "path" è l'url parziale

Fatto questo se uno digita nel browser l'url "/heroes" angular saprà che deve puntare al componente "AppComponent" ma a livello UI Angular ancora non sa dove deve visualizzare il componente grafico. Basterà quindi aggiungere il tag nel punto desiderato e angular stamperà tutti i componenti subito sotto a questo elemento. Il RouterOutlet è una direttiva inclusa nel RouterModule.

Per cambiare route invece basterà utilizzare: <a routerLink="/heroes">Heroes</a>

Per mappare la default route (homepage, corrispondente a /) si fa così:

{
  path: '',
  redirectTo: '/dashboard',
  pathMatch: 'full'
},

Route parametrizzata

In questo caso avremo una route tipo questa:

{
  path: 'detail/:id',
  component: HeroDetailComponent
},

dove ovviamentente "id" sarà il nostro parametro.

A questo punto per recuperare il valore di "id" nel nostro component (hero-detail.component.ts) dovremo fare:

import { ActivatedRoute, Params }   from '@angular/router';
import { Location }                 from '@angular/common';
import 'rxjs/add/operator/switchMap';

export class HeroDetailComponent implements OnInit {
	
	let hero: Hero;
	
	constructor(  
		private route: ActivatedRoute,
		private location: Location
	) {}
		
	ngOnInit(): void {
		this.route.params
		.switchMap((params: Params) => this.heroService.getHero(+params['id']))
		.subscribe(hero => this.hero = hero);
	}
		
}

NOTA: Rispetto agli Observable standard che necessitano di Unsubscribe (se no causano Memory Leaks), in questo caso la Subscription su "Params" non necessita di nulla perchè gestisce tutto il Router.

Per navigare una route parametrizzata si fa in questo modo:

<a [routerLink]="['/detail', hero.id]" ></a>

Da codice invece si fa:

 this.router.navigate(['/detail', this.selectedHero.id]);

Go back Esempio di torna alla view precedente...

goBack(): void {
  this.location.back();
}

Fogli di stile legati al componente

Volendo, al posto di avere un unico LESS con gli stili di tutti i components, si puà creare anche un singole less dentro alla cartella di ogni Component e legarlo a quest'ultimo in questo modo:

@Component({
  selector: 'my-heroes',
  templateUrl: './heroes.component.html',
  styleUrls: [ './heroes.component.css' ]
})

Sempre in merito all Routes, in caso di route attiva (e quindi visualizzata al momento), è possibile aggiungere automaticamente una classe css in questo modo:

<a routerLink="/heroes" routerLinkActive="active">Heroes</a>

HTTP

IL primo passo è sicuramente quello di registrare il modulo HTTP nel progetto.

In "app.module.ts" fare:

import { HttpModule }    from '@angular/http';

@NgModule({
  imports: [
    BrowserModule,    
    HttpModule,
	.....
  ],
  ...
})

** Simulate Web Api **

Invece di utilizzare delle api reali è possibile simulare il loro funzionamento utilizzando il modulo "InMemoryDataService"

// Imports for loading & configuring the in-memory web api
import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
import { InMemoryDataService }  from './in-memory-data.service';

@NgModule({
  imports: [
    BrowserModule,
    HttpModule,
    InMemoryWebApiModule.forRoot(InMemoryDataService),
	....
  ],
  ....
})

Di seguito un esempio di api mock... Creare il file "in-memory-data.service.ts"

import { InMemoryDbService } from 'angular-in-memory-web-api';
export class InMemoryDataService implements InMemoryDbService {
  createDb() {
    let heroes = [
      {id: 11, name: 'Mr. Nice'},
      {id: 12, name: 'Narco'},
      {id: 13, name: 'Bombasto'},
      {id: 14, name: 'Celeritas'},
      {id: 15, name: 'Magneta'},
      {id: 16, name: 'RubberMan'},
      {id: 17, name: 'Dynama'},
      {id: 18, name: 'Dr IQ'},
      {id: 19, name: 'Magma'},
      {id: 20, name: 'Tornado'}
    ];
    return {heroes};
  }
}

Poi modificare il file hero.service.ts in questo modo:

import { Injectable }    from '@angular/core';
import { Headers, Http } from '@angular/http';
import 'rxjs/add/operator/toPromise';
import { Hero } from './hero';


private heroesUrl = 'api/heroes';  // URL to web api

constructor(private http: Http) { }
  
getHeroes(): Promise<Hero[]> {
  return this.http.get(this.heroesUrl)
             .toPromise()
             .then(response => response.json().data as Hero[])
             .catch(this.handleError);
}

private handleError(error: any): Promise<any> {
  console.error('An error occurred', error); // for demo purposes only
  return Promise.reject(error.message || error);
}

In questo codice "http.get" ritorna un RxJS Observable. Observables sono un buon metodo per gestire i flussi asincroni di dati. ".toPromise()" invece seve a convertire un Observable in una Promise.

Nel caso in cui bisognasse richiamare un Hero specifico per id, basterà modifica la request http in questo modo:

const url = `${this.heroesUrl}/${id}`;
return this.http.get(url)....

** Esempio di Update, Create, Delete ** Sempre in "hero.service.ts":

update(hero: Hero): Promise<Hero> {
  const url = `${this.heroesUrl}/${hero.id}`;
  return this.http
    .put(url, JSON.stringify(hero), {headers: this.headers})
    .toPromise()
    .then(() => hero)
    .catch(this.handleError);
}


create(name: string): Promise<Hero> {
  return this.http
    .post(this.heroesUrl, JSON.stringify({name: name}), {headers: this.headers})
    .toPromise()
    .then(res => res.json().data as Hero)
    .catch(this.handleError);
}


delete(id: number): Promise<void> {
  const url = `${this.heroesUrl}/${id}`;
  return this.http.delete(url, {headers: this.headers})
    .toPromise()
    .then(() => null)
    .catch(this.handleError);
}

** OBSERVABLE ** Ogni Http request in Angular2 ritorna un Observable della Http response object. Un Observable è un flusso di eventi che puoi processare con un array-like operator. Angular ha un supporto basico degli Observable ma lo puoi estendere includendo la libreria RxJS (Microsoft).

Convertire un Observable in una Promise è spesso una scelta giusta perchè generalmente quando fai per esempio una http.get() lo fai per avere una risposta singola one-time e ti aspetti un solo oggetto di result. Quando ricevi la risposta sei a posto. Quindi in genere i componenti che richiamano un singolo risultato devono utilizzare delle promises.

Un observable invece va bene per esempio se devi fare richieste http multiple una dietro l'altra come per esempio un search con autocomplete. Di seguito un esempio di input di ricerca con autocomplete ed utilizzo di Observable.

Creare il componente "hero-search.component.ts".

import { Observable }        from 'rxjs/Observable';
import { Subject }           from 'rxjs/Subject';
// Observable class extensions
import 'rxjs/add/observable/of';
// Observable operators
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';


export class HeroSearchComponent implements OnInit {

  heroes: Observable<Hero[]>;
  private searchTerms = new Subject<string>();
  
  constructor(
    private heroSearchService: HeroSearchService,
    private router: Router) {}
	
  // Push a search term into the observable stream.
  search(term: string): void {
    this.searchTerms.next(term);
  }
  
  ngOnInit(): void {
    this.heroes = this.searchTerms
      .debounceTime(300)        // wait 300ms after each keystroke before considering the term
      .distinctUntilChanged()   // ignore if next search term is same as previous
      .switchMap(term => term   // switch to new observable each time the term changes
        // return the http search observable
        ? this.heroSearchService.search(term)
        // or the observable of empty heroes if there was no search term
        : Observable.of<Hero[]>([]))
      .catch(error => {
        // TODO: add real error handling
        console.log(error);
        return Observable.of<Hero[]>([]);
      });
  }

}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment