Angular è un framework open source per lo sviluppo di applicazioni web con licenza, è sviluppato e mantenuto principalmente da Google, proviene da quello che si chiamava AngularJS ma a differenza del suo predecessore, non si basa su Javascript ma sul linguaggio TypeScript. Questa tecnologia si pone come sostituto di tutte le librerie Javascript e tutti i framework come jQuery, infatti bisogna sempre tenere a mente una cosa: Angular è una piattaforma che impone regole molto rigide a differenza di Javascript e tutte le librerie collegate che sono state rilasciate negli anni.

Nei progetti sviluppati con Angular, e quindi con TypeScript, bisogna sempre tener conto di alcuni aspetti:

  • Ordine: viene ridefinito il concetto di Sito Web come insieme di files (HTML, CSS e JS) posti nella stessa cartella, con Angular è indispensabile avere cartelle specifiche e file specifici, con una gerarchia ben definita con import e collegamenti, un file al posto sbagliato non viene considerato
  • Componenti: viene eliminato il concetto di Pagina Web corrispondente ad un file HTML (o JSP/PHP/ASPX) ma tutto il progetto è un componente che contiene tanti sotto-componenti, ovviamente rispettando la gerarchia del punto precedente
  • Script: viene sostituito il linguaggio Javascipt (NON compilato) con TypeScript che viene verificato e compilato nell'ambiente di sviluppo
  • Server: nonostante sia tutto codice front-end c'è bisogno di un WebServer (IIS, Apache, Tomcat, Wildfly) per l'esecuzione dei Typescript
  • CSS: uso dei CSS in maniera stretta e nel rispetto delle regole CSS3 a differenza di molti siti in HTML puro
  • Tag: nuove proprietà dei Tag HTML in modo da creare "relazioni" e "corrispondenza" tra oggetto DOM e oggetti TypeScript, superando il concetto Javascript di recupero del DOM dalla proprietà id (come con getElementById o le istruzioni jQuery)

Si da per scontato che chi inizia a lavorare con Angular conosca bene gli standard HTML5, CSS 3, Bootstrap e Javascipt; sarebbe inutile iniziare a sviluppare con Angular senza conoscere questi standard, e bisogna sempre prestare attenzione che non basta conoscere HTML base ma servono le conoscenze dettagliate delle ultime versioni di HTML e CSS. Importante distinguere la differenza tra AngularJS e Angular (a volte seguito da un numero che identifica la versione), i due framework sono simili ma si basano su due linguaggi diversi: il primo si basa su Javascript mentre il secondo su Typescript. In questa serie di articoli si farà riferimento solo ad Angular alla versione 7 o successiva (dalla primavera del 2019 in poi).

JSON, acronimo di JavaScript Object Notation, è un formato adatto all'interscambio di dati fra applicazioni client/server e in particolare nelle applicazioni basate su Javascript e Typescript come le applicazioni web sviluppate con Angular. I tipi di dati supportati da questo formato sono:

  • booleani : che possono valere true/false
  • interi: numero in virgola mobile
  • string: stringhe racchiuse da doppi apici (");
  • array: sequenze ordinate di valori, separati da virgole e racchiusi in parentesi quadre [];
  • array associativi: sequenze coppie chiave-valore separate da virgole racchiuse in parentesi graffe;
  • null: valore null

Tutti gli oggetti json sono identificati con il content-type "Content-Type: application/json" e sono racchiusi da parentesi graffe {}, elencano le proprietà separate da virgole e (convenzionalmente) ogni proprietà è su una riga. Un semplice esempio di oggetto JSON:

{
"nome": "Mario",
"cognome": "Rossi",
"attivo": true,
"eta": 42,
"compleanno": {
"day": 12,
"month": 4,
"year": 2019
},
"lingue": [ "it", "en" ],
moglie: null
}

che corrisponde all'XML:

<root>
<nome>Mario</nome>
<cognome>Rossi</cognome>
<attivo>true</attivo>
<eta>42</eta>
<compleanno>
<day>12</day>
<month>4</month>
<year>2019</year>
</compleanno>
<lingue>it</lingue>
<lingue>en</lingue>
<moglie />
</root>

Il linguaggio Typescript si basa interamente su Json e tutti i dati sono rappresentati in questo modo, esistono tuttavia librerie che possono trasformare oggetti di altro tipo, come XML, in Json ma sono sconsigliate.

TypeScript è un linguaggio di programmazione open source inizialmente sviluppato da Microsoft e si tratta di una evoluzione di JavaScript, con la doppia compatibilità: qualsiasi codice JavaScript è in grado di funzionare con TypeScript e qualsiasi codice TypeScript può essere compilato/convertito in JavaScript. Questo secondo caso è il sistema usato da Angular per generare i progetti: il codice scritto con Typescript viene convertito in JavaScript dal compilatore che i browser riescono ad interpretare senza problemi. Rispetto al Javascipt classico, Typescript aggiunge i concetti di

  • Classi e Interfacce come in java, in javascript già presenti ma usate molto poco
  • Moduli da interpretare come i package java
  • L'operatore => usato come in C++ per accedere alle proprietà e ai metodi di oggetti
  • Tipi di dati (quasi) sempre obbligatori come in java a differenza del javascript
  • L'uso del carattere : per separare variabile dal tipo "variabile : tipo"

Un esempio di codice è:

function add(left: number, right: number): number {
return left + right;
}

dove la funzione viene definita alla javascript (con il function), con il nome e due parametri in ingresso e il tipo di ritorno è messo dopo i parametri sempre con l'operatore duepunti. Un esempio di classe è:

export class Persona {
private nome: string;
private eta: number;
constructor(nome: string, eta: number) {
//il costruttore è definito come costructor e non ha (quasi) mai ritorno
this.nome = nome;//è obbligatorio l'uso del this per accedere alle proprietà
this.eta = eta;
}
toString(): string {
return this.nome + " (" + this.eta + ")";
//come in Javascipt è possibile concatenare stringhe con +
}
confontaEta(parametro: int) : boolean{
return parametro==this.eta;
}
isMario() : boolean{
if (this.nome=="Mario"){
//come in javascript i confronti tra stringhe si eseguono con ==
//a differenza di java dove si usa .compare
return true;
}else{
return false;
}
}
}

A differenza di Java, all'interno di un file Typescript possono essere scritte più classi, questa tecnica è sconsigliata quasi sempre tranne nei casi di oggetti Model nidificati, per esempio in caso di Json a più livelli, un esempio di file:

export class Persona {
private nome: string;
private eta: number;
constructor(nome: string, eta: number) {
//il costruttore è definito come costructor e non ha ritorno
this.nome = nome;
this.eta = eta;
}
toString(): string {
return this.nome + " (" + this.eta + ")";
}
...
}
export class ClasseScolastica{
private studenti: Persona[];
private insegnanti: Persona[];
private nomeClasse: String;
...
}

NodeJs è il runtime JavaScript costruito sul motore JavaScript V8 di Chrome ed è progettato per creare applicazione di rete "scalabili", usa un [event loop] come costrutto di runtime invece che come una libreria, non esiste alcuna chiamata per avviare il ciclo ed entra semplicemente nel ciclo degli eventi dopo aver eseguito lo script di input, uscendo dal ciclo di eventi quando non ci sono più callback da eseguire. Questo comportamento è simile a JavaScript in browser: il ciclo degli eventi è nascosto all'utente ed tutto automatico.

Esempio script node che crea un mini-server sulla porta 3000:

const http = require('http');
const hostname = '127.0.0.1';
const port = 3000;
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello World\n');
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});

L'uso di NodeJS risulta (quasi) indispensabile con Angular, infatti al suo interno ha un runtime usato per la validazione (alcuni la chiamano compilazione) del TypeScript/Javascipt. Grazie a NodeJS sarà possibile eseguire il progetto Angular senza dover ogni volta eseguire il deploy su un WebServer e le modifiche al codice con le successive ricompilazioni saranno molto veloci. Con leggerezza si può paragonare NodeJS al RunTime della JVM ed in particolare il componente JRE (Java RunTime Edition).

npm (abbreviazione di Node.js Package Manager) è un gestore di pacchetti usato di default negli ambienti che usano node.js, consiste di un client a linea di comando e di un database online di pacchetti pubblici e privati a pagamento chiamato npm registry, indispensabile per la realizzazione di siti con Angular.

Sui sistemi GNU Linux Debian basta installare i pacchetto npm e nodejs, mentre sui sistemi Windows bisogna installare NodeJS scaricato dal sito ufficiale.

Git (che nello slang americano significa idiota) è un software di controllo versione distribuito utilizzabile da riga di comando, molto utilizzato in tutto il mondo è andato a sostituire tutti i tipi di repository come CVS e SVN per la sua completezza e la sua facilità d'uso oltre che alla sua completezza e stabilita. E' possibile trovare molti siti GitLab dove sono disponibili progetti liberi o repository pronti per ricevere nuovi progetti.  I principali comandi da riga di comando sono:

  • git init : creare un repository dentro la cartella in cui si trova il terminale
  • git clone http://server.it/nome/progetto.git: checkout di un repository in una cartella locale cioè scaricare il progetto da un server GIT a locale
  • git clone /percorso/del/repository : checkout di un repository in un percorso locale
  • git chechout development : spostarsi da un branch ad un altro all'interno di un progetto GIT
  • git checkout master : spostarsi da un brach alla radice madre
  • git add * :
  • git commit -m "Messaggio per la commit" : committare nella propria area di lavoro GIT quando presente in locale
  • git push : inviare nello stream di GIT quanto committato in GIT
  • git pull : scaricare dal server le nuove versioni se presente
  • git status : visualizzare lo stato del progetto (file in ingresso ed in uscita)
  • git remote -v : visualizzare le informazioni remote
  • git branch -a : elenco dei branch attivi (nel server)
  • git log : avere la sequenza delle commit eseguite (per esempio 1b2e1d63ff)
  • git tag 1.0.0 1b2e1d63ff : marchiare una commit con un tag (la sequenza è data da git log)

L'installazione di un ambiente di sviluppo completo su sistemi MsWindows inizia con l'installazione del browser Chrome e di editor di testo avanzato (come Notepad++). Prima componente da installare è il pacchetto NodeJs disponibile sul sito ufficiale https://nodejs.org/en/ (ad oggi disponibile alla versione 12.12.0), dopo l'installazione una finestra installerà in automatico anche i componenti di python2 e altri componenti di necessari.

Secondo step è quello di installare GIT scaricabile facimente dal sito ufficiale git-scm.com/downloads, dopo l'installazione è buona norma controllare la presenza del comando GIT nella riga di comando BASH.

Terzo step, non obbligatorio, è quello di scaricare e installare il programma "Visual Studio Code" disponibile dal sito ufficiale microsoft: code.visualstudio.com e al termine riavviare il sistema per rendere effettive tutte le modifiche.

Creando una cartella , per esempio c:\provaAngular\ basta entrare nella riga di comando (cmd) e digitare i comandi

cd c:\provaAngular\
npm install -g @angular/cli
ng new provaAngular
(durante l'installazione chiesto conferma ad alcune opzioni, premere invio)
cd provaAngular
ng serve

Aprire un browser e digitare l'indirizzo

http://localhost:4200/ 

per constatare se il progetto appena creato è stato avviato correttamente

L'installazione di un ambiente di sviluppo completo sui sistemi GNU Linux Debian comprende passi altrettanto semplici, anche se npm e NodeJS sono disponibili su Synaptic è consigliato usare i pacchetti disponibili sul sito ufficiale nodejs.org mentre GIT può essere installato dal gestore dei pacchetti. Per installare il tutto basta lanciare i comandi:

cd ~
wget https://nodejs.org/dist/v10.5.0/node-v10.5.0-linux-x64.tar.xz
tar xvf node-v10.5.0-linux-x64.tar.xz
mv node-v10.5.0-linux-x64 nodejs
mkdir ~/bin
cp nodejs/bin/node ~/bin
cd ~/bin
ln -s ../nodejs/lib/node_modules/npm/bin/npm-cli.js
node --version
npm --version
npm config set user 0
npm config set unsafe-perm true
npm install -g @angular/cli
mkdir provaAngular
cd provaAngular
ng new provaAngular
ng serve

Aprire un browser e digitare l'indirizzo http://localhost:4200/ per constatare se il progetto appena creato è stato avviato correttamente. Per il programma di sviluppo è possibile scaricare dal sito www.visualstudio.com la versione per debian e poi installarla con il comando

dpkg -i code_1.39.2-1571154070_amd64.deb

Esistono diversi tools e ambienti SDK per lo sviluppo si applicazioni Angular, il più utilizzato è Visual Studio Code sviluppato da Microsoft che è disponibile per tutte le piattaforme quindi potrete trovare anche il programma per i sistemi AppleMac e per GNU Linux oltre che ovviamente per MS Windows. L'installazione è immediata e il programma non necessita nemmeno di un hardware potente, l'uso risulta molto fluido anche su grandi progetti e questi sono i principali motivi del successo di questo programma rispetto alla concorrenza.
All'avvio si deve scegliere la cartella dove è installato un progetto, nel nostro caso quello dove abbiamo lanciato il comando "npm install -g @angular/cli" e dove è presente il file "package.json" che descrive proprio il progetto Angular e tutte le dipendenze.

Oltre al classico menù applicativo, sulla sinistra compaiono cinque bottoni: file, search, git, debug e extensions, queste sezioni permettono di navigare velocemente tra una sezione ed un altra, concerto molto simile alle prospettive di Eclipse. Una delle cose molto utili e quasi indispensabili è la console terminale (terminal in inglese), questa vi permette di lanciare il comando "nv serve" direttamente dal programma e di controllare lo stato del server, eventuali errori di compilazione e permette di fermare il server velocemente.

Come tutti gli SDK, c'è il sistema di editing assistito a seconda del tipo del file aperto e la funzione di autocomplete. I sistemi di Debug e Navigazione disponibili sono molto semplici da usare e sono sempre molto veloci.

Il comando "ng new nomeprogetto" genera un progetto completo anche se con una sola pagina, all'interno del progetto ci sono le cartelle e i files:

e2e/
node_modules/
src/
.editorconfig
.gitignore
README.md
angular.json
package-lock.json
package.json
tsconfig.json
tslint.json
  • La cartella e2e contenente i file Protractor
  • La cartella node_modules contiene tutti i package scaricati con NPM censiti nel file package.json
  • La cartella src contenente i file sorgente e di configurazione dell'applicazione iniziale per i comandi di Angular CLI.
  • Il file README.md (formato Markdown) contiene una breve descrizione del progetto appena generato con alcuni dei comandi base come ng serve e ng build.
  • I file tsconfig.json e tslint.json rappresentano i file di configurazione per TypeScript.
  • I file di configurazione package-lock.json e package.json contengono diverse informazioni utili sul progetto corrente come la lista delle dipendenze.
  • Il file .gitignore è invece usato da Git per determinare quali file e directory ignorare in fase di commmit.
  • Il file .editorconfig è usato degli editor e i vari IDE.
  • Il file angular.json è il file di configurazione del Workspace generato e modificato da Angular CLI.

All'interno della cartella src si trovano il codice sorgente specifico dell'applicazione predefinita del Workspace.

app/
app.component.css
app.component.html
app.component.spec.ts
app.component.ts
app.module.ts
assets/
.gitkeep
environments/
environment.prod.ts
environment.ts
browserslist
favicon.ico
index.html
karma.conf.js
main.ts
polyfills.ts
styles.css
test.ts
tsconfig.app.json
tsconfig.spec.json
tslint.json
  • tsconfig.app.json, tsconfig.spec.json e tslint.json: sono i file di configurazione per TypeScript e TSLint
  • il file test.ts che è il file di configurazione dei test
  • il file karma.conf.js serve al tester runner Karma
  • il file polyfills.ts contiene i comandi javascript (polyfill) per supportare i browser meno recenti
  • il file style.css è il foglio di stile principale del progetto ma in fase di compilazione con il comando "ng new" è possibile impostare l'uso di un preprocessore CSS come SASS
  • il file favicon.ico è la favicon usata come icona nella scheda del browser
  • il file browserslist è attualmente usato per modificare delle regole CSS
  • il file main.ts è l'accesso principale della nostra applicazione e deve contenere il codice per avviare il modulo base
  • il file index.html è la pagina HTML principale, da notare che non deve contenere i riferimenti a javascript e css perchè sarà compito del compilatore ad aggiungere tali riferimenti
  • la cartella assets contiene le immagini e le risorse da copiare nel progetto n cui verranno inseriti i file della nostra applicazione in seguito alla fase di build, inizialmente nuota
  • la cartella environments contiene i vari file delle variabili d'ambiente. Inizialmente ci sono due file: environment.ts e environment.prod.ts, file usati dal mail.ts
  • la cartella app contiene il cuore della nostra applicazione: in questa cartella si troveranno i file dei componenti

All'interno della cartella app, nella applicazione iniziale, sono presenti i file

  • app.component.ts è presente la logica del componente base della nostra applicazione denominato AppComponent cioè una classe TypeScript con la direttiva @Component
  • app.component.html è il template HTML del componente
  • app.component.css è il file CSS del componente, da notare che le regole definite in questo file saranno valide solo per questo componente.
  • app.component.spec.ts è il file del componente
  • app.module.ts è il codice TypeScript del modulo base

Il comando ng generate è il comando più usato in fase di sviluppo perché permette di creare componenti all'interno di applicazioni generando i file necessari per definire moduli, componenti, servizi, pipe e librerie secondarie. La sintassi è:

ng generate <tipo> <nome>

Infatti è possibile creare oggetti con i file corrispondenti che vengono aggiunti in automatico nel progetto. In termini pratici è possibile per esempio creare:

ng generate class <nome-classe> per generare una semplice classe tg
ng generate interface <nome-interfaccia> per creare un'interfaccia
ng generate component <nome-componente> per un nuovo componente
ng generate directive <nome-direttiva> per un nuova direttiva
ng generate service <nome-servizio> per un nuovo servizio
ng generate module <nome-modulo> per un nuovo modulo

Creando un componente con ng generate component:

ng generate component my-new-component --spec=false 

il risultato è la creazione di tre file

src/app/my-new-component/my-new-component.component.css
src/app/my-new-component/my-new-component.component.html
src/app/my-new-component/my-new-component.component.ts

E' possibile anche langiare il comando "ng g c" al posto di "ng generate component", utilizzando la sintassi abbreviata del comando ng. Il nuovo componente viene creato in una nuova directory (my-new-component) che contiene tutti i file ad esso associati, non è presente il file di test perché è stato specificata l'opzione spec=false per saltare la creazione. Inoltre è possibile notare che viene aggiornato in automatico il file app.module.ts perché di default il nuovo componente viene aggiunto al modulo base AppModule.
Altri parametri al comando ng generate sono

  • --flat indica che i file vengano creati nella cartella app, ma questa opzione è assolutamente sconsigliata.
  • -s non crea il file css ma usa la proprietà styles
  • -t non crea il file template
  • --dry-run genera a video i nomi dei file che verrebbero creati ma non vengono creati veramente

per esempio con il comando :

ng generate component my-new-component --spec=false --dry-run

CREATE src/app/my-new-component/my-new-component.component.css (0 bytes)
CREATE src/app/my-new-component/my-new-component.component.html (35 bytes)
CREATE src/app/my-new-component/my-new-component.component.ts (307 bytes)
src/app/app.module.ts (432 bytes)
NOTE: The "dryRun" flag means no changes were made.

Le applicazioni angular si basano interamente sul concetto di componente, cioè tutti gli elementi sono moduli e sottomoduli che visualizzano oggetti HTML e permettono di iteragire con altri componenti o service. Ogni componente è formato da tre file: css, html e ts ben divisi e mai mischiati. All'interno delle applicazioni si crea una struttura gerarchica di componenti con un elemento base (detto root component) che ha il nome di AppComponent, con un app-root ed è l'unico che viene inserito come tag <app-root> direttamente nel file index.html.

Per convenzione e per questione di ordine, i file di un componente sono inseriti all'interno di una directory identificata dal nome del componente, più parole vengono separate da trattini (kebab-case) quindi diventa nome-componente.component.estensione.

Nel nostro primo esempio andremo a creare un componente che calcola il doppio di un numero, con un input text, un bottone e una label. Alla pressione del bottone typescript calcola il doppio del valore nell'input text e lo salva nella label. Partendo da un progetto vuoto, per prima cosa andremo a creare un componente con il comando

ng generate component calcola-doppio --spec=false

vengono generati i 3 file del componente, per prima cosa andiamo a verificare che nel file app.modules.ts andiamo ad aggiungere il modulo FormsModule che indispensabile nella gestione gli input e output del componente verso l'html: per prima cosa aggiungiamo l'import all'inizio del file

import {FormsModule} from '@angular/forms'; 

e seconda cosa definiamo l'import all'interno della classe (in teoria BrowserModule dovrebbe essere già presente di default):

imports: [ BrowserModule ,FormsModule ],

Poi definiamo le varie parti del componente

calcola-doppio.component.css

.labelDoppio{color:green;} 

calcola-doppio.component.html

<h1>Titolo: {{titolo}}</h1>
<div>
<input type="text" [(ngModel)]='valoreSingolo' id="numeroInserito" />
<input type="button" id="bottoneCalcolaDoppio"
(click)="ngCalcolaDoppio()" value="Calcola doppio"/>
<span id="numeroCalcolato" class="labelDoppio" >
{{valoreDoppio}}
</span>
</div>

calcola-doppio.component.ts

import { Component, OnInit, Input, Output } from '@angular/core';

@Component({
selector: 'app-calcola-doppio',
templateUrl: './calcola-doppio.component.html',
styleUrls: ['./calcola-doppio.component.css']
})
export class CalcolaDoppioComponent implements OnInit {
public titolo : string ='Componente per calcolare il doppio di un numero';

@Input ()
valoreSingolo : number;

@Output ()
valoreDoppio : number;

constructor() { }

ngOnInit() {
this.valoreSingolo = 11;
this.ngCalcolaDoppio();
}

ngCalcolaDoppio(){
this.valoreDoppio=this.valoreSingolo * 2;
}
}

Nel prossimo articolo andremo ad analizzare nel dettaglio il file TypeScript e alcune particolarità dell'HTML. Per provare il componente basta aggiungere il tag

<app-calcola-doppio></app-calcola-doppio>

nel file index.html del nostro progetto e caricare la pagina web in un browser per vedere il componente e testare la funzionalità.

Nell'esempio calcola-doppio abbiamo definitio un TypeScript che andiamo ad analizzare istruzione per istruzione il codice scritto. All'inizio di ogni file ts (TypeScript) sono presenti gli import, necessari al compilatori, in caso di mancanza di un import, l'ambiente di sviluppo segnala l'errore

import { Component, OnInit, Input, Output } from '@angular/core';

Prima della classe c'è la definizione del component, cioè il legame tra la classe, il suo file HTML e il suo file CSS

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

La definizione della classe e le classi che implementa, in questo esempio implementa solamente OnInit ma vedremo casi dove sarà necessario aggiungere altre classi con OnSubmit o altri eventi. La parola chiave export è necessaria per rendere la classe visibile al di fuori del file TypeScript dove è definita.

export class CalcolaDoppioComponent implements OnInit {

Definizione di una proprietà pubblica di tipo stringa, da notare che il tipo e la visilità sono opzionali ma è sempre bene metterli. Il valore di default è indicato qui come fosse una costante

  public titolo : string ='Componente per calcolare il doppio di un numero';

Definizione degli input del componente, in realtà sono delle proprietà normali con solo l'aggiunta della direttiva "@Input ()" che indica a Angular che deve essere gestito come un input inserito dall'utente

  @Input ()
valoreSingolo : number;

Definizione degli output del componente, come per gli input sono delle proprietà con l'aggiunta della direttiva "@Output ()"

  @Output ()
valoreDoppio : number;

Definizione del costruttore che nel nostro esempio non è usato

  constructor() { }

Definizione del metodo ngOnInit() che è richiamato quando il componente viene inizializzato, nel nostro caso inizializziamo il valore iniziale con un valore e poi calcoliamo il doppio del valore iniziale.

  ngOnInit() {
this.valoreSingolo = 11;
this.ngCalcolaDoppio();
}

Funzione che calcola il doppio, leggendo la proprietà valoreSingolo della classe e valorizzando la proprietà valoreDoppio della classe stessa, senza dover toccare il DOM degli oggetti grazie alle direttive @Input e @Output i valori saranno letti e modificati in automatico nell'HTML mostrato nel browser.

  ngCalcolaDoppio(){
this.valoreDoppio=this.valoreSingolo * 2;
}
}//fine classe CalcolaDoppioComponent

Da notare che quando sono utilizzate le proprietà di una classe, viene usato il "this." che in TypeScript è sempre obbligatorio. Il codice TypeScript funziona se nel codice HTML del componente sono inserite alcune proprietà specifiche di Angular per la gestione degli Input e degli Output.

Per visualizzare il valore di una proprietà si usa {{nomeVariabile}} come

<h1>Titolo: {{titolo}}</h1>

Negli input si usa [(ngModel)]='nomeVariabile' per indicare ad Angular che questo valore è sia in lettura con le quadre, sia in lettura con le tonde, in questo modo ogni volta che il valore nel componente cambia, cambierà nel browser e vieversa:

<input type="text" [(ngModel)]='valoreSingolo' id="numeroInserito" /> 

Nel bottone viene definito con la proprietà (click)="ngCalcolaDoppio()" che gestisce l'evento click, cioè ogni volta che si scatena l'evengo click del bottone viene richiamato il metodo del componente

<input type="button" id="bottoneCalcolaDoppio" (click)="ngCalcolaDoppio()" value="Calcola doppio"/>

Come per il titolo anche per il valore doppio si usa {{nomeVariabile}} per visualizzare il valore di una proprietà nel componente:

<span id="numeroCalcolato" class="labelDoppio" >{{valoreDoppio}}</span>

Lo scopo delle direttive è quello di creare un "dialogo" tra la componente grafica HTML e la componente TS: la filosofia base di Angular è proprio quello di creare un metodo TypeScript per gestire il comportamento degli elementi grafici. Le prime direttive sono quelle legate agli stili e alle classi, come già detto più volte la filosofia base di Angular è la separazione completa dei compiti, cioè nel file CSS si definiscono le classi, per esempio usando il nostro componente di calcolo del doppio:

.labelDoppioTre{color:green;} 
.labelDoppioNonTre{color:blue;}

Poi nel file ts (typescript) è possibile definire un metodo che ritorna "true" se il numero calcolato è multiplo di tre

isTre () : boolean {
if (this.valoreDoppio % 3 == 0){
return true;
}else{
return false;
}
}

Poi nel tag HTML si può usare la proprietà class dinamica usando le parentesi quadre per indicare che il valore proviene dal typescript:

<span id="numeroCalcolato" [class]="isTre() ? 'labelDoppioTre' : 'labelDoppioNonTre' " >{{valoreDoppio}}</span>

la logica all'interno della valore della proprietà è simile alle condizioni abbreviate Java cioè

condizione ? valoreVero : valoreFalso 

Questa direttiva è usata se si vuole indicare nell'HTML il nome delle classi CSS, è possibile indicare nel TypeScript la classe con la direttiva ngClass, per esempio un nuovo metodo:

getClasseDoppio () : string {
  if (this.valoreDoppio % 3 == 0){
return "labelDoppioTre";
}else{
return "labelDoppioNonTre";
}
}

E nel codice basta indicare la direttiva ngClass e il metodo che ritorna il valore

<span id="numeroCalcolato" [ngClass]="getClasseDoppio()" >{{valoreDoppio}}</span>

Questa seconda direttiva è sicuramente la più consigliata anche se obbliga il programmatore a scrivere la classe CSS all'interno del file TypeScript e non nel codice HTML come sarebbe più naturale. Queste direttive possono essere usate per tutte le proprietà base standard HTML5 dei tag, per esempio se volessimo usare la proprietà disabled per bloccare il bottone se l'utente ha inserito un valore non positivo, si può definire un metodo che ritorna true/false a seconda della positività del valore inserito.

valoreInseritoNegativo () : boolean {
if (this.valoreSingolo <= 0)
return true;
return false;
}

e nel tag input usiamo la direttiva con le parentesi quadre per impostare dinamicamente la proprietà nel tag:

<input type="button" id="bottoneCalcolaDoppio" (click)="ngCalcolaDoppio()" value="Calcola doppio" [disabled]="valoreInseritoNegativo()" />

Stesso discorso può valere per i title dei tag, per esempio per aggiungere un title al nostro bottone disabilitato basta aggiungere una proprietà nella classe e la gestione nel metodo

public messaggioErrore : string =''; 
valoreInseritoNegativo () : boolean {
this.messaggioErrore='Numero zero o negativo';
if (this.valoreSingolo <= 0)
return true;
this.messaggioErrore='';
return false;
}

e basta aggiungere la direttiva nel title del bottone

<input type="button" id="bottoneCalcolaDoppio" (click)="ngCalcolaDoppio()" value="Calcola doppio" [disabled]="valoreInseritoNegativo()" [title]="messaggioErrore" /> 

Altro esempio è la gestione degli input text, per esempio aggiungendo una classe

.inputValoreErrato{background: brown;}

e nel tag input aggiungiamo la condizione sulla direttiva class

< input type="text" [(ngModel)]='valoreSingolo' id="numeroInserito"  [class]="valoreInseritoNegativo() ? 'inputValoreErrato' : 'inputValoreCorretto' " />

Lo scopo delle direttive condizionali è quello di gestire la presenza di TAG a seconda di condizioni gestite da TypeScript, anche in questo caso si tenta di separare il più possibile la parte grafica (HTML/CSS) dalla logica (TypeScript). Mantenendo il metodo valoreInseritoNegativo() usato nel precedente esempio andiamo ad usare la direttiva *ngIf come una proprietà di un tag con al suo interno il nome del metodo o della proprietà del componente Typescript dove recuperare il valore della condizione

<span *ngIf="valoreInseritoNegativo()" class="inputValoreErrato">
Il valore inserito non valido, controlla quello che hai inserito
</span>

Anche in questo caso è possibile definire la condizione all'interno del codice dell'HTML con le espressioni regolari ma questo è vivamente sconsigliato. Altra direttiva molto interessante è la switch che permette di eseguire un if multiplo, per esempio definendo il metodo che calcola se il valore inserito è multiplo di qualche numero piccolo, ritornando il numero in lettere (per avere un esempio più semplice)

multiploDi() : string {
if (this.valoreInseritoNegativo())
return null;
if (this.valoreSingolo % 2 == 0)
return "due";
if (this.valoreSingolo % 3 == 0)
return "tre";
if (this.valoreSingolo % 5 == 0)
return "cinque";
if (this.valoreSingolo % 7 == 0)
return "sette";
return null;
}

E nel codice HTML è bisogna usare ngSwitch per definire il metodo/variabile da usare come condizione, ngSwitchCase da usare come valori di confronto (da ricordare che bisogna usare anche gli apici ' se si stratta di valore string) e ngSwitchDefault per il caso "default" dell'istruzione switch.

<div [ngSwitch]="multiploDi()">
<div *ngSwitchCase="'due'">
Il valore è divisibile per 2 (due)
</div>
<div *ngSwitchCase="'tre'">
Il valore è divisibile per 3 (tre)
</div>
<div *ngSwitchCase="'cinque'">
Il valore è divisibile per 5(cinque)
</div>
<div *ngSwitchDefault>
Il valore non è divisibile
</div>
</div>

Da notare che nell'esempio è stato usato il tag DIV ma potevano essere usati altri componenti, per esempio è possibile usare un componente "componente-tabellina-due"

<componente-tabellina-due *ngSwitchCase="'due'">
Il valore è divisibile per 2 (due)
</componente-tabellina-due>

La direttiva NgFor è la direttiva per gestire i cicli di elementi, da notare che anche in questo caso si separano i dati (oggetti TypeScript) e l'elemento grafico (HTML).

export class Prodotto{
public nome : string;
public prezzo : number;
public codice : string;

constructor(nome : string, prezzo: number, codice: string) {
this.nome=nome;
this.prezzo=prezzo;
this.codice=codice;
}
}


public magazzino : Array<Prodotto>;
ngOnInit() {
this.magazzino = [
new Prodotto("Penna",2,"PEN"),
new Prodotto("Matita",1,"MAT"),
new Prodotto("Blocco",3,"BLO")
];
}

<h1>Elenco prodotti</h1>
<div *ngFor="let prodotto of this.magazzino; index as i">
<div >{{i}}: {{prodotto.nome}} : {{prodotto.prezzo}} €</div>
</div>

Dove come condizione del ciclo abbiamo le istruzioni:

  • let prodotto: definisce come "prodotto" il nome della variabile nome per accedere al singolo elemento nel ciclo
  • of magazzino: definisce l'elenco da dove prendere la lista da ciclare
  • index as i: definisce una variabie indice, che può essere usata per visualizzare o gestire il contatore,

un esempio di uso dell'indice è quello di definire una proprietà per evidenziare il prodotto selezionato, ovviamente c'è bisogno anche di un metodo per la gestione della seleziona con il click sul prodotto stesso:

public prodottoSelezionato : number; 

selezionaProdotto( event: any, indice: number){
this.prodottoSelezionato=indice;
}

e modificando il codice con

<h1>Elenco prodotti</h1>
<div *ngFor="let prodotto of this.magazzino; index as i">
<div [class]="this.prodottoSelezionato==i ? 'inputValoreErrato' : '' " (click)="selezionaProdotto($event,i)">
{{prodotto.nome}} : {{prodotto.prezzo}} €
</div>
</div>

oltre all'indice index è possibile usare anche even, odd, first e last per gestire le posizioni, in complesso è possibile definire la regola del ciclo come

let prodotto of this.magazzino; index as i;even as isEven; odd as isOdd; fist as isFirst; las as isLast; 

Da notare che la direttiva NgFor non ridefinisce l'elenco ma usa gli stesso elementi, quindi a volte è necessario definire metodi che identificano gli elementi non per indice ma in altro modo, per fare questo bisogna aggiungere un metodo "identificativo", per esempio da un codice univoco:

identificaDaCodice(index : number, prodotto : Prodotto){
return prodotto.codice;
}

e nel codice del ciclo si aggiunge l'indicazione trackBy

<h1>Elenco prodotti</h1>
<div *ngFor="let prodotto of this.magazzino; index as i; trackBy : identificaDaCodice">
<div [class]="this.prodottoSelezionato==i ? 'inputValoreErrato' : '' " (click)="selezionaProdotto($event,i)">
({{prodotto.codice}}) {{prodotto.nome}} : {{prodotto.prezzo}} €
</div>
</div>

Da notare che non è possibile inserire nello stesso elemento più direttive di cicli e condizionali, per esempio va in errore il codice

<div *ngFor="let prodotto of magazzino; index as i; trackBy : identificaDaCodice" *ngIf="magazzino.lenght > 4" >

La creazione di un componente tramite il comando ng genera codice di default con proprietà e caratteristiche modificabili e personalizzabili. Il codice base di con componente è:

@Component({
selector: 'nome-componente',
templateUrl: './nome-componente.html',
styleUrls: ['./nome-componente.css']
})
export class NomeComponente implements OnInit {
// ...
}

usiamo il termine "caratteristica" per indicare tutto quello che è elencate dentro alla sezione

@Component

la prima caratteristica modificabile è "selector", che prevede tre modalità di definizione:

  • selector: 'nome-componente' viene usato come tag <nome-componente></nome-componente>
  • selector: '.nome-componente' viene usato come classe di un tag <div class="nome-componente"></div>
  • selector: '[nome-componente]' viene usato come attributo di un tag <div nome-componente></div>

la seconda caratteristica modificabile è il "templateUrl" cioè il riferimento al template HTML come url del file html oppure è possibile definire il codice template all'interno del codice TypeScript come scringa, per esempio:

template: '<div class="contenuto"> ... contenuto HTML </div>',

la terza caratteristica modificabile è lo styleUrls che definisce il file css, in alternativa si può definire il codice degli stili come stringa, per esempio:

styles: ['
.positive{color:green;}
.negative{color:red;}
']

nei progetti di grandi dimensioni è necessario ordinare i componenti in maniera efficente e Angular mette a disposizione una quarta caratteristica con la possiblità di definire come gli stili dei componenti devono essere caricati:

  • ViewEncapsulation.Emulated il valore di default, dove Angular unisce i CSS e crea un DOM unico
  • ViewEncapsulation.Native in questo caso, Angular prova a usare i CSS solo all'interno del componente usando un ShadowDOM, questo funziona solo per i browser recenti che supportano questo componente
  • ViewEncapsulation.None in questo caso si usano solo i CSS glogali senza nessun incapsulamento

da tenere presente che il ShadowDOM è un sistema "recente" per definire i CSS multi-classe evitando il "!important" molto usato di recente e definendo un CSS locale solo per alcune porzioni di documento HTML.

E' possibile usare altre caratteristiche minori ma a volte molto utili

  • preserveWhitespaces per ridefinire il comportamento di HTML di ridurre tutti gli spazi non necessari
  • interpolation consente di ridefinire i caratteri speciali da inserire nel componente HTML "{{" e "}}", per esempio con interpolation: ['<<', '>>']
  • viewProviders verrà scritto un articolo dedicato al componente che permette di usare parti di un componente in altri
  • exportAs permette di esporre ad altri componenti, metodi e proprietà del componente
  • changeDetection consente di definire un metodo per la gestione degli eventi, verrà descritta in un articolo dedicato

In una classe TypeScript di un componente Angular ci sono tre direttive che incidono nel ciclo di vita di un componente, queste direttive sono usate per includere a livello logico le classi. Se si crea un componente normale queste direttive possono sembrare superflue ma se si vogliono usare moduli e componenti evoluti di Angular, le direttive diventano indispensabili.

  • declarations si usa quando il componente deve essere disponibile nell'ambito di un modulo. Il compilatore aggiunge in automatico il componente quando si crea dentro ad un modulo specifico
  • imports all'interno di un file Typescript questa direttiva si usa per indicare al compilatore quali componenti o moduli sono usati all'interno del Typescript
  • export si usa se un Typescript è usato da altri, senza questa direttiva infatti, non è possibile importare una classe in altri Typescript

Altre due direttive molto importanti sono quelle di Input e di Output
Input viene usato per definire quali proprietà sono collegate a elementi di input, si usa molto per inviare dati dal browser al componente e si definisce con

@Input() public prodotto: Prodotto ;

Mentre Output usato per definire il collegamento tra il componente TypeScript ed un elemento HTML del componente stesso, soprattuto per gestire gli eventi con la classe EventEmitter, per esempio:

export class StockItemComponent {
@Input()
public prodotto: Prodotto;

@Output()
private prodottoSelezionato: EventEmitter<Prodotto>;

constructor() {
this.prodottoSelezionato = new EventEmitter<Prodotto>();
}

ngOnInit(): void {
this.prodotto = new Prodotto("Penna",1,"PEN");
}

onClickPersona(event) {
this.prodottoSelezionato.emit(this.prodotto);
}
}

dove la propietà Output viene usata in un oggetto EventEmitter, nel metodo onClickPersona viene chiamato il metodo emit, immaginando poi un codice HTML all'interno di un ciclo:

<div class="prodotto">
<div class="nome">{{prodotto.nome + ' (' + prodotto.codice + ')'}}</div>
<div class="price">€ {{prodotto.prezzo}}</div>
<button (click)="onClickPersona($event)">Seleziona</button>
</div>

In precedenza è stato accennato ad una caratteristica changeDetection per la gestione degli eventi: Angular permette di definire un metodo per la gestione, di default la caratteristica vale

ChangeDetectionStrategy.Default

ma è possibile cambiare questo valore. Il valore di default indica ad Angular di rilevare tutti gli eventi e cercare in ogni componente nella struttura se uno dei valori è cambiato e necessario per essere aggiornato nella vista. Nelle applicazioni di medie o grandi dimensioni , la presenza di tanti componenti che potrebbero avere stesso nome o stesse proprietà impongono una strategia migliore, il valore più usato è

ChangeDetectionStrategy.OnPush

Per esempio se sono presenti tre componenti A e B, dove il componente A contiene B con il codice HTML:

<b [inputToC]="compositeObj"></b>

E i componenti si comporteranno:

  • se il componente B contiene una proprietà compositeObj e se B modifica il valore della proprietà
  • se il componente A crea o cambia il riferimento a compositeObj di B, allora il componente B riconosce la modifica e aggiorna il valore
  • se il componente A modifica un attributo su compositeObj in risposta ad un evento lanciato da A (usando lo stesso riferimento) queste modifiche non vengono aggiornate nel componente B (modifica principale rispetto al comportamento predefinito di Default che prevede la modifica)
  • se il componente A modifica un attributo in risposta a un evento lanciato da B (senza modificare il riferimento), queste modifiche hanno effetto perché la modifica ha origine dal componente B

da notare che la differenza tra Default e OnPush è solo nel terzo caso, negli altri i componenti si comportano nella stessa maniera.

Tutti i componenti di Angular hanno un ciclo di vita che viene eseguito nell'ordine indicato, ogni volta concluso il rendering di un componente, si genera l'evento per la creazione dei figli finchè ci sono elementi da creare:

  • costruttore
  • onChanges
  • onInit
  • doCheck
  • afterContentInit
  • afterContentChecked
  • afterViewInit
  • afterViewChecked
  • onDestroy

Angular chiamerà per primo il costruttore per qualsiasi componente, poi sono chiamati i passi in ordine, OnInit e AfterContentInit sono chiamati solo una volta mentre gli altri vengono chiamati ogni volta che si verifica un evento.

Per ogni step del ciclo di vita, viene fornita una interfaccia che deve essere implementata quando un componente deve gestire l'evento stesso. Ad esempio, il passaggio del ciclo di vita di OnInit richiede l'implementazione di una funzione chiamata ngOnInit nel componente. ViewChildren è qualsiasi componente figlio i cui tag compaiono all'interno del modello del componente. ContentChildren è qualsiasi componente figlio che viene proiettato nella vista del componente, ma non è direttamente incluso nel modello all'interno del componente.
L'elenco delle dei metodi disponibili è:

  • ngOnChanges(changes:SimpleChange) (della interfaccia OnChanges): viene chiamato subito dopo il costruttore e anche dopo ogni volta che una proprietà di input cambia
  • ngOnInit() della interfaccia OnInit: questo metodo è usato per caricare i dati, da separare dal costruttore in una filosofia di separazione dei compiti
  • ngDoCheck() della interfaccia DoCheck: è il metodo di Angular di fornire al componente un modo per verificare se ci sono vincoli o modifiche che Angular non può o non deve rilevare da solo. Questo è uno dei modi in cui possiamo utilizzare per notificare ad Angular una modifica del componente, quando sostituiamo il valore predefinito ChangeDetectionStrategy per un componente da Default a OnPush.
  • ngAfterContentInit() della interfaccia AfterCon: viene attivato durante i casi di proiezione del componente e solo una volta durante l'inizializzazione del componente. Se non è presente alcuna proiezione, questa viene attivata immediatamente.
  • ngAfterContent() della interfaccia AfterCon: viene attivato ogni volta che viene eseguito il ciclo di rilevamento delle modifiche di Angular e, nel caso in cui venga inizializzato, viene attivato subito dopo AfterContentInit
  • ngAfterViewInit() della interfaccia AfterContentChecked: è il complemento di AfterContentInit e viene attivato dopo che tutte le componenti figlio utilizzate direttamente nel modello del componente sono state inizializzate e le loro viste aggiornate con i collegamenti.
  • ngAfterViewChecked() della interfaccia AfterViewChecked: viene attivato ogni volta che tutti i componenti figlio sono stati controllati e aggiornati.
  • ngOnDestroy() della interfaccia OnDestroy: viene chiamato quando un componente sta per essere distrutto e rimosso dall'interfaccia utente. In genere è buona norma pulire tutto ciò che è stato registrato (timer, osservabili, ecc.)

La tecnica Projection è un'idea importante in Angular in quanto ci dà maggiore flessibilità quando sviluppiamo i nostri componenti e ci offre di nuovo un altro strumento per renderli veramente riutilizzabili in contesti diversi. Questa tecnica è utile quando vogliamo costruire componenti ma impostiamo alcune parti dell'interfaccia utente del componente in modo che non siano definita all'interno del componente, ad esempio, supponiamo che stessimo costruendo un componente per un componente "carosello" con alcune semplici funzionalità: visualizza un elemento e ci consente di navigare verso l'elemento successivo/precedente. Ma una cosa che non è di competenza del componente carosello è la visualizzazione del contenuto stesso, un programmatore potrebbe usare il componente per visualizzare un'immagine, una pagina di un libro o qualsiasi altra cosa. Per esempio andiamo a costruire un componente, chiamato app-stock-item, di un eventuale sito di vendite on-line con un carosello che usa questa tecnica:

<div class="stock-container">
<div class="name">{{stock.name}}</div>
<div class="price">$ {{stock.price}}</div>
<ng-content></ng-content>
</div>

Dove ovviamente ng-content è il concetto di projection, cioè un componente che potremmo chiamare incompleto. Nel componente padre, ci basterà richiamare il nostro contentuo app-stock-item passandogli il valore :

<app-stock-item [stock]="stockObj1">
  <button (click)="addToCart()">Add to cart</button>
</app-stock-item>
<app-stock-item [stock]="stockObj2">Product not disponible</app-stock-item>

Nel nostro semplice esempio abbiamo richiamato il componente due volte, la prima volta passando un bottone mentre nella seconda abbiamo visualizzato un testo. Si potrebbe pensare a Projection come una sorta di componente parametrico, dove un componente "delega" al padre una parte del componente stesso, spesso infatti si trovano componenti dove la parte di template (HTML) viene scritta nella classe typescript

@Component({
selector: 'nome-input',
template: '<ng-content></ng-content>'
})
export class NomeInputComponent {
...
}

Ma questa tecnica è considerata deprecata e non da usare.

La fase di test dei componenti sviluppati è fondamentale per un programmatore, in particolare nelle applicazioni Angular è quasi indispensabile creare di processi di test "automatici" visto che i componenti possono essere riutilizzati e modificati, per esempio un componente sviluppato in un momento che utilizza altri componenti, potrebbe subire modifiche in futuro a casusa di modifiche sei suoi sottocomponenti magari fatti da altri. Questi test hanno lo scopo di

  • test di sviluppo: verificare il funzionamento in fase di sviluppo
  • test di non-regressione: verificare la assenza di errori causati da modifiche a componenti figlio o padri
  • completezza: verificare che il codice scritto copra tutti i casi possibli
  • modularità: verificare che il codice scritto è modulare e che i moduli siano indipendenti dove possibile

Le tecnologie di ultima generazione vorrebbe imporre la definizione di automatismi per i test dei componenti, nel caso di applicazioni sviluppate con Angular si può parlare di test dei component. Esistono diverse tecnologie/librerie per sviluppare dei test, le quattro componenti che verranno usate sono:

  • Jasmine: il framework orientato alla scrittura dei classici unit-test, non solo specifico per angular può essere usato anche per appliazioni sviluppate con altre tecnologie. Gli step sono rappresentati da un elenco di comandi in una filosofia di test-writing
  • Karma: questo framework esegue comandi per il test-running, cioè questo framework esegue i comandi su un vero browser e attende il risultato in real-time, anche questo framework può essere usata anche per appliazioni sviluppate con altre tecnologie.
  • Protractor: questa libreria si concentra sul risultato indipendentemente dagli step eseguiti dalle librerie dei punti precedenti
  • Angular testing utilities: questa libreria interna di Angular permette di inizializzare moduli, componenti e servizi di Angular.

In un progetto Angular standard pre-generato come visto in precedenza, sono già presenti due file "karma.conf.js" e "test.ts", il primo è il file di configurazione di Karma ed è un javascipt per la gestione del browser e dell'esecuzione dei passi delle unit-test.  Il secondo è il file di configurazione del componente standard Angular e in questo file è indicato che tutti i file del tipo "spec.ts" sono da considerarsi test-unit del framework, nell'istruzione

const context = require.context('./', true, /\.spec\.ts$/);

Un "isolated-test" (a volte detto IT) è in Angular il Javascript puro che esegue componenti di Angular (che sono scritti in Typescript).  Volendo creare uno unit-test per la classe Prodotto usata nel "Direttive per i cicli", possiamo simulare un utente che seleziona un prodotto, nel file spec.ts è possibile definire un IT, per esempio:

//con it definiamo un isolated-test 
it('should select product with id=2', () => {
//creazione dell'oggetto
const prodotto=new Prodotto();
//inizializzazione del componente come se fosse visualizzato
prodotto.ngOnInit();
//simulo il click sul bottone
prodotto.selezionaProdotto(null,2);
//con il comando expect imposto qual'è il valore atteso nella proprietà
expect ( prodotto.prodottoSelezionato).toEqual(2);
});

Abbiamo usato il comando expect per indicare alle librerie qual'è il risultato atteso del test utilizzando il metodo toEqual, sono a disposizione anche i metodi toNull, toBeTruthy e toBeFalsy. Nel nostro piccolo test di esempio se si seleziona il secondo prodotto, il IT va a verificare che nella proprietà del componente sia presente il valore 2. Per eseguire i test dell'applicazione basta lanciare, dalla console di Visual Studio, il comando

ng test 

e il risultato sarà una compilazione e una pagina chrome (che si auto-apre) dove verranno visualizzati tutti gli esiti dei test. E' possibile effettuare anche test sul contenuto HTML visualizzato, per esempio se nel componente è presente il tag

<h1 class="elencoProdotti">Elenco prodotti</h1>

è possibile verificarne la coretta visualizzazione con l'oggetto fixture che di default viene generato e che rappresenta il contenuto HTML visualizzato

it('should have Elenco prodotti H1 element', () => {
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('.elencoProdotti').textContent).toContain('prodotti');
});

Per eseguire per il comando, ogni volta bisogna sempre tenere presente che vengono eseguiti tutti i test di tutti i componenti, compreso i test del "app-component", ogni volta che si aggiunge un componente bisogna ricordarsi infatti di aggiungere le import in questo file altrimenti i test potrebbero generare errore, basta infatti aprire il file "app.component.spec.ts" e aggiungere i componenti nella sezione import di configureTestingModule. Ovviamente se non si usa la pagina di default di Angular i test presenti in questo file vanno tolti.

Durante la fase di sviluppo di una applicazione è spesso necessario crare una unit-test completa che possa comprendere più isolated-test, in un file spec infatti è possibile inserire più unit-test. Ogni file spec dei test di un componente è di fatto un file typescript ma non una classe, semplicemente è un metodo come

describe ('Nome Componente', () => {
// codice della test-unit
});

All'interno della test unit si definiscono due "proprietà"

let fixture, istance;

E il metodo per inizializzare la test unit

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ NomeClasseComponent ]
}).compileComponents();
}));

dove il compileComponents viene usato per compilare tutti i componenti del componente.
Inoltre bisogna valorizzare le due proprietà di questa clase, fixture viene valorizzato con la classe mentre istance con una sua istanza (notare la differenza tra classe e oggetto).

beforeEach(() => {
fixture = TestBed.createComponent(CalcolaDoppioComponent);
istance = fixture.componentInstance;
fixture.detectChanges();
});

All'interno di un isolated-test nella unit-test è possibile usare vari comandi per accedere ad oggetti e proprietà delle classi typescript dei componenti ma anche ai componenti grafici HTML dei componenti. Per esempi il metodo By.Css permette di controllare un oggetto con una specifica classe

const elemento=fixture.debugElement.query(By.css('.nomeClasse'));

Per esempi il metodo querySelector per recuperare un tag

const element=istance.querySelector('span').textContent
const element2=istance.querySelector('.nomeClasse').textContent

Il titolo di una pagina è accessibile da

const titolo = fixture.debugElement.componentInstance.title;

E' possibile poi recuperare il testo di un elemento con la proprietà textContent

const testoElemento=elemento.nativeElement.textContent);

La gestione base dei form è già stata vista nei precedenti punti ma Angular prevede due tipi di gestione avanzata dei form e dei valori inseriti degli input nei componenti, in particolare le tecniche sono la Template-Driven e la Reactive, in italiano potrebbero essere tradotte come Form-Instradati e Form-Reattivi anche se queste traduzioni non rendono l'idea. La tecnica del Template-Driven è la tecnica ereditata da AngularJs (che si basava di Javascript e non su Typescript), importata in Angular proprio per rendere la migrazione da Js a Ts veloce e indolore. Il nome stesso indica che si basa su un template e la associazione dei dati verso e da il componente grafico, quindi si definisce prima il template e poi la logica dei/sui dati. Per prima cosa nel file app.modules.ts bisogna importare il modulo FormsModule

import { FormsModule} from '@angular/forms'

e aggiungere anche la import nella class

imports: [ BrowserModule ,FormsModule ],

operazioni in realtà già eseguite in precedenza nello sviluppo del componente calcola-doppio. Questo componente viene usato da ngModel per il data-binding tra Typescript e il suo componente Html. In questo punto esistono due opzioni utilizzabili: l'uso classico degli eventi oppure l'uso di ngModel in maniera pura. La prima tecnica si ispira a Javascipt e all'uso degli eventi, cioè gestire gli input con l'evento javascript che in Angular si indica con (input), per esempio se in un componente abbiamo una proprietà

public nomeProprieta: String; 
costructor(){
nomeProprieta ='Valore iniziale'; //costruttore per inizializzare la proprietà
}

e definiamo un input

<input type="text" name="nameInput"
[value]="nomeProprieta"
(input)="nomeProprieta=$event.target.value"
/>

in questo modo abbiamo usato la gestione di angular per fare due cose, la gestione del from-to, cioè le due direzioni tra typescript (oggetto) e componente (input html) con la parentesi quadra abbiamo legato il valore dall'oggetto all'input (from): ogni volta che cambia il valore nell'oggetto si aggiorna anche il valore nell'input html con la parentesi tonda abbiamo legato il valore dell'input all'oggetto (to): ogni volta che cambia il valore nell'input html si aggirona anche il valore nell'oggetto con la gestione dell'evento input. La seconda tecnica, prevede di utilizzare il componente ngModel "puro" di angular, cioè il nostro input diventa

<input type="text" name="nameInput" [(ngModel)]="nomeProprieta" />

a differenza della prima tecnica abbiamo legato il value dell'input con la proprietà dell'oggetto typescript, non usando l'evento input ma la proprietà ngModel definita da Angular, questa crea un relazione bidirezionale from-to tra input html e proprietà dell'oggetto, quando uno dei due cambia viene aggiornato anche l'altro, si delega ad angular la gestione degli eventi. Questa seconda tecnica, che prevede l'uso di quadre e tonde [()], viene chiamata "Banana-in-a-box" perché le due parentesi tonde possono sembrare una banana () mentre le quadre più esterne possono sembrare una scatola [].

La scelta di quali delle due tecniche usare è demandata la programmatore, la tecnica con ngModel è ovviamente la più semplice e più usata ma a volte è necessario dover gestire in maniera diversa l'evento "input" quindi si preferisce usare la prima. Per esempio: se è necessario dover implementare un controllo di un input, come la correttezza di un numero o un range di date, obbliga l'uso della prima tecnica legando l'evento input ad un metodo, se si ha una proprietà

public prezzo: number;

nell'input è possibile chiamare all'evento input un metodo dell'oggetto typescript

<input type="text" name="nameInput"
[value]="nomeProprieta"
(input)="controllaNumero(event.target.value)"
/>

e il metodo sarebbe

controllaNumero(price){
if (isNumeri(price))
this.prezzo=price;
}

Se si necessita di utilizzare gli input di tipo radio è necessario usare la stessa proprietà nel ngModel con value diversi, per esempio:

<input type="radio" [(ngModel)]="prezzo" value="2">Due euro
<input type="radio" [(ngModel)]="prezzo" value="5">Cinque euro
<input type="radio" [(ngModel)]="prezzo" value="10">Dieci euro

allo stesso modo nelle menù a tendina creati dal tag SELECT si usa ngModel nel tag Select ma i value si impostano nel tag option (come sempre è stato)

<select name="prezzo" [(ngModel)]="prezzo">
<option value="2">Due euro</option>
<option value="5">Cinque euro</option>
</select>

Ma è possibile anche usare gli elenchi di Angular, per esempio se creiamo una proprietà

public valutaSelezionata : String;
public elencoValute = ['EUR','USD','STD'];

possiamo eseguire un ciclo per visualizzare tutti i valori

<select name="valutaSelezionata" [(ngModel)]="valutaSelezionata">
<option *ngFor="let valuta of elencoValute"
[ngValue]="valuta">{{valuta}}</option>
</select>

Nota: nel tag input sono state definite anche le proprietà name per completezza dell'esempio, i nomi delle proprietà name e la proprietà della classe typescript possono anche non corrispondere, anche se è buona norma usare lo stesso nome.

Angular estende le validazioni base di HTML5 grazie ai template-driven e lavorano direttamente nei form e negli oggetti DOM fruttando gli oggetti ngModel e ngForm. Ci sono due aspetti importanti

  • stato: con cui si gestiscono gli eventi di un input come i cambiamenti di valore o il comportamento dell'utente
  • validazione: con cui si controlla se un elemento è valido o meno gestendo il motivo di una eventuale invalidità

I componenti ngModel hanno delle direttive molto chiare riguardo: ci sono tre modelli primari tutti con una coppia di classe che vengono aggiunte in automatico dal framework

  • Visited: ng-touched e ng-untouched
  • Changed: ng-dirty e ng-pristine
  • Valid: ng-valid e ng-invalid

e queste classi possono essere usate per la classi personalizzazione, per esempio

.ng-valid{background-color:green;}

Un semplice esempio di input può essere

<input type="text" name="nameInput" required [(ngModel)]="nomeProprieta"/>

Per le validazioni, si utilizzano quelle standard HTML5 con alcune accorgimenti, si utilizza il # per indicare ad un input come nome-referenza da usare in un template.

Un esempio completo con i commenti nel codice:

<form (ngSubmit)="invioDati(oggettoForm)" #oggettoForm="ngForm">
<!-- #oggettoForm="ngForm" è la referenza per il template delle validazioni -->
<input type="text" required plaseholder="Nome oggetto" name="nomeObj" #oggettoNome="nomeObj" />
<!--referenza con #oggettoNome="nomeObj" -->
<div *ngIf="oggettoNome.errors && oggettoNome.errors.required">
Nome Obbligatorio
</div><!--stato dell'oggetto in caso di invalidazione -->
<inut type="text required minlegth="2" #oggettoCodice="ngModel" [(ngModel)]="codice" />
<div *ngId="oggettoCodice.dirty && oggettoCodice.invalid">
<!--oggettoCodice come referenzia e gestione invalidità -->
<div *ngIf="oggettoCodice.errors.minlegth">
Codice deve essere di almeno 2 caratteri
</div><!--gestione oggettoCodice e lunghezza minima-->
</div>
<input type="checkbox" name="conferma" required [(ngModel)]="confirmed" />
<button type="submit">Create</button>
</form>

E gestire la fase di invio del submit nel typescript nella classe del componente

invioDati(oggettoForm){
console.log("Form:", oggettoForm.value) //si può mostrare in console l'oggetto
if (oggettoForm.valid){
//in caso di invio valido
}else{
//in caso si tenti di inviare un oggetto invalido
}
}

Nelle ultime versioni di Angular sono stati introdotti i form reattivi, il nome purtroppo è precisamente quello di "Reactive forms" che può generare confusione vista l'assonanza con il framwork Reactive, in realtà questa è a tutti gli effetti una libreria standard che si trova sulle versioni recenti di Angular e ormai il nuovo standard usato nella gestione dei form e delle validazioni dei campi.

Essendo i reactive forms interni al framework base non è necessario installare nulla e basta configurare l'import dei moduli nel app.modules e/o nel modulo di riferimento dei componenti

import { FormsModule, ReactiveFormsModule } from '@angular/forms';
imports: [

FormsModule, ReactiveFormsModule

, ...

]
Poi nel componente bisogna definire una proprietà interna
 
bookForm = newFormGroup({
id:newFormControl(),
title:newFormControl(),
author:newFormControl(),
type:newFormControl(),
});
E nel costruttore, oltre al injection del modulo bisogna definire i validatori
constructor(
privateformBuilder: FormBuilder,
privatebookService: BookService,
privateactivatedRoute: ActivatedRoute,
privatelocation : Location
) {

this.bookForm = this.formBuilder.group({
id :['', [Validators.required]],
title :['', [Validators.required, this.custumValidatorsMin10]],
author :[''],
type :['', [Validators.required]]
})
}
Poi nel metodo di caricamento dei dati si può valorizzare/modificare i dati del form, per esempio
this.bookService.getAll().subscribe ( (res : Book[]) =>{
letbooks : Book[] = res;
this.book = books.filter (el=>el.id===id)[0];
//load data into form
this.bookForm.controls['id'].setValue(this.book.id);
this.bookForm.controls['title'].setValue(this.book.title);
this.bookForm.controls['author'].setValue(this.book.author);
this.bookForm.controls['type'].setValue(this.book.type);
},(error) => { console.log(error); this.error=error;});
Nella componente html del componente basta definire un tag input associato al form, per esempio
<div class="col-12 _NO_mx-auto" [formGroup]="bookForm">
<p>
<label for="id">ID</label>
<input type="text" id="id" name="id" formControlName="id">
</p>
<p>
<label for="title">title</label>
<input type="text" id="title" name="title" formControlName="title" >
</p>
<p>
<label for="author">author</label>
<input type="text" id="author" name="author" formControlName="author">
</p>
<p>
<label for="type">type</label>
<select id="type" name="type" formControlName="type">
<option [ngValue]="book.type" *ngFor="let el of typeList"
[selected]="el===book.type">{{el}}</option>
</select>
</p>
<p>
<button type="submit" class="btn btn-primary"
[disabled]="!bookForm.valid" (click)="onSubmit()">Submit</button>
</p>
</div>
Come si vede dall'esempio bisogna racchiudere tutto in un tag con la proprietà [formGroup].
Il bottone di submit può essere velocemente disabilitato con la proprietà disabled collegata alla validità del form stesso. 
Per visualizzare i classici messaggi di errore in caso un campo non rispetti i requisiti di validazione si possono usare le proprietà del form, per esempio:
<div [hidden]="!bookForm.get('title').errors?.minlength">
Min length of title allowed is {{bookForm.get('title').errors?.minlength.requiredLength}}
, actual {{bookForm.get('title').errors?.minlength.actualLength}}
</div>
<div [hidden]="!bookForm.get('title').errors?.required">
Title is required
</div>
Mentre il metodo di submit può recuperare agilmente i dati dal form con nomeform.values.nomeProprietà oppure è possibile passare al metodo del service direttamente, per esempio
onSubmit() {
console.log(this.bookForm.value);
this.bookService.update(this.bookForm,this.book.id).subscribe ( (res : Book) =>{
console.log("done" + res);
this.goBack();
},(error) => { console.log(error); this.error=error;});
}
Tutte le proprietà del form possono essere gestite e visualizzate in pagina, per esempio
<div style=" m-5">
<h3>Form Status</h3>
<b>valid : </b>{{bookForm.valid}}
<b>invalid : </b>{{bookForm.invalid}}
<b>touched : </b>{{bookForm.touched}}
<b>untouched : </b>{{bookForm.untouched}}
<b>pristine : </b>{{bookForm.pristine}}
<b>dirty : </b>{{bookForm.dirty}}
<b>disabled : </b>{{bookForm.disabled}}
<b>enabled : </b>{{bookForm.enabled}}
<h3>Form Value</h3>
{{bookForm.value |json}}
</div>
L'esempio funzionante può essere scaricato dal GIT AngularBookExample
 

Con le varie librerie Angular è possibile eseguire l'upload e la manipolazione di file di testo csv con un piccolo progetto e poche righe di codice, in questo esempio eseguiremo anche una tabella HTML dove visualizzeremo i dati caricati ma un programmatore può caricare il contenuto in un database o altro. Il concetto è quello di convertire i dati dal file ad un array tipizzato (getDataRecordsArrayFromCSVFile).

Partendo da zero si può creare il progetto base

ng new AngularFileManager
cd AngularFileManager
ng serve

e sull'indirizzo http://localhost:4200/ dovrebbe rispondere il tool di base di angular. Poi si può importare la libreria grafica di bootstrap con il semplice comando

npm install bootstrap

e poi bisogna modificare il file "angular.json" aggiungengo la riga nella sezione css

"node_modules/bootstrap/dist/css/bootstrap.min.css"

Poi nel html bisogna creare un input file e una tabella, per esempio nel file app.component.html

<!-- barra di navigazione --->
<nav class="navbar navbar-expand-lg navbar-light bg-dark mb-3">
<div class="container-fluid">
<div class="navbar-header text-light">Tool file manager</div>
<div class="form-inline my-2 my-lg-0" *ngIf="(records.length>0)">
<button class="btn btn-outline-danger my-2 my-sm-0" type="button"
(click)="fileReset()">Restart</button>
</div>
</div>
</nav>
<div class="container-fluid -2"><!-- input per caricare il file -->
<div class="card " *ngIf="!(records.length>0)">
<h5 class="card-title">Selezionare il file</h5>
<input type="file" #csvReader name="Upload CSV" id="txtFileUpload"
(change)="uploadListener($event)" accept=".csv" />
</div>
<!-- tabella che visualizza i dati-->
<span *ngIf="records.length>0 " class="card ">
<h5 class="card-title">{{records.length}} record in input</h5>
<div style="max-height: 300px; overflow-y:scroll;">
<table class="minimalistBlack" class="table" >
<thead>
<tr><th>Id</th><th>Campo1</th> <th>Campo2</th> </tr>
</thead>
<tbody>
<tr *ngFor="let record of records;let i = index;">
<td> <span>{{record.id}}</span> </td>
<td> <span>{{record.campo1}}</span> </td>
<td> <span>{{record.campo2}}</span> </td>
</tr>
</tbody>
</table>
</div>
</span>
</div>

E poi bisogna modificare il typescript per gestire le fasi app.component.ts:

import { Component, ViewChild } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'file-manage';
//variabili generiche
public records: any[] = [];
@ViewChild('csvReader') csvReader: any;
//metodo per controllare il formato del file
isValidCSVFile(file: any) {
return file.name.endsWith(".csv");
}
//metodo per il caricamento
uploadListener($event: any): void {
let text = [];
let files = $event.srcElement.files;
if (this.isValidCSVFile(files[0])) {
let input = $event.target;
let reader = new FileReader();
reader.readAsText(input.files[0]);
reader.onload = () => {
let csvData = reader.result;
let csvRecordsArray = (<string>csvData).split(/\r\n|\n/);
this.records = this.getDataRecordsArrayFromCSVFile(csvRecordsArray, 3);
};
reader.onerror = function () {
console.log('error is occured while reading file!');
};
} else {
alert("Please import valid .csv file.");
this.fileReset();
}
}
fileReset() {
this.records = [];
}
//metodo per convertire una riga del file in un oggetto typescript CSVRecord
getDataRecordsArrayFromCSVFile(csvRecordsArray: any, headerLength: any) {
let csvArr = [];
for (let i = 0; i < csvRecordsArray.length; i++) {
let curruntRecord = (<string>csvRecordsArray[i]).split(';');
if (curruntRecord.length >= headerLength) {
let csvRecord: CSVRecord = new CSVRecord();
csvRecord.id = curruntRecord[0].trim();
csvRecord.campo1 = curruntRecord[1].trim();
csvRecord.campo2 = curruntRecord[2].trim();
csvArr.push(csvRecord);
}else{
let csvRecord: CSVRecord = new CSVRecord();
csvRecord.id ="Riga scatata per lunghezza errata len="+curruntRecord.length ;
csvArr.push(csvRecord);
}
}
return csvArr;
}
}
export class CSVRecord {
public id: any;
public campo1: any;
public campo2: any;
}

In questo esempio, come si vede alla classe CSVRecord, il file deve essere in csv con tre campi, per l'esempio non c'è nessuna validazione sul tipo, infatti in questo piccolo esempio non sono presenti validazioni sul formato dei dati nel metodo isValidCSVFile.

Questo piccolo progetto è scaricabile da GitHub all'indirizzo AngularFileManager

Partendo dal precedente articolo di upload di un file si può eseguire la creazione e il successivo download di un file csv partendo da un array tipizzato. In questo esempio viene usato lo stesso tipo dell'esempio del upload ma nella versione creata del file non viene compreso il secondo campo. 

Per la parte web basta aggiungere un bottone 

<button (click)="downloadFile()" class="btn btn-outline-success mt-2">Download file</button>

Per l'esportazione sono presenti due metodi: la generazione dei dati e il download del broswe 

public recordsOut: any[] = [];  // variabile d'appoggio
generateOut(){const stringaVuota="";const valoreUno="1";
let rec = [];
for (let i=1; i<=this.records.length;i++){ //per ogni record
let record : CSVRecord=this.records[i-1];
//per ogni recordo si crea una rigaOut nel nuovo formato
let rigaOut : string=record.id+";"+record.campo2+"\n";
rec.push(rigaOut);
}
this.recordsOut=rec;
}
downloadFile() { //metodo per il download da browser
this.generateOut();
//possibile cambiare formato da txt a csv per esempio
var blob = new Blob(this.recordsOut, {type: 'text/txt' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'file.txt';
a.click();
window.URL.revokeObjectURL(url);
a.remove(); //per scaricare si crea un elemento A e alla fine lo si rimuove
}

Questo piccolo progetto è scaricabile da GitHub all'indirizzo AngularFileManager

Esistono molti modi diversi per importare un file excel tramite un componente Angular, la libreria più famosa si chiama XLSX e può essere installabile con il npm come qualunque altra libreria, nel componente si deve importare la libreria

import * as XLSX from 'xlsx'; 

e poi si può usare come evento agganciato ad un input di tipo File, un semplice esempio di codice è:

uploadListener($event: any): void {
this.fileUploaded = $event.target.files[0];
let readFile = new FileReader();
readFile.onload = (e) => { let storeData: any;let worksheet: any;
storeData = readFile.result;
var data = new Uint8Array(storeData);
var arr = new Array();
for (var i = 0; i != data.length; ++i)
arr[i] = String.fromCharCode(data[i]);
var bstr = arr.join("");
var workbook = XLSX.read(bstr, { type: "binary" });
var first_sheet_name = workbook.SheetNames[0];
worksheet = workbook.Sheets[first_sheet_name];
let csvData = XLSX.utils.sheet_to_json(worksheet, {header: 1, defval: '' , raw: true});
//console.log(csvData[0]);
this.records = csvData;
}
readFile.readAsArrayBuffer(this.fileUploaded);
}

In questo esempio sono eseguiti alcuni passi base

  • viene definito un evento readFile.onload con la regola dei caricamento e richiamato l'evento con il metodo readFile.readAsArrayBuffer
  • il metodo XLSX.read legge il file excel e lo carica su un oggetto strutturato
  • il metodo XLSX.utils.sheet_to_json trasforma un foglio in un oggetto json, questo oggetto contiene una riga per ogni riga dell'excel

così facendo l'oggetto di ritorno è un json formattato dove la prima riga del excel verrà usata come header nelle proprietà dell'oggetto json, se si vuole invece avere chiavi specifiche bisogna aggiungere un ciclo per trattare i valori, per esempio:

if (csvData.length > 0){
this.headersRow=Object. keys(csvData[0]);
//console.log(this.headersRow);
this.records=[];
for (let j=0;j<csvData.length;j++) {
let row=csvData[j];
let obj=[];
for (let i=0;i<this.headersRow.length;i++){
obj[i]=row[this.headersRow[i]];
}
if (row[this.headersRow[0]]!==undefined)
this.records.push(obj);
}
}else{
alert("File vuoto o non corretto");
}

Questo esempio è da intendersi come esercizio e si rimanda alla documentazione ufficiale della libreria XLSX per maggiori informazioni.

L'aggiornamento della versione di Angular in un progetto potrebbe rivelarsi una procedura piuttosto complicata, non tanto per angular in se ma per tutte le dipendenze che le altre librerie hanno verso Angular, infatti molto spesso si preferisce rimanere in versioni vecchie del framework piuttosto che aggiornare la libreria base con il grande rischio di vedere il pom.xml pieno di dipendenze non rispettare. Un sito viene in aiuto di questa procedura : https://update.angular.io

In questo sito è possibile inserire la versione di partenza e quella di destinazione, a questo punto saranno elencati tutti i passi necessari per la migrazione in maniera pulita di angular e dei pacchetti principali (come il core). Bisogna sempre ricordare che prima di procedere si può eseguire un aggiornamento manuale delle versioni con il comando:

npm update --force

E poi per esempio per migrare dalla versione 12 alle 14 il sito "consiglia" di passare attraverso la versione 13 coni comandi:

ng update @angular/core@13 @angular/cli@13 --force
ng update @angular/core@14 @angular/cli@14

Da notare che il sito stesso suggerisce alcune verifica del file pom.xml per esempio indica che la versione 14 di Angular necessita della versione 4.4 di Typescript e indica che è necessaria la versione 12.20 di Node.js. I messaggi visualizzati nel sito sono:

Angular now uses TypeScript 4.4, read more about any potential breaking changes
Make sure you are using Node 12.20.0 or later

I programmatori Angular sono abituati ad eseguire l'injection dei servizi nel costruttore di componenti che ne necessitano l'uso, questo può comportare la presenza di tanti elementi nel costruttore che poi è un metodo che non fa nulla al proprio interno, per esempio:

constructor(private store: Store, private router: Router,
private actionListener$: ActionsSubject,
private alertController: AlertController
){ }

per superare questo problema e venire in contro ai programmatori più delicati che richiedevano un miglioramento estetico di questa tecnica, dalla versione 14 di Angular è stato introdotti il injection-diretto dalla libreria core. Per esempio:

import { HttpClient } from '@angular/common/http';
import { Component, inject, ViewChild } from '@angular/core';
export class AppComponent {
http=inject(HttpClient);//angular14 senza costruttore ma inject
ngOnInit(){
console.log("Http inject senza costruttore: ");
console.log(this.http);
}
}

In questo esempio injection viene importata dalla libreria core e viene eseguito senza l'uso del costruttore ma solo come attributo della classe del componente che si va a disegnare. Un esempio più complesso ma efficace di questa tecnica è l'uso del substribe su un elemento con injection, a titolo di esempio un injection sul routing è possibile con il codice

export function getRouterEvent(){
return inject(Router).events/*.pipe( filter(e => e instanceof NavigationEnd) )*/;
}
@Component({...})
export class AppComponent{
routerEvents=getRouterEvent();
constructor(){
this.routerEvents.subscribe( console.log );
}
}

In questo semplice esempio la subscribe viene eseguita sul metodo che esegue injection del Router, nonostante sembri uno scioglilingua il codice è molto più semplice di quanto sembri.

Dalla versione 14 di Angular è possibile creare dei componenti e di usarli senza doverli importare nei moduli dell'applicazione per eseguirne il routing. Un semplice esempio pratico che mostra la differenza può essere impostato con i comandi:

$ npm i -g @angular/cli@next
$ ng new angular14-demo -s -S -t
$ ng g c home --standalone --flat
$ ng g c contact --flat

Nel template principale basta aggiungere due bottoni per il caricamento delle rotte:

<button routerLink="home">home</button>
<button routerLink="contact">contatti</button>
<router-outlet></router-outlet>

Utilizzando proprio la tecnica del standalone, nella definizione delle rotte si può importare il file direttamente senza dover importare nel modulo la classe Component specifica, per esempio:

const routes: Routes = [ 
{path:'home', loadComponent: ()=>import('./home.component').then(c=>c.HomeComponent)},
{path:'contact',component:ContactComponent}
];

Nonostante sembra un tecnica poco utile, in progetti con migliaia di progetti può essere una tecnica molto utile per semplificare e snellire il codice.

Nelle ultime versioni di Angular, quando viene creato un progetto, di default il compilatore viene configurato in modalità "Strict", questo viene definito e salvato nel file tsconfig.json con la configurazione

"strict":"true"

questo implica che in fase di compilazione possano esserci degli errori che nelle precedenti versioni non si verificavano. Nello specifico ci sono alcuni controlli ulteriori che vengono attivati e possono essere disattivati sempre nel file di configurazione aggiungendo specifiche configurazioni. Per chi proviene da versioni di Angular precedenti alla 11 conviene disattivare tutta la modalità strict. Alcuni dei controlli attivati dal compilatore con questa modalità sono:

NoImplicitAny: impone di tipizzare ogni proprietà di classe qualunque essa sia, per esempio il controllo si attiva con il codice:

export class TestComponent {
value;
}

con un errore del tipo member value implicitly has an 'any' type. Per risolvere l'errore basta aggiungere il tipo any alla variabile:

export class TestComponent {
value: any;
}

StrictPropertyInizialization: impone di valorizzare le proprietà tipizzate anche semplici, per esempio viene generato errore dal codice:

export class TestComponent {
value: number;
}

con un errore del tipo Property value has no initializer and is not definitely assigned in the costructor". Per risolvere l'errore basta aggiungere la valorizzazione nella dichiarazione o nel costruttore:

value: number=42;
array: any[] =[];

StrictNullCheck: impone che i tipi NON siano null a meno che non sia specificato. Per esempio viene generato errore dal codice:

value: number=null;

con un errore del tipo Type null is not assignable to type number. Per risolvere l'errore si può aggiungere al tipo il "null" come alternativa:

value: number | null | undefined =null;

il vantaggio di attivare la strict mode senza l'uso del pipe null è non trovarsi errori in runtime, come dal codice

contructor(){
const id=this.user.id;//errore se user è nullo
}

che genera un errore del tipo Object is possibly null mentre per risolvere si possono usare i due modi:

//Istruzione condizionale nel valore null
if (this.user){
const id=this.userid;//.map(u => u.id) per gli array
}

//Uso del ? che genera valore undefinined
console.log( this.user?.id ) //uso per ? che genera undefined

Talvolta è necessario rendere disponibile il download di un file che si trova all'interno del sito stesso, in teoria questo sarebbe vietato in quanto il framework angular non lo permette nativamente, esistono diverse librerie estensioni che espongono metodi utili a questo scopo. In maniera semplice è possibile utilizzare istruzioni Javascript all'interno di un componente Angular e poi usare la cartella assets come appoggio per questo tipi di documenti, in ordine si crea un link fittizio con il parametro href puro, poi si clicca sul link stesso per avviare il download e poi si elimina il link. Il codice di esempio è:

downloadTemplate(){
let link = document.createElement('a');
link.setAttribute('type', 'hidden');
link.href = 'assets/template.xlsx";
document.body.appendChild(link);
link.click();
link.remove();
}

in questo esempio la funzione è eseguibile con un semplice bottone o da un'altra funzione.Questa semplice tecnica, come qualsiasi altra libreria che usa la cartella assets, necessita della "ricompilazione" tramite il comando "build" e il deploy ogni volta che deve essere modificato il file target quindi può risultare scomoda se il file in questione è soggetto a frequenti modifiche.

Lo SlicePipe è una tecnica che permette di controllare il numero di occorrenze all'interno di un ciclo ngFor, questa tecnica è molto usata per evirare di mostrare troppe righe in una pagina se la sorgente dei dati ritorna un numero molto elevato di record che renderemmo il rendering molto lento. L'esempio più semplice è:

<ul *ngFor="let record of recordsIn | slice:0:1000;let i = index;"> 
<li>{{record}}</li>
</ul>

La sintassi infatti prevede che dopo la definizione della lista si aggiunga un pipe e il comando slice con due parametri: l'indice di inizio e il secondo, opzionale, l'indice di fine. Bisogna sempre tenere a mente che gli indici iniziano da zero ed immettendo un numero di fine più grande del numero di elementi non si genera errore. La documentazione di questa tecnica è nel sito ufficiale

Il framework Angular permette di sviluppare classi interceptor per catturare qualsiasi chiamata alle API di un backend, questa tecnica è usata per aggiungere un token JWT nel caso di utente autenticato, questo permette di non dover impostare il token nelle classi service visto che spesso queste possono essere decine o migliaia in caso di un numero molto elevato di risorse API. La definizione della classe interceptor si deve censire nel modulo principale aggiungendo:

import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { JwtInterceptor } from 'libs/example-central-lib/src/lib/services/auth.service';
...
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true }
],

e la classe JwtInterceptor implementa una interfaccia e deve descrivere il comportamento: per ogni chiamata http aggiunge il token se è presente un token, in questo esempio viene cercato dal selector del redux:

@Injectable()
export class JwtInterceptor implements HttpInterceptor {
constructor(private store: Store) { }
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// add auth header with jwt if user is logged in and request is to api url
const token$= this.store.pipe(select(getJwtToken ) );
let token = null;
token$.subscribe( v => token=v );
if (token && request.method!=='OPTION') {
request = request.clone({
setHeaders: { //...request.headers,
Authorization: `Bearer ${token}`
}
});
}
return next.handle(request);
}
}

Da notare che questa modifica può creare un errore di tipo CORS perchè, se il server gestire il cors bisogna aggiungere "Authorization" come tipo permesso, per esempio:

header('Access-Control-Allow-Headers: token, Content-Type, Authorization');

questo errore può essere subdolo e poco parlante nel frontend perché nemmeno dalle console dei browser è visibile il motivo di tale blocco CORS.

I contenuti di AlNao.it potrebbero avere inesattezze o refusi. Non potrà in alcun caso e per qualsiasi motivo essere ritenuta responsabile di eventuali imprecisioni ed errori né di danni causati. Il sito web e tutte le informazioni ed i contenuti in esso pubblicati potranno essere modificati in qualsiasi momento e di volta in volta e senza preavviso. Poiché ogni materiale sarà scaricato o altrimenti ottenuto attraverso l’uso del servizio a scelta e a rischio dell’utente, ogni responsabilità per eventuali danni a sistemi di computer o perdite di dati risultanti dalle operazioni di scarico effettuato dall'utente, ricade sull'utente stesso e non potrà essere imputata ad AlNao.it che declina ogni responsabilità per eventuali danni derivanti dall'inaccessibilità ai servizi presenti sul sito o da eventuali danni causati da virus, file danneggiati, errori, omissioni, interruzioni del servizio, cancellazioni dei contenuti, problemi connessi alla rete, ai provider o a collegamenti telefonici e/o telematici, ad accessi non autorizzati, ad alterazioni di dati, al mancato e/o difettoso funzionamento delle apparecchiature elettroniche dell’utente stesso.

AlNao.it ha adottato ogni possibile accorgimento al fine di evitare che siano pubblicati, nel sito web, contenuti che descrivano o rappresentino scene o situazioni inappropriate o tali che, secondo la sensibilità degli utenti, possano essere ritenuti lesivi delle convinzioni civili, dei diritti umani e della dignità delle persone, in tutte le sue forme ed espressioni. In ogni caso non garantisce che i contenuti del sito web siano appropriati o leciti in altri Paesi, al di fuori dell’Italia. Tuttavia, qualora tali contenuti siano ritenuti non leciti o illegali in alcuni di questi Paesi, ti preghiamo di evitare di accedere al nostro sito e ove scegliessi, in ogni caso, di accedervi, ti informiamo che l’uso che deciderai di fare dei servizi forniti dal sito sarà di tua esclusiva e personale responsabilità. L’utente sarà esclusivo responsabile della valutazione delle informazioni e del contenuto ottenibile mediante il sito web. Il sito web e tutte le informazioni ed i contenuti in esso pubblicati potranno essere modificati in qualsiasi momento e di volta in volta e senza preavviso.

Le pagine di questo sito sono protette dal diritto d’autore (copyright). In particolare a norma della legge sul diritto d’autore e il contenuto del sito è protetto contro duplicazioni, traduzioni, inserimento o trasformazione dello stesso in altri media, incluso l’inserimento o la trasformazione con mezzi elettronici. La riproduzione e lo sfruttamento economico di tutto o di parte del contenuto di questo sito sono consentite solo a seguito del consenso scritto dell’avente diritto. Sia il contenuto che la struttura del sito sono protetti dal diritto d’autore. In particolare, la duplicazione di informazioni o dati, l’uso dei testi o di parte di essi o delle immagini contenute nel sito (eccetto per le foto ad uso stampa) è consentita solo previo consenso scritto dell’avente diritto. Anche le illustrazioni, a norma dell’art. 1 della legge 633/1941 – e successive modifiche e integrazioni – sono protette dal diritto d’autore. Il diritto di pubblicazione e riproduzione di questi disegni è di titolarità dell’avente diritto. Il diritto d’autore sui disegni rimane in vigore anche per i disegni automaticamente o manualmente aggiunti a un archivio. Nulla di quanto contenuto in questo sito vale come concessione a terzi dei diritti di proprietà industriale ed intellettuale indicati in questa sezione. Ci riserviamo tutti i diritti di proprietà intellettuale del sito web.

Marchi ed immagini riferibili a soggetti terzi utilizzati in questo sito non appartengono a AlNao.it e sono da ritenersi di proprietà esclusiva dei rispettivi titolari. Le denominazioni dei prodotti pubblicati su questo sito web o delle società menzionate, anche qualora non siano indicate con il simbolo identificativo della registrazione del marchio sono marchi di titolarità di terzi e sono protetti dalla legge sui marchi (D.lgs. 30/2005 e successive modifiche e integrazioni) e dalle norme in tema di concorrenza sleale. Qualsiasi riproduzione degli stessi è da ritenersi vietata ai sensi di legge. In particolare è espressamente vietato qualsiasi uso di questi marchi senza il preventivo consenso scritto del relativo titolare ed, in particolare, è vietato utilizzarli in modo da creare confusione tra i consumatori in merito all'origine dei prodotti o per finalità di sponsorizzazione, nonché in qualsiasi modo tale da svilire e discreditare il titolare del marchio. Tutti i diritti che non sono espressamente concessi sono riservati al titolare del marchio.

In aggiunta a quanto indicato in altre previsioni delle Condizioni Generali d’Uso, AlNao.it non potrà essere ritenuta in alcun caso responsabile di alcun danno derivante dall'utilizzo o dall'impossibilità di utilizzare il sito web, i contenuti, le informazioni o connessi alla qualità degli stessi.