- Inizializzazione ambiente
- Versioni
- Funzionamento
- Local vs Global
- Typescript
- Binding
- Angular Fundamentals
- Component API
- Form
- Consuming HTTP service
- Routing
- Autenticazione e autorizzazione
- Deployment
- API end-point lato-client
- Risorse
Inizializzazione ambiente
- Installare node.js (https://nodejs.org/it)
- Installare librerie di terze parti tramite NPM (Node Package Manager)
-
-- Per sapere quali librerie sono già installate in node.js npm [-g] ls --depth=0 ng --version -- Aggiornare tutte le librerie di terzi referenziate nel nodo "dependencies" nel file <package.json> -- Se si indica la versione della dipendenza con ^ o ~ , npm potrebbe non usare l'esatta versione indicata -- Può così modificare il file package-lock.json se ci sono dei cambiamenti nelle versione o nuove librerie npm install -- Elimina i file in node_modules per assicurare uno stato pulito -- Guarda alle dipendenza in package-lock.json e installe tutte le dipendenza con l'esatta versione -- Non modifica il file package-lock.json npm ci -- Installare Angular CLI (globalmente) npm install -g @angular/cli@latest -- Installare Angular CLI (in locale) npm install --save-dev @angular/cli@latest -- Installare bootstrap npm install boot --save (--save aggiunge la dipendenza bootstrap nel file /package.json) -- aprire il file <style.css> e fare l'import della libreria css di bootstrap @import "~bootstrap/dist/bootstrap.css
-
- Creo Cartella nuovo progetto
-
//con la shell mi posiziono nella cartella scelta per il nuovo progetto e lancio il comando: ng new <project-name> ng new --help ng new <project-name> --minimal --skip-git --style css --strict --routing false --directory <folder_name> //con la shell mi posiziono nella cartella scelta e apro 'Visual Studio Code' code . //nel folder dell'applicazione lancio il seguente comando attivando Angular Live Development Server //l'applicazione è raggiungibile a http://localhost:4200/ ng serve [dietro le quinte viene chiamato anche il compiler typescript] //eseguo l'applicazione ng start //cartella generica progetto Angular /e2e [contiene test end-to-end (simulano attività degli utenti) per testare l'appli] /node_modules [risiedono tutte le librerie di terze parti, le dipendenze veramente usate verranno messe in un bundle e deployate sul server di produzione compilando l'applicazione] /src [contiene l'applicazione vera e propria] /app [contiene moduli + componenti] /assets [contiene gli asset static (immagini, icone, file)] /environment [contiene le configurazioni] index.html [pagine iniziale] main.ts [punto di partenza dell'applicazione (=startup per console windows application)] polyfills.ts [fa il download di alcuni metodi javascript necessari ad Angular ma che non tutti i browser supportano] style.css [global css style setting] test.ts [setto configurazione per i test] .editorconfig [è importante che i membri di un team abbiano gli stessi settaggi per l'editor] .gitignore karma.conf.js [è un test runner per javascript code. Permett di gestire test] package.json [file usato da ogni app nodejs, contiene info generali sull'app. Le dipendenze alle librerie di 3° parti usate in prod e le dipendenze usate durante lo sviluppo] tsconfig.json [settaggi per il typescript compiler]
-
Versioni
- AngularJs => Scritto in javascript nel 2010
- Angular => Scritto in typescript a partire dal 2016
Funzionamento
- Angulare è costituito di alcune librerie che possono essere scaricate tramite NPM
- @angular/core
- @angular/compiler
- @angular/http
- @angular/router
- webpack =>
- Quando modifichiamo la nostra applicazione viene, l’applicazione viene compilata automaticamente e un tool di bundling aggrega i file js e css di cui l’applicazione ha bisogno in un solo file di bundle e lo rende disponibile sul server.
- HTM (Hot module reloading) => Permette di vedere i cambiamenti apportati alla pagina senza nemmeno fare il refresh del browser.
- Anche i file css vengono compilati in un bundle javascript
Local vs Global
- Local => le librerie installate in locale (nella root del progetto) possono essere riferenziate all’interno del progetto stesso tramite using import { ‘whatever’}
- Global = le librerie installate globalmente possono essere richiamate tramite nella shell (i binari finiscono nella variabile globale PATH)
Typescript
- Superset di javascript
- Strong typing
-
//dichiarazione di variabili let a:number; let b:string; let c:boolean; let d:number[] = [1,2,3]; let e:any = [1,true, 'a']; //enum enum Color { Red = 0, Green = 1, Blue = 2 }; let background = Color.Red;
-
- OOP, class, interfaces..
-
//interfaccia interface Point { x:number, y:number, draw: () => void } //classi //usare (?) permette di rendere i parametri opzionali //public = default class Point { constructor(private _x?:number, private _y?:number){ .. } //proprietà X get x() { return this._x; } set x(value){ if(value < 0) throw new Error('Value is not correct'); this._x = value; } draw() { console.log('X: ' + this._x + ', Y: ' + this._y); } getDistance() { ... } } let point = new Point(1, 2); let x = point.X; point.X = 10; point.draw();
-
- Catch compile-time errors
- Strong typing
- Ogni volta che si compila un’applicazione typescript viene “traspilato” in javascript (I browser non interpretano direttamente typescript.
- Il compilatore typescript può decidere di compilare i file js rispettando le specifiche ES5 (ignoroando eventuali errori dovuti a ES6).
- Installare globalmente typescript
-
npm install -g typescript //visualizzo la version del compilatore typescript tsc --version //eseguo il file javascript node <my-file.js>
-
- Type assertion: è possibile indicare al compiler quale tipo ha la mia variabile, Senza che questo sia modificato a runtime
-
let message; let endsWithC = (<string>message).endsWith('c'); let endsWithC = (message as string).endsWith('c');
-
- Arrow function (equivale a lamda expression in C#)
-
//normal syntax let log = function(msg) { console.log(msg); } //arrow function syntax let log => (message) => { console.log(msg); }
-
- Moduli typescript
- Invece di scrivere tutto il codice typescript in un unico file, creo molti file ts
- Un modulo è un file con la parola chiave export o import.
- Un modulo può definito in un file e importato e utilizzato in un altro file typescript della mia applicazione
-
//nel file shapes.ts definisco 2 classi e le esporto export class Point { .. } export class Square { .. } //nel file main.ts importo le due classi dal file square.ts e le uso //se importo moduli definiti nell'applicazione in from utilizzerò il path relativo import { Point, Square } from './square let point
-
Binding
- Property binding & String interpolation
- Quando un <component> viene compilato, Angular è capace di legare (bind) le prorietà degli elementi del DOM alle proprietà del <component>. Per forzare il bind è sufficiente mettere tra parentesi quadre tale proprietà (vedi esempio sotto)
-
//E' possibile mischiare del codice HTML con delle variabili definite nel <component> tramite -- string interpolation syntax <img src={{ imageUrl }} /> oppure -- property binding syntax (component -> view) <img [src]="imageUrl" /> -- component export class MyComponent { imageUrl = <img_path_url>; isActive = true; } ----------------------------------------------------------------------- Regola generale - per i testi (in <p>, <span>, <div>) è più succinta la sintassi string interpolation - per settare le proprietà degli elemnti del DOM è più succinta la sintassi property binding
- Attribute binding
- Normalmente esiste un mapping 1 a 1 tra gli attributi degli elementi HTML e gli attributi degli elementi del DOM.
- Esistono invece alcuni casi dove invece un attributo presente nell’HTML non ha corrispondenza nel DOM.
- In questo caso, è necessario usare una sintassi leggermente diversa per poter utilizzare il binding
-
<tr> <td [attr.colspan]="colspan"></td> </tr>
-
- Class and style binding
-
//Se la variabile <isActive> del nostro <compoment> è = true allora aggiungo //all'elemento <button> la classe <active> altrimenti se <isActive>=false non aggiungo niente <button class="btn btn-primary" [class.active]="isActive" [style.backgroundColor]="isActive ? 'blue' : 'red'">Press!</button> --component export class MyComponent { isActive = true | false; }
-
- Event binding / Event filtering / Template variables
-
<button (click)="onSave($event)">Press!<button> //Event filtering and template variables <input #email type="text" (keyup.enter)="onKeyUp(email)" /> export class MyComponent { onSave($event:any) { .. //evita l'event bubbling. Interrompe cioè la propagazione dell'evento agli elementi superiori del DOM $event.stopPropagation(); } onKeyUp(email:any) { console.log(email); } }
-
- Two-way binding
- Normalmente il property binding è dal <component> => <view>. E’ possibile abilitare il binding bidirezionale. Per vedere anche nel <component> le modifiche fatte nel <view>.
-
<input [(ngModel)]="email" type="text" (keyup.enter)="onKeyUp" /> export class MyComponent { email = "indicare una mail valida!"; onKeyUp(){ console.log(this.email); } } //app.module.ts => E' necessario importare esplicitamente gli angular FormsModule import { FormsModule } from '@angular/forms'; @NgModule({ imports: [ FormsModule ] })
Angular Fundamentals
-
- Components
- Definiscono le parti i cui è stata suddifica l’interfaccia utente.
- Suddividere il mio codice in più componenti mi permette di scrivere del codice più leggibile e riusabile.
- Un’applicazione Angular è essenzialmente un tree di componenti che partonto dal componente App (root)
- Ogni component contiene:
- Data
- HTML template
- Logic
- Moduli => un modulo angular contiene un gruppo di componenti correlati tra loro.
- Ogni applicazione Angular ha almeno un modulo root chiamato App module.
- Ogni applicazione Angular ha almeno un component root chiamato App component.
- Aggiungere un componente manualmente
- Crearlo (courses.component.ts)
-
//courses.component.ts => creo il componente CoursesComponent //E' una classe con la dichiarazione di un @Component //e nel cui nome per convenzione si sua la parole Component import { Component } from '@angular/core'; @Component({ selector: 'courses', //definisco il tag <courses> da usare //per poter aggiungere questo componente al nostro HTML template: '<h2>{{ 'Title: ' + title }}</h2>' [oppure templateUrl: './app.component.html'] }) export class CoursesComponent { title = 'my title'; }
-
- Registrarlo in un modulo
-
import { CoursesComponent } from './course.component'; //apro app.module.ts //aggiungo il componente CoursesComponent a quelli già dichiarati in @NgModule @NgModule({ declarations: [ AppComponent, CoursesComponent ], ... })
-
- Aggiungerlo in un HTML markup
-
//Nel file app.component.html (la home page) <courses></courses>
-
- Crearlo (courses.component.ts)
- Aggiungere un componente tramite CLI
-
//g=generate //c=component ng g c <component-name> (es: courses, senza la parola Component) -- creando una cartella ng g c vehicle-form --module ../app (src/app/vehicle-form/vehicle-form.component.ts) //tramite il comando sopra Angular esegue create src/app/course/<component-name>.component.css create src/app/course/<component-name>.component.html create src/app/course/<component-name>.component.spec.ts create src/app/course/<component-name>.component.ts update src/app/app.module.ts Una volta creato il component è necessario aggiungere un nuovo root in app.module.ts per mappare l'URL al componente
-
- Directives (comandi speciali per manipolare il DOM)
- Structural => modificano la struttura del DOM
- Attribute => modificano gli attributi del DOM
-
-- Angulare traduce l'asterisco (*) iniziale presente nelle direttive con <ng-template ..> -- change detection (Angular di default si accorge dei cambiamenti in memoria delle variabili e rilancia automaticamente il rendering dell'elemento cambiato) -- trackBy (per migliorare le performance è possibile passare da trackBy:memoria a trackBy:id) -- exported values 1- ngFor @Component({ selector: 'courses', template: ` <h2>{{ title }}</h2> <ul> <li *ngFor="let course of courses; index as i; first; last; even; odd"> {{i}} - {{ course }} </li> </ul>` }) export class CoursesComponent { title = "List of courses"; courses = []; //dependency injection constructor(service:CoursesService) { this.courses = service.getCourses(); } } 2- ngIf <div *ngIf="courses.length > 0">Lista dei corsi</div> <div *ngIf="courses.length == 0">Nessun corso</div> oppure <div *ngIf="courses.lenght > 0; then coursesList; else noCourses"></div> <ng-template #coursesList>List of courses!</ng-template> <ng-template #noCourses>No courses!</ng-template> oppure (per piccoli tree, perchè nasconde senza eliminare dal DOM) <div [hidden]="courses.lenght == 0">List of courses!</div> <div [hidden]="courses.lenght > 0">No courses!</div> 3- ngSwitchCase <div [ngSwitch]="courses.lenght"> <div *ngSwitchCase="'0'">Zero corsi</div> <div *ngSwitchCase="'1'">Un corsi</div> <div *ngSwitchDefault>Molti corsi</div> </div> 4- ngClass (attribute directive) <span [class.glyphicon-star]="isSelected" [class.glyphicon-star-empty]="!isSelected"></span> oppure <span [ngClass]="{ 'class.glyphicon-star': isSelected, 'class.glyphicon-star-empty': !isSelected }"></span> 5- ngStyle (attribute directive) <button [ngStyle]="{ 'backgroundColor': canSave ? 'blue' : 'gray', 'color': canSave ? 'white' : 'black', }"></button>
- Custom directives
-
-- creo un nuovo custom-directive ng g d <directive-name> -- app.component.html <input type='text' [<directive-name>]="'uppercase'" /> -- <directive-name>.directive.ts import { HostListener, ElementRef, Input } from '@angular/core'; @Directive({ selector: '[appInputFormat]' }) export class InputFormatDirective { @Input('appInputFormat') format:any; constructor(private el: ElementRef){ .. } //sull'evento onBlur faccio diventare lowercase il contenuto del campo @HostListener('blur') onBlur() { let value:string = this.el.nativeElement.value; if(this.format == 'uppercase') this.el.nativeElement.value = value.toLowerCase(); else this.el.nativeElement.value = value.toLowerCase(); } }
-
- Services
- Esternalizzare la logica (per recuperare i dati da visualizzare nel <component>) in un altro tipo di classi chiamato <service> permette di =>
- Evita che la presentazione (<components>) e la logica di recupero dati (<services>) siano fortemente accoppiati
- Da’ la possibilità di riutilizzare il codice scritto in un <service>
-
//courses.service.ts //E' una semplice plain text class con la convezione di avere la parola Service nel proprio nome //Decoratore @Injectable: è necessario inserire questo decorator solo //se il nostro servizio ha bisogno di dipendenze nel suo costruttore //Non è invece da esplicitare nel caso di un <component> avendo utilizzato //già il decoratore @Component che lo include già [ @Injectable({ providedIn: 'root' }) ] export class CoursesService { constructor(){ .. } getCourses() { return ["course1", "course2", "course3"]; } }
- Aggiungere un componente tramite CLI
-
//g=generate //s=service ng g s <service-name> (es: courses, senza la parola Service) //tramite tale comando Angular esegue: create src/app/<service-name>.service.spec.ts create src/app/<service-name>.service.ts
-
- Esternalizzare la logica (per recuperare i dati da visualizzare nel <component>) in un altro tipo di classi chiamato <service> permette di =>
- Components
- Dependency injection (DI)
- Per usare un <service> in un <component> si utilizza un componente presente in Angular che implementa il meccanismo della DI.
- Bisogna registrare le varie dipendenze di tutti i componenti di un modulo
-
//app.module.ts @NgModule({ providers: [ CoursesService, //E' possibile dire a Angular di usare una classe (AppErrorHandler) al posto di un altra (ErrorHandler) { provide: ErrorHandler, useClass: AppErrorHandler } ] })
- Quando Angular vede che il <component> ha delle dipendenze registrate, prima di crearne un’instanza crea le istanze di tali dipendenze.
-
- Singleton: quando si registra una dipendenza come <provider> in un <module>, Angular crea un’unica istanza di tale dipendenza da “iniettare” in tutti i componenti del modulo che la richiedono.
- Formatting data
- Build-in pipes
-
<h2>{{ fistName | uppercase }}</h2> <h2>{{ rating | number:'5.2-2' }}</h2> (5 interi, 2 decimali) <h2>{{ price | currency:'EUR':true:'3.2-2' }}</h2> (3 interi, 2 decimali) <h2>{{ date | date:'shortDate'}}</h2> export class MyComponent() { firstName; }
-
- Custom pipes
-
1- creare il nuovo file => <custom-pipe-name>.pipe.ts import {Pipe, PipeTransform } from '@angular/core'; @Pipe({ name: summary }) export class <custom-pipe-name>Pipe implements PipeTransform { transform(value:any, args?:any) { return .. } } 2- modificare il file => app.module.ts @NgModule({ declarations: [ <custom-pipe-name>Pipe ] }) 3- usare il nostro pipe customizzato in un componente {{ longText | <custom-pipe-name>Pipe }} 0- generare Pipe con Angular CLI ng g p <custom-pipe-name>
-
- Build-in pipes
Component API
- Component input properties
-
-- file app.component.html //il componente <app.component> padre passare un valore / alla proprietà isFavorite del componente <favorite> <favorite isFavorite="post.favorite"></favorite> -- file app.component.ts export class AppComponent { post = { title: "Title", isFavorite = false } } -- <component> favorite -- // METODO 1 (uso direttiva @Input) import { Input } from '@angular/core'; export class FavoriteComponent { -- espongo la proprietà isFavorire @Input() isFavorite; } // METODO 2 (uso @Component) @Component({ inputs: ['isFavorite'] }) export class FavoriteComponent { isFavorite; }
- Alias Input properties
-
E' possibile dare un alias alle proprietà di un componente che si decide di rendere visibili all'esterno Nel nostro esempio la proprietà isFavorite del componente <favorite> diventa 'is-favorite' all'esterno -- file app.component.html <favorite [is-favorite]="post.favorite"></favorite> -- <component> favorite @Input('is-favorite') isFavorite
-
-
- Component output properties
-
-- file app.component.html <favorite (change)="onFavoriteChanged($event)"><favorite> -- file app.compoment.ts import { FavoriteChangedEventArgs } from /.favorite/favorite.component'; export class AppComponent { onFavoriteChanged(eventArgs:FavoriteChangedEventArgs ) { console.log("Favorite has changed!" + eventArgs.newValue); } } -- component <favorite> import {Output, EventEmitter } from '@angular/core'; export class FavoriteComponent { isFavorite: boolean = false; @Output() change = new EventEmitter(); onClick() { this.isFavorite = !this.isFavorite; this.change.emit({newValue:this.isFavorite}); } } export interface FavoriteChangedEventArgs { newValue:boolean; }
- Alias output properties
-
E' possibile dare un alias alle proprietà di un componente che si decide di rendere visibili all'esterno Nel nostro esempio la proprietà isFavorite del componente diventa 'is-favorite' all'esterno -- file app.component.html <favorite (my-change)="onFavoriteChanged($event)"></favorite> -- <component> favorite @Ouptup('my-change') change = new EventEmitter(); onClick() { this.change.emit(); }
-
-
- ViewEncapsulation
- Emiulated => Angular cerca di emulare il comportamento del Shadow DOM. Un stile applicato ad un componente sarà valido solo in quel componente. Non viene propagato (non fa ombra) sugli altri elementi della pagina esterni a quel componente
- Native => Angular non emulare il comportamento del Shadow DOM ma lo lascia gestire al broser stesso. (Solo i browser moderni lo fanno).
- None =>
-
@Component({ encapsulation: ViewEncapsulation.Emulated [default] | Native | None })
-
- ngContent / ngContainer
-
-- app.content.html <my-panel> <ng-container class='heading'>Titolo</ng-container> <ng-container class='body'>Ipse dixit, Ipse dixit..</ng-container> oppure <ng-container id='heading'>Titolo</ng-container> <ng-container id='body'>Ipse dixit, Ipse dixit..</ng-container> </my-panel> -- <my-panel>.component.html //Nel componente <my-panel> definisco due placeholder richiamabili dall'elemento subscriber //come fosse delle classi o degli id in css <div class='panel panel-default'> <div class='panel-heading'> <ng-content select='.heading'></ng-content> oppure <ng-content select='#heading'></ng-content> </div> <div class='panel-body'> <ng-content select='.body'></ng-content> oppure <ng-content select='#body'></ng-content> </div> </div>
-
Form
- Form Template-driven (Angular crea automaticamente le cose in background)
- Quando usarli
- Per form più semplici
- Con validazione semplice
- Più facili da creare
- ngForm / ngSubmit =>
- Angular applica automaticamente al <form> la direttiva ngForm che crea la classe formGroup (rappresenta tutti i campi)
- Espone la proprietà ngSubmit che serve per catturare il contenuto del form
- ngModelGroup =>
- Se il form è molto grosso è possibilie opzionalmente suddividerlo applicando delle direttive ngModelGroup (rappresenta un gruppo di campi)
- ngModel =>
- Applicando la direttiva ngModel al campo del form, Angular gli attacca automanticamente formControl (rappresenta un campo)
-
@import { FormsModule } from '@angular/forms'; //Applicando ngModel a l'attributo name ad un campo del nostro form Angular crea automaticamente //un'instanza della classe controlForm e la associa a tale campo <input ngModel name='firstName' ...>
- Validation / ngValue =>
-
-- contact-form.component.html //Esempio di validazione del campo 'firstName' <form #f="ngForm" (ngSubmit)="submit(f)"> <div ngModelGroup="contact" #contact="ngModelGroup"> <div #ngIf="!contact.valid">...</div> <!-- messaggio d'errore per il gruppo di campi --> <div class="form-group"> <!-- input text --> <label for="firstName">First Name</label> <input required minlength="3" pattern="banana" ngModel name="firstName" id="firstName" type="text" class="form-control" #firstName="ngModel" (change)="log(firstName)" /> <!-- validation messages --> <div class="alert alert-danger" *ngIf="firstName.touched && !firstName.valid"> <div *ngIf="firstName.errors?.['required']">First name is required</div> <div *ngIf="firstName.errors?.['minlength']">First name should be {{ firstName.errors?.['minlength']['requiredLength'] }} minimum charactes</div> <div *ngIf="firstName.errors?.['pattern']">First name doesn't match the pattern</div> </div> </div> </div> <!-- checkbox --> <div class="checkbox"> <label> <input type="checkbox" ngModel name="isSubscribe" /> Subscribe to mailing list </label> </div> <!-- dropdown list --> <div class="form-group"> <label for="contactMethod">Contact Method</label> <select ngModel name="contactMethod" id="contactMethod" class="form-control"> <option *ngFor="let m of contactMethods" [value]="m.id">{{ m.name }}</option> </select> </div> <!-- radio button --> <div *ngFor="let m of contactMethods" class="radio"> <label> <input ngModel type="radio" name="contactMethod" [value]="m.id" /> {{ m.name }} </label> </div> <!-- radio button sulla stessa linea --> <label for="radio1" class="radio-inline"> <input type="radio" id="radio1" name="group1" /> </label> <label for="radio2" name="group1" class="radio-inline"> <input type="radio" id="radio2" name="group1" /> </label> <!-- per debug --> <p>{{ f.value | json }</p> <!-- submit button --> <button class="btn btn-primary" [disabled]="f.valid">Submit</button> </form> -- contact-form.component.ts export class ContactFormComponent { contactMethods = [ { id:1, name: 'email' }, { id:2, name: 'phone'}, ]; submit(f:any) { .. } } -- CSS .form-control.ng-touched.ng-invalid { border:2px solid red; }
-
- Quando usarli
- Form Reactive (anche Model-driven, lo sviluppatore ha piena libertà e sviluppa tutto ad hoc)
- Quando usarli
- Per form più complessi
- Validazione può essere customizzata a piacimento
- Unit testable
- AbstractControl => E’ la classe base per le classi FormControl, FormGroup, and FormArray
- E’ necessario esplicitare la struttura nel nostro <form> nel componente. (usare formBuilder)
- E’ possibile usare 3 direttive per collegare gli elementi del DOM con il componente
- formGroup
-
collega l'elemento <form> con la variabile form dichiarata nel componente <form [formGroup]="form">
-
- formGroupName
-
<div formGroupName="contact"> <div formControlName="email"> <div formControlName="address"> </div>
-
- formControlName
-
<input formControleName="username" />
-
- formArray
-
<li *ngFor="let item of topics.controls"> -- form; constructor(fb:FormBuilder){ this.form = fb.group({ topics: fb.array([]) }) } get topics(){ return this.form.get('topics') as FormArray; }
-
- formGroup
- FormControl
-
-- app.module.ts //aggiungere il modulo ReactiveFormsModule nella sezione imports @NgModule({ imports: [ ReactiveFormsModule ] }) -- signup-form-component.html -- al <form> bisogna agggiungere il binding alla variabile form definita nel component -- ad ogni campo bisogna aggiungere formControlName <form [formGroup]="form"> <div class="form-group"> <label for="username">Username</label> <input formControlName="username" id="username" type="text" class="form-control" /> <div *ngIf="u.touched && u.errors.['required']" class="alert alert-danger">Username is required</div> <div *ngIf="u.touched && u.errors.['minlength']" class="alert alert-danger">Greater than 3</div> <div *ngIf="u.touched && u.errors.['maxlength']" class="alert alert-danger">Less than 10</div> </div> <div class="form-group"> <label for="password">Password</label> <input formControlName="password" id="password" type="text" class="form-control"/> <div *ngIf="p?.touched && p?.invalid" class="alert alert -danger"> Password must be greater then 6 letters </div> </div> <button class="btn btn-primary" type="submit">Sign Up</button> </form> -- signup-form-component.ts import { FormGroup, FormControl, Validators } from '@angular/forms'; export class SignupFormComponent { //creo variabile form da bindare nel tag <form> nella pagina html form = new FormGroup({ username: new FormControl('[optional <initial_value>]', [ Validators.required, Validators.minLenght(3), Validators.maxLenght(10) ]), password: new FormControl('', Validators.required) oppure (javascript accetta le due sintassi) "username": new FormControl(), "password": new FormControl() }); get u() { return this.form.get('username'); } get p() { return this.form.get('password'); } }
-
- Custom validator / Asyncronous validator
-
//creo file dove metto le mie classi che definisco le mie validazioni personalizzate -- username.validators.ts import { AbstractControl, ValidationErrors } from "@angular/forms"; export class UsernameValidators { //verifico che il campo non contiene spazi static cannotContainSpace(control:AbstractControl): ValidationErrors | null { if((control.value as string).indexOf(' ') >= 0) return { cannotContainSpace: true }; return null; } //asyncronous validation //verifico che la username inserita non sia 'mario.rossi' static shouldBeUnique(control:AbstractControl): Promise { return new Promise((resolve, reject) => { //uso setTimeout per simulare una chiamata asincrona setTimeout( () => { if(control.value === 'mario.rossi') resolve({shouldBeUnique:true }); else resolve(null); }, 2000); }); } } //nel file del mio form aggiungo la validazione personalizzata appena creata -- signup-form-components.ts form = new FormGroup({ username: new FormControl('', //syncronous validators [UsernameValidators.cannotContainSpace], //asyncronous validators [UsernameValidators.shouldBeUnique] ) }); --signup-form-component.html //aggiungo un messaggio che gestisce la validazione asincrona //mi avvisa che sto verificando l'unicità della username inserita //sfrutto l'attributo pending dell'oggetto form.get('username') <div *ngIf="u?.pending">Checking for unique username</div>
-
- Validation onSubmit
-
--signup-form-component.html <form [formGroup]="form" (ngSubmit)="login()"> <div *ngIf="form.errors" class="alert alert-danger">Username or password invalid</div> </form> --signup-form-component.ts login() { //interrogo il server e setto l'errore alla variabile form // o alla variabile che rappresenta il mio campo this.form.setErrors({ invalidLogin:true }); //errore generico nel form oppure this.u.setErrors({ invalidLogin:true }); //errore di un campo specifico }
-
- FromGroup (raggruppare i campi del form in differenti sotto gruppi)
-
//E' possibile raggruppare i campi nel form in sottogruppi e usare la classe FormGroup per gestire //i vari gruppi di campi //creo un gruppo di campi chiamato account con 2 campi (username, password) -- signup-form-component.html <form [formGroup]="form"> <div fromGroupName='account'> <div formControlName='username'></div> <div formControlName='password'></div> </div> </form> -- signup-form-component.ts form = new FormGroup({ account: new FormGroup({ username: new FormControl('', Validators.required), password: new FormControl('', Validators.required) }) }); get u() { return this.form.get('account.username'); }
-
- FormArray
-
-- form-array-component.html <form> <input type="text" class="form-control" (keyup.enter)="addTopic(topic)" #topic /> <ul class="list-group"> <li *ngFor="let item of topics.controls" (click)="removeTopic($any(topic))" class="list-group-item"> {{ item.value }} </li> </ul> </form> --form-array-component.ts import { FormArray, FormControl, FormGroup } from '@angular/forms'; export class FormArrayComponent { form = new FormGroup({ topics: new FormArray([]) }); addTopic(topic: HTMLInputElement) { this.topics.push(new FormControl(topic.value)); topic.value =''; } removeTopic(topic: FormControl) { let index = this.topics.controls.indexOf(topic); this.topics.removeAt(index); } get topics() { return this.form.get('topics') as FormArray; }; }
-
- FormBuilder
-
-- form-builder-component.html -- form-builder-component.ts import { FormArray, FormBuilder, FormControl, FormGroup } from '@angular/forms'; export class FormBuilderComponent { form; constructor(fb:FormBuilder){ this.form = fb.group({ topics: fb.array([]), username: ['', Validators.required], contact: fb.group({ adress: [], zipcod: [] }) }) } }
-
- Quando usarli
Consuming HTTP service
- CRUD
- Leggere dati (GET)
-
-- app.module.ts //importare il modulo per la gestione delle comunicazioni HTTP //così facendo tutte le classi definite al suo interno sono automaticamente //utilizzabili nei construttori dei miei componenti, in quando all'interno di HttpClientModule //è definita una propria derittiva @NgModule (come in tutti i moduli) dove è stato //già inizializzato l'array dei servizi forniti (providers:[]) import { HttpClientModule } from '@angular/common/http'; @NgModule({ imports: [ HttpClientModule ] }) --post.service.ts import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; @Injectable({ providedIn: 'root' }) export class PostService { //private url = "https://jsonplaceholder.typicode.com/posts"; constructor(private http: HttpClient){ } getPosts() { //return this.http.get(this.url); return this.http.get('/makes'); } } --posts-component.ts import { PostService} form '../../../services/post.service'; export class PostsComponent implements OnInit { posts:any; constructor(private service: PostService) { .. } ngOnInit(): void { this.service.getPosts() .subscribe({ next: (data) => { this.posts = data }, error: (e) => { alert('An unexpedcted error occured.'); console.log(e); } }); } }
-
- Creare nuovi dati (POST)
-
-- post.service.ts createPost(post: any) { return this.http.post<{id:number}>(this.url, JSON.stringify(post)); } -- posts.component.ts createPost(input: HTMLInputElement) { let post:any = { title: input.value }; input.value = ''; this.service.createPost(post) .subscribe({ next: (data) => { post.id = data.id; this.posts.splice(0, 0, post); }, error: (e) => { alert('An unexpected error occured.'); console.log(e); } }); }
-
- Modificare dati (PUT/PATCH)
-
updatePost(post:any){ //E' possibile usare PATCH per modificare solo un sottoinsieme delle proprietà dell'oggetto //In tal caso si passa solo le proprietà modificare (es: title) in JSON //Se invece è stato modificato il maggior numero di proprietà dell'oggetto meglio usare PUT //In tal caso si passo tutto l'oggetto in JSON this.http.patch(this.url + '/' + post.id, JSON.stringify({title:post.title}) [.put(this.url + '/' + post.id, JSON.stringify(post)] .subscribe(data => { console.log(data); } }
-
- Eliminare dati (DELETE)
-
-- post.service.ts deletePost(postId: number) { return this.http.delete(this.url + '/' + post.id); } -- posts.component.ts deletePost(post:any){ this.service.deletePost(post.id) .subscribe({ next: (data) => { let index = this.posts.indexOf(post); this.posts.splice(index, 1); }, error: (e) => { if(e.status === 404) alert('This post has already been delete!); else { alert('An unexpected error occured.'); console.log(e); } } }); }
-
- Leggere dati (GET)
- Global error handling
-
-- app.module.ts //Sostituire la classe di Angulare che gestisce gli errori normalmente con la classe custmo AppErrorHandler providers: [ { provide: ErrorHandler, useClass: AppErrorHandler} ], -- /common/app-error-handler.ts import { ErrorHandler } from "@angular/core"; export class AppErrorHandler implements ErrorHandler { handleError(error: any): void { -- some code here! -- } }
-
- Pessimistic vs Ottimistic approach
- Quando si implementano le operazioni CRUD, è possibile seguire 2 approcci
- Pessimistic approach => Modifico l’interfaccia solo dopo aver ricevuto la risposta positiva dal servizio HTTP che esegue l’operazione richiesta.
- Optimisti approach => Modifico l’interfaccia subito, senza aspettare la risposta del servizio. Nel caso la risposta sia effettivamente negativa, inserisco la logica per annullare la modifica apportata all’interfaccia.
- Quando si implementano le operazioni CRUD, è possibile seguire 2 approcci
- Observables vs Promies
- Observables =>
- lazy (un servizio observable non viene eseguito finché non viene definito il suo metodo subscribe) .
- Permettono reactive programming
- E’ possibile convertire un observable verso un promise => promise = observable.ToPromise()
- Promies => eager (un servizio asincrono viene eseguito anche se non è stato definita una funzione di callback (then o catch).
- Observables =>
- Lifecycle hooks
-
OnInit => ngOnInit (usato per inizializzare variabili nei <component>) OnChanges => ngOnChanges DoCheck => ngDoCheck AfterContentInit => ngAfterContentInit
-
Routing
- Configurare il routing
-
//Definisco gli accoppiamenti tra l'URL visitato dall'utente e il componente da visualizzare //Deve andare dal più specifico al più generico //'**' è una wildcard che significa tutti i path -- app.module.ts import { RouterModule } from '@angular/router'; imports: [ RouterModule.forRoot([ { path: '', component: HomeComponent }, { path: 'followers/:username', component: GithubProfileComponent }, { path: 'followers', component: GithubFollowersComponent }, { path: 'posts', component: PostsComponent }, { path: '**', component: NotFoundComponent } ]) ]
-
- Aggiungere <router-outlet>
- Affinche il meccanismo di routing funzioni così come settato nel <RouteModule> è necessario definire almeno un <Router-Outlet>.
- Un <Router-Outlet> agisce come un placeholder che Angular riempie dinamicamente in base alla corrente impostazione del browser.
-
-- app.component.html <navbar></navbar> <router-outlet></router-outlet>
- Aggiungere <routerLink>
-
1- Link senza parametri --navbar.component.html <a routerLink="/followers">Followers</a> <a routerLink="/posts">Posts</a> 2- Link con passaggio di parametri --followers-component.html <a [routerLink]="['/followers', f.id]">Vedi dettaglio del follower</a> //cliccando, l'utente navigherà alla pagina => /followers/1234 (dove f.id=1234) 3- Settare il link attivo <li routerLinkActive="active current"> <a [routerLink]="['/followers', f.id]">Vedi dettaglio del follower</a> </li> 4- Leggere i parametri passati --follower-detail-component.ts import { ActivatedRoute } from '@angular/router'; export class FollowerDetailComponent implements OnInit { constructor(private route: ActivatedRoute){ .. } id: any; ngOnInit() { //se il componente può essere richiamato da se stesso allora //devo passare per la versione observable di route.paramMap this.route.paramMap .subscribe(params => { this.id = params.get('id'); }) //altrimenti posso usare la versione semplice non asincrona //infatti se l'utente arriva da questo componente da un altro //allora abbiamo la certezza che OnInit verrà sempre eseguito //non abbiamo bisogno di fare il subscribe della versione observable di route.paramMap this.id = this.route.snapshot.paramMap.get(id'); } } Angular quando naviga ad un nuovo componente, distrugge il precedente e aggiunge al DOM il corrente. Quando navigo da un componente allo stesso (ad es: visualizzo un log e dopo il seguente) questo meccanismo non avviene. Angular si accorge che è inutile distruggere e creare lo stesso componente. Questa è la ragione per cui si usa un observable per leggere i parametri del componentente ActivatedRoute 5- Link con parametri in queryString (QS) //scrivo link con parametri in QS -- navbar.component.html <a routerLink="/followers" [queryParams]="{ page: 1, order: 'newest' }">Go to followers</a> //leggo parametri in QS --followers.component.ts //caso in cui il componente possa autochiamarsi //devo usare la versione asincrona del metodo che mi permette di leggere i parametri in QS this.page = this.route.queryParamMap.subscribe(params => { this.page = params.get('page'); }); //caso in cui il componente è acceduto sempre e sicuramente da un altro componente //posso non uso versione asincrona this.page = this.route.snapshot.queryParamMap.get('page'); 6- Navigare via codice import { Router } from '@angular/router'; export class GithubProfileComponent { constructor(private route:Router) { .. } this.route.navigate(['/followers/1'], { queryParams: { page:1 , order: 'newest' } }); }
-
- Combining observables
-
//E' possibile unire più observables e fare un unico subscribe complessivo. //In altre parole è possibile combinare più eventi asincroni e sottoscrivere un unico metodo che li attende entreambi import { combineLatest } from 'rxjs' export class GithubFollowersComponent implements OnInit { followers?: any[]; constructor(private route: ActivatedRoute, private service:GithubFollowersService) { } ngOnInit(): void { const paramMap = this.route.paramMap; const queryParamMap = this.route.queryParamMap; //caso1 => leggo i parametri passati nell'URL in modo asincrono //combino 2 observables insieme combineLatest([ paramMap, // error here queryParamMap ]) .pipe( //leggo gli observables e mappo l'uscita come un array di followers switchMap(([val1, val2]) => { let id = val1.get('id'); this.page = val2.get('page'); return this.service.getAll(); }) ) .subscribe(followers => { this.followers = followers; }); //caso2 => leggo i parametri passati nell'URL in modo sincrono //this.route.snapshot.paramMap.get('id'); //this.page = this.route.snapshot.queryParamMap.get('page'); //this.service.getAll() // .subscribe({ // next: followers => this.followers = followers // }); } }
-
Autenticazione e autorizzazione
- Bearer authentication => In Angular l’autenticazione/autorizzazione è implementata tramite i JSON web tokens (JWT)
- Un JWT ha
- header
- payload => è la sezione di un JWT che contiene i dati che vogliamo passare dal client al server.
- digital signature => creata in rapporto al contenuto dell’header e del payload. E’ generata sulla base di una chiave secret conosciuto solo dal server.
- Sul sito jwt.io
- si trovano le librerie da usare sia lato client che lato server per implementare l’autenticazione/autorizzazione tramite i JSON Web tocked
- un degubber per generare al volo un token (in rapporto al contenuto delle sue 3 sezione e della chiave secret) e usarlo nella nostra applicazione per debuggarla.
- Un JWT ha
- NPM: angular2-jwt (vecchie versione / HttpInterceptor (nuove versioni)
- Usare *ngIf per mostrare o nasconde alcune
- Proteggere gli indirizzi (routes) => Usare i servizi guards
- E’ possibile proteggere l’accesso ad alcune pagine del nostro sito implementando dei servizi Guard e usarli nella definizione del RouterModule in app.module.ts
-
-- app.module.ts //Abbiamo protesso l'accesso al component AdminComponent con il guard AdminAuthGuard RouterModule.forRoot([ { path: '', component: HomeComponent }, { path: 'admin', component: AdminComponent, canActivate: [AdminAuthGuard] } ]) -- admin.auth-guard.service.ts //Se l'utente è un admin = true, else = false //Questo servizio eredita da AuthGuard che è un altro Guard che verifica se l'utente è loggato o no import { AuthGuard } from './auth-guard.service'; import { Injectable } from '@angular/core'; @Injectable() export class AdminAuthGuard extends AuthGuard { canActivate() { let isAuthenticated = super.canActivate(); if (!isAuthenticated) return false; if (this.authService.currentUser.admin) return true; this.router.navigate(['/no-access']); return false; } } -- auth-guard.service.ts import { AuthService } from './services/auth.service'; import { Injectable } from '@angular/core'; import { Router, CanActivate } from '@angular/router'; @Injectable() export class AuthGuard implements CanActivate { constructor(protected router: Router, protected authService: AuthService) { } canActivate() { if (this.authService.isLoggedIn()) return true; this.router.navigate(['/login']); return false; } }
- Chiamare API endpoints protetti (accessibili solo ad utenti loggati) => usare AuthHttp
-
-- order.service.ts import { Injectable } from '@angular/core'; import { AuthHttp } from "angular2-jwt/angular2-jwt"; @Injectable() export class OrderService { //Per chiamare un API protetta è sufficiente sostituire HTTP con authHTTP //AuthHTTP al suo interno aggiunge automaticamente un header con il campo 'Authorization' //senza doverlo fare automaticamente (come invece bisogna fare se si usa http) constructor(private authHttp: AuthHttp) { } getOrders() { //si possono saltare tutte le seguenti linee di codice //let headers = new Headers(); //let token = localStorage.getItem('token'); //headers.append('Authorization', 'Bearer ' + token //let options = new RequestOptions({ headers: headers }); //return this.http.get('/api/orders', options) return this.authHttp.get('/api/orders') .map(response => response.json()); } }
-
Deployment
- Tecniche di ottimizzazione pre-deploy
- Minification => Lanciare un tool per minificare il nostro codice
- Uglification => Lanciare un tool per ririscrivere nomi lunghi e descrittivi in nomi corti e senza un significato per gli umani.
- Bundling =>
- Combinare più file js e css insieme e fare il deploy di 1 solo file
- Il bundling permette al client che utilizzerà la nostra applicazione di eseguire meno http request possibili verso il server per scaricare le librerie utilizzate nell’applicazione stessa.
- il client di carica più velocemente.
- Il server può essere a disposizione per più client alla volta.
- Dead code elimination => Eliminare tutto il codice non utilizzato.
- Ahead-of-time compilation (AOT)
-
//Angular compila l'applicazione specificatamente per un ambiente di prod //quindi applica tutte le tecniche di ottimizzazione ng build [--configuration=production] ng build [--configuration=development]
- AOT vs JIT compilation
- Just-in-time compilation
- Angular compiler è eseguito per ogni utente
- Compila interamente l’applicazione, quindi in caso di grosse applicazione, risulta un processo lento
- Più adatto all’ambiente di sviluppo
- Il compiler va deployato insieme all’applicazione.
- AOT
- L’applicazione parte più velocemente
- Non dobbiamo deploiare il compilatore
- Possono essere intercettati alcuni errori prima del deploy stesso
- Più sicuro => meno possibilità di injection attacks
- Just-in-time compilation
- Angular compiler
-
-- /package.json "dependencies": { "@angular/compiler: "^4.0.0" } -- eseguire il compiler //creo nella cartella (es: ./src/dist/out-tsc) indicata nel file tsconfig.json //il file .map per ogni componente definito nell'applicazione > ./node_modules/ .bin/ngc -p ./src -- compilo //nella cartella configurata in tsconfig.json Angular mette i file compilati //E' possibile indicare quale file delle variabili d'ambiente considerare ng build --env=prod | --env=dev
-
- Aggiungere enviroment
-
1- creare un nuovo file nella cartella /src/app/enviroment environment.staging.ts 2- ./angular.json [prima < Angular 6.0 era angular-cli.json] Nel nodo "project/architect/build/configurations" aggiungere il nodo "staging" copiando quelli presenti e indicando in "fileRemplacements" il file creato al punto 1 "configuration" : { "staging": "fileReplacements": [ { "replace": "src/environments/environment.ts", "with": "src/environments/environment.staging.ts" } ] } 3- ./angular.json Nel nodo "serve" aggiungere la proprietà "broserTarger" "serve": "configurations" : { "staging": : { "browserTarget": "exercice-blog:build:staging" } }
-
- Linter
- tslint => E’ un pacchetto da importare nel progetto che permette di sincronizzare alcuni elementi della sintassi cosi da armonizzare il codice tra diversi collaboratori.
-
//Visualizza un elenco di punti del codice dove sono state infrante le regole definite nel file //di configurazione tsling.json ng lint //Permetti a lint di sistemare gli errori che trova ng lint --fix 2- esling //simile a tsling c'è eslint (utile anche per javascript) //il file di configurazione è .eslintrc.json 3- VSCode //Esiste un'estensione che può essere installata in Visual Studio Code
- Fare il deploy verso Github Pages(Ospita solo pagine statiche (html, css, js))
-
1- Creare un nuovo repository in Github 2- Collega il git locale con il repo appena creato git remote add origin https://github.com/<github-username>/<repository-name>.git 3- Sincronizza la cartella locale con la cartella remota git push -u origin main 4- Installare la CLI Github Pages nel progetto angular npm i -g angular-cli-ghpages 5- Fai il build dell'applicazione mettendo come ulr base l'indirizzo delle 'pages' del repo Github ng build [--prod] --base-href="https://<github-username>.github.io/<repository-name>/" 6- Lanciare l' Angular-cli-ghpages ngh --dir dist/<angular-app-name> 7- Attiva le 'pages' nel repo Github sul ramo 'gh-pages' 8- L'applicazione è ora disponibile https://<github-username>.github.io/<repository-name>/ 9- Creare uno custom script in package.json "scripts": { "deploy:gh": "ng build --base-href='https://leperegoriot1977.github.io/angular-blog/' && ngh --dir dist/exercice-blog" } 10- Ogni volta che si vuole deployare su Github Pages npm run deploy:gh
-
- Firebase
-
1- Creare una nuova applicazione in Firebase 2- Installare nella cartella del progetto locale le estenzioni di Firebase per Angular npm i -g firebase-tools 3- Loggarsi a Firebase tramite la Angular-CLI e scegliere le opzioni proposte firebase login 4- Inizializzare Firebase nel folder del nostro progetto locale => si crea un nuovo file firebase.json firebase init 5- Aprire il nuovo file firebase.json e verificare che sia ben impostata la cartella dove verrà fatto il build dell'applicazione angular -- firebase.json { "hosting": { "public": "dist/exercice-blog", "ignore": [ "firebase.json", "**/.*", "**/node_modules/**" ], "rewrites": [ { "source": "**", "destination": "/index.html" } ] } } 6- Build l'applicazione ng build 7- Fare il deploy su Firebase firebase deploy 8- L'applicazione è deploiata https://<my-firebase-app>.web.app/ 9- Creare uno custom script in package.json "scripts": { "deploy:firebase": "ng build && firebase --deploy" } 10- Ogni volta che si vuole deployare su Github Pages npm run deploy:firebase
-
API end-point lato-client
- Configurare proxy
-
1- Creare il proxy.conf.js -- proxy.conf.js const PROXY_CONFIG = [ //aggiungere una voce per ogni controller con API che si vogliono raggiungere tramite URL contest: [ "/api" "/weatherforecast", "/makes", ] ] oppure 1B - Creare il file proxy.conf.json -- proxy.conf.json { "/api": { "target": "https://localhost:7147/", "secure": false } } 2- Fare in modo che l'applicazione prenda in considerazione il file proxy.conf.js (oppure proxy.conf.json) -- angular.json "projects": "Vega": "serve": "development": "proxyConfig: "proxy.conf.js" (oppure "proxy.conf.json") oppure ng serve --proxy-config proxy.conf.js (oppure "proxy.conf.json")
-
- src/app/services
-
//usare @Inject('BASE_URL') per l'url di base (es: https://localhost:7147) import { Injectable, Inject } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { MakeResource } from '../resources/make-resource'; @Injectable({ providedIn: 'root' }) export class MakeService { public makes: MakeResource[] = []; constructor(private http: HttpClient, @Inject('BASE_URL') private baseUrl: string) { } getMakes() { //GET https://localhost:7147/makes return this.http.get<MakeResource[]>(this.baseUrl + 'makes'); } }
-
Risorse
- Bootstrap componens
- C# To TypeScript
- In Visual Studio Code installare l’estensione aggiuntiva ‘C# To TypeScript’
- Prendere le classi C# che si voglio copiare in formato TypeScript (per la nostra applicazione Angular)
- Aprire il componente in Angular
- Fare F1 => cercare ‘C# to TypeScript (Past As)’ => dare l’ok, le classi verranno copiate come desisderato
- Simulare la presenza di servizi lato server
- JSon web tockens
- Firebase
- StackBlitz
- Boot a fresh environment in a few seconds
- https://www.npmjs.com/
- Search packages