Pubblicato il 16/12/2023 da alnao nella categoria Angular & Ionic

La programmazione di tutte le applicazioni web attuali si basa sul paradigma che prevede il caricamento dei dati in maniera asincrona rispetto al caricamento delle pagine e dei componenti grafici: prima si carica la pagina e poi si caricano i dati, questa tecnica permette di creare siti “apparentemente” molto veloci e dinamici slegando le componenti grafiche dalle componenti dei dati. Angular prevede questa separazione separando completamente il codice typescript e i template che rappresentano le componenti grafiche.

Nei progetti Angular oltre alla separazione classe e template si aggiunge un ulteriore tecnica: i service, si tratta di componenti con una classe specializzata: eseguire chiamate a servizi esterni o altri componenti in maniera asincrona, richiamabili dai componenti grafici con la tecnica del dependency-injection, si rimanda alla documentazione ufficiale per approfondimenti riguardo a questa tecnica.

Sempre la documentazione ufficiale descrive un semplice esempio di service, come semplice classe typescript con la annotation Injectable che esegue dei log:

@Injectable()
export class LoggerService {
  log(msg: any) { console.log(msg); }
  error(msg: any) { console.error(msg); }
  warn(msg: any) { console.warn(msg); }
}

Questa tenica permette di iniettare (traduzione del termine Inject) nel costruttore del componente grafico, richiamando successivamente il service come se fosse una proprietà dell’oggetto:

export class HeroService {
  private heroes: Hero[] = [];
  constructor(private backend: BackendService, 
    private logger: LoggerService) { }
  getHeroes() {
    this.backend.getAll(Hero).then( (heroes: Hero[]) => {
      this.logger.log(`Fetched ${heroes.length} heroes.`);
      this.heroes.push(...heroes); // fill cache
    });
    return this.heroes;
  }
}

Angular suggerisce di distingue i componenti template dai servizi per aumentare la modularità, efficienza e la riusabilità, cercando di definire applicazioni che rispettano il paradigma MVC.


La tecnica dei service è combinata con le tecniche di asincronia per creare componenti dinamici e veloci, principalmente esistono due tipi di tecniche per gestire i servizi asincroni:

  • una Promise è la gestione di un evento di tipo non sincrono, nel codice si invia una richiesta e si definisce il comportamento alla risposta e la gestione dell’errore.
    un Observable è una sequenza di eventi inviato ad una funzione di callback simile al concetto di Stream

da notare che questa distinzione spesso viene fraintesa o completamente dimenticata dai programmatori che “troppo” spesso le confondono.

Un semplice esempio di utilizzo della tecnica Promise è la classica chiamata ad un servizio API rest in maniera asincrona, qui viene richiamato il servizio Wiki per recuperare

import { Injectable } from '@angular/core';
import { URLSearchParams, Jsonp } from '@angular/http';
@Injectable()
export class WikipediaService {
  constructor(private jsonp: Jsonp) {}
  search (term: string) {
    var search = new URLSearchParams()
    let endoint="http://en.wikipedia.org/w/api.php?callback=JSONP_CALLBACK";
    search.set('action', 'opensearch');
    search.set('search', term);
    search.set('format', 'json');
    return this.jsonp.get(endpoint, { search }).toPromise()
      .then((response) => response.json()[1]);
  }
}

e il componente può richiamare il service indicando un metodo per la gestione dell’esito con “then” e la gestione di un eventuale errore, per esempio:

@Component({
selector: 'my-app',
template: `
  <div>
    <h2>Wikipedia Search</h2>
     <input #term type="text" (keyup)="search(term.value)">
    <ul><li *ngFor="let item of items">{{item}}</li></ul>
  </div>
`
})
export class AppComponent {
  items: Array<string>;
  constructor(private wikipediaService: WikipediaService) {}
  search(term) {
    this.wikipediaService.search(term)
    .then(items => this.items = items)
    .error(mes => console.log(mes) );
  }
}

maggior dettagli di questo esempio possono essere trovati alla documentazione. Ovviamente il metodo definite di callBack non è necessariamente una sola istruzione ma può essere un blocco di codice complesso, per esempio un metodo per scaricare un file di testo ritornato da una API

downloadFile(e : any, el: any){ 
  axios.get(this.urlLista +"?fileName="+el.fileName,this.getConfigHttp()
  ).then( (response:any) => {
    const lista : any[]= response.data
    const lista2 : any[]=[]
    lista.forEach(element => {
      lista2.push(element + this.stringaAcapo);
    });
    var blob = new Blob(lista2, {type: 'text/txt' })
    const url = window.URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = el.fileName +".csv";
    a.click();
    window.URL.revokeObjectURL(url);
    a.remove();
  }).catch( e => { 
      console.log(e); alert("Impossibile scaricare il file"); 
  });
}

Il framework Angular mette disposizione dei programmatori un operatore specifico per la gestione degli oggetti asincroni: se un oggetto viene definito come asincrono

greeting: Promise<string>|null = null;

se questo venisse usato nei template senza specifiche, verrebbe generato un errore in fase di esecuzione perchè l’oggetto non sarebbe valorizzato quindi è stato introdotto il AsyncPipe, nella documentazione ufficiale viene indicato come il gestore di valori asincroni di un Observable o una Promise e restituisce l’ultimo valore emesso: quando viene emesso un nuovo valore, viene assegnato il nuovo valore nel template mentre quando il componente viene distrutto, la pipe asincrona annulla automaticamente la sottoscrizione per evitare potenziali perdite di memoria.

Tecnicamente questa è una comune pipe già descritta in un articolo specifico:

<span>Wait for it... {{ greeting | async }}</span>


Un semplice esempio di Observable è l’uso di setInterval e l’uso di next per la gestione dello “stream” di tipo Observable:

@Component({
selector: 'async-observable-pipe',
template: '<div><code>observable|async</code>: Time: {{ time$ | async }}</div>'
})
export class AsyncObservablePipeComponent {
  time$ = new Observable<string>((observer: Observer<string>) => {
    setInterval(() => observer.next(new Date().toString()), 1000);
  });
}

La documentazione ufficiale consiglia che le proprietà di questo tipo abbiano il simbolo $ prima o dopo il nome così da poterle identificare velocemente. Per ogni oggetto di questo tipo si possono/devono gestire tre metodi:

numbers$.subscribe({
  next: value => console.log('Observable emitted the next value: ' + value),
  error: err => console.error('Observable emitted an error: ' + err),
  complete: () => console.log('Observable emitted the complete notification')
});

i cui nomi sono molto esplicativi del loro utilizzo. Si rimanda alla documentazione ufficiale per maggiori dettagli riguardo alla libreria RxJS e agli EventEmitter che sono sistemi di gestione avanzati di servizi asincroni.


Sempre la sotto-libreria RxJS mette a disposizione un metodo specifico forkJoin che permette di chiamare più sevice di tipo Obserable in parallelo e viene eseguito un blocco di codice di rollback quando tutti gli obsersable sono completate, la definizione del metodo è

export declare function forkJoin<A, B, C>(
  sources: [ObservableInput<A>, ObservableInput<B>, ObservableInput<C>]
): Observable<[A, B, C]>;

cioè questo metodo accetta più Obserable in input e ritorna un unico oggetto di tipo Obserable, in questo modo è possibile gestire il parallismo delle chiamate a service. Un esempio di utilizzo di questa tecnica è:

const $flussi=this.elencoService.get("flussi");
const $out=this.elencoService.getStaging("outgoing");
const $in=this.elencoService.getStaging( "incoming")
forkJoin([$flussi, $out,$in]).subscribe(([returnFlussi, returnOut, returnIn]) => {
  console.log("Risposta di tutti forkJoin");
  this.caricaEsiti(returnFlussi, returnOut, returnIn);
},error => {alert("Errore");console.log(error);});
MENU