1 of 140

Angular

2 of 140

Índex

3 of 140

Nom i versions

  • El framework ha canviat de nom i de manera d’anomenar les versions.
  • Fins a la versió 1.7 era Angular.js 1.7
  • Després ha sigut Angular 2 4 5 6 7
  • La major part del codi de Angular 2 funciona en les següents versions.
  • Per exemple: Angular 7.0.2:
    • 7: Canvi de versió. Pot ser no retrocompatible.
    • 0: Actualizació menor, retrocompatible, agrega funcions
    • 2: Partxes, retrocompatible.
  • Al dia de hui: Angular 18.X.X
  • Cada 6 mesos allibera una nova versió.

4 of 140

Característiques

  • Expressivitat en les plantilles HTML.
  • Disseny modular i Lazy Loading
  • Facilitat per reutilitzar components.
  • Facilita la comunicació en el backend. Permet inclús Angular en el servidor.
  • Ferramentes de desenvolupament potents (Augury, Karma, Jasmine...)
  • Integració amb frameworks de disseny Bootstrap, Angular Material, Ionic…
  • SPA
  • Facilitat per ser ampliat amb els seu sistema modular.
  • Simplificació de la reactivitat.
  • DOM virtual.
  • Pensat per a grans aplicacions.

5 of 140

Tipus de webs

  • Pàgines web tradicionals:
    • HTML generat al servidor.
    • Javascript per a interacció i AJAX.
  • SPA:
    • HTML Generat al client amb els JSON o XML que envía el servidor.
  • Progressive Web Application:
    • Com una SPA, però que pot ser executada sense connexió al servidor.
    • Els navegadors poden descarregar-la i crear icones per accedir dirèctament.
    • Poden tindre accés al hardware del dispossitiu (fer fotos, per exemple)
    • Tenen limitacions respecte a les aplicacions natives o híbrides.
  • App híbrida:
    • Es programa en Javascript, possiblement amb Angular e Ionic o React, Cordova…
    • S’instal·la i utilitza com una App normal nativa.
    • No necessita un servidor ni navegador web, però corre damunt del seu propi navegador mínim.

6 of 140

Requisits previs

  • Node: sudo apt install nodejs
  • npm: sudo apt install npm
  • Typescript: sudo npm install -g typescript
  • CLI Angular: https://cli.angular.io/ sudo npm install -g @angular/cli
  • Editor de text:
  • Angular 2 TypeScript Emmet
  • Angular Language Service
  • Angular Snippets
  • Material icon Theme
  • Terminal
  • Al navegador: https://angular.io/guide/devtools

Actualitzar node:

sudo npm install -g n�sudo n stable

sudo npm install -g npm

7 of 140

Abans de continuar…

  • Qualsevol framework implica que el programador signe un contrat:
    • Has de pagar un impost per utilitzar-lo: Temps de formació, canvi d’habits, no poder utilitzar algunes llibreries, no poder cridar a baix nivell. No entendre què està passant, Estar molt pendent del framework, actualitzar per seguretat i perdre coses, Compromís en el framework, perdre coneixements bàsics.
    • Qualsevol framework implica clavar el projecte en un silo al no poder portar el codi a altre framework.
  • Encara es pot treballar en Vanilla JS amb llibreries puntuals (JQuery, Lodash, Ramda, Mocha, RxJS…)

8 of 140

Introducció a TypeScript

9 of 140

Què és Typescript

  • Al principi JS era sols per a validar formularis i poc més.
  • Per fer projectes grans, apareixen biblioteques com JQuery o frameworks com Angular.
  • També apareixen ferramentes per a programar amb més disciplina en JS.
  • TypeScript té tipat estàtic, detecta errors en temps d’escriptura, Ajuda a autocompletar en funció del tipus de variable, Mètode estàtic de programació, classes i mètodes (abans de ES6).
  • Problemes de JS: Errors per variables no definides, un objecte no té una propietat, no sabem cóm funcionen funcions d’altres, es poden sobreescriure variables, funcions...
  • ES6 fa que no siga tan necessari.

10 of 140

Transpilar TypeScript

  • Els navegadors no entenen TS.
  • TypeScript és un superset de Javascript.
  • Cal transpilar de TS a JS. Es fa en ferramentes automàtiques sense errors.
  • TypeScript s’encarrega de que funcione el JS en navegadors diferents o antics.
  • Podem dir-li a npm o a Webpack que transpile al passar a producció.

11 of 140

Exemple: Situació inicial

<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="UTF-8">

<title>Introducción</title>

</head>

<body>

<script src="app.js"></script>

</body>

</html>

function saludar( nombre ) {

console.table( 'Hola ' + nombre ); // Hola John

}

const persona = {

nombre: 'John'

};

saludar();

Quin problema trobem?

12 of 140

Exemple: Passant a Typescript

  • Canviem l’extensió de .js .ts
  • Ja apareixen els errors en Visual Studio.
  • Cal transpilar a JS amb: tsc app.ts

(function(){

function saludar( nombre:string ) {

console.table( 'Hola ' + nombre );

}

const persona = {

nombre: 'John'

};

saludar(persona.nombre);

})();

13 of 140

Configurar TypeScript

  • Podem fer que transpile cada vegada que guardem
  • tsc --init
  • Crea un arxiu tsconfig.json (Li fa cas tant tsc com Visual Studio)
  • tsc -w (Es queda a l’espera de canvis en els .ts per transpilar automàticament)

Angular ja farà açò per nosaltres

14 of 140

Estàndards

  • TypeScript en el fitxer tsconfig.json, per defecte, transpila de TS a JS ES5.
  • Nosaltres sabem programar en ES6, però ES5 és més compatible i pot fer el mateix.
  • Les novetats de ES6 són per a programar millor, més paregut a TS: classes, let, const, funcions fletxa…
  • Mirem què passa si transpilem aquest codi:

let nombre = 'Joaquin';

if(true){

let nombre = 'Chimo';

}

console.log(nombre);

15 of 140

Tipus de dades

  • TS assigna un tipus de dades estàtic en la primera assignació no explícita.
  • És més recomanable fer-ho explícit com en els llenguatges tipats.
  • Si necessitem tipat dinàmic podem fer-ho de tipus any (no recomanable)

let nombre: string = 'Joaquin';

let numero: number = 123; // (minúscula)

let qualquierdato; // any

let booleano: boolean;

let hoy: Date = new Date(); // Tipus classe

let persona = {

nombre: 'Pepe',

edad: 30

}

let personajes: string[] = []

let p: Array<string> = []

// intentem assignar a persona altres dades

16 of 140

Paràmetres de les funcions

(function(){

function saludar(quien:string, // Obligatori

momento?: string, // Opcional

objecto: string = 'la mano', ){ // Per defecte

if(momento){

console.log(`${quien} saludó con ${objecto} ${momento}`);

}

else {

console.log(`${quien} saludó con ${objecto}`);

}

}

saludar('Paul');

saludar('Leto', 'por la tarde', 'el crys');

saludar('Gurney', 'el basilet');

})();

17 of 140

Return de les funcions

(() =>{

function sumar(a:number, b: number): number{ return a+b; }

function nombre():string {return 'Pepe';}

})();

18 of 140

Funcions fletxa

(function(){

const toptero = {

posicion: 'aire',

comunica(){

setTimeout(function(){

console.log(`Posición: ${this.posicion}`);

},1000);

}

}

toptero.comunica();

})();

(function(){

const toptero = {

posicion: 'aire',

comunica(){

setTimeout( () => console.log(`Posición: ${this.posicion}`)

,1000);

}

}

toptero.comunica();

})();

Quin problema donarà?

Cóm quedarà el .js?

19 of 140

Promeses i tipat

  • ES5 no pot utilitzar promeses, per tant, TypeScript no pot transformar-les. Cal canviar a ES6 en tsconfig.json.
  • Angular ja utilitza biblioteques que fan que funcionen en ES5.
  • Observem com fiquem que retorna una promesa y aquesta un number.

(() =>{

const recogerEsencia = (cantidad: number): Promise<number> => {

let cantidadActual = 1000;

return new Promise ( (resolve,reject) =>{

if(cantidad > cantidadActual){

reject('No queda');

} else {

cantidadActual -= cantidad;

resolve(cantidadActual);

}

});

}

recogerEsencia(500)

.then( cantidadActual => console.log(`Queda ${cantidadActual}`))

.catch ( err => console.warn(err));

})();

20 of 140

Interfaces

(() =>{

function enviar ( persona: { nombre: string }){ // Problemàtic

console.log(`Enviando a ${persona.nombre} a Arrakis`);

}

let persona = { nombre: 'Jessica', edad: 30 }

enviar (persona);

///////////////////// Interfaces ///////////////////////

interface Caracter {

nombre: string,

edad: number,

familia?: string // opcional

}

let personaInterface: Caracter = { nombre: 'Hawat', edad: 80}

function enviarInterface ( persona: Caracter){ // Més fàcil de mantindre

console.log(`Enviando a ${persona.nombre} a Arrakis`);

}

enviarInterface(personaInterface);

})();

21 of 140

Classes

(() =>{

class Recolector {

private piloto:string = 'fremen';

constructor(

public identificador: string,

public propietario: string,

public buenEstado: boolean = true,

private lugar?: string

){}

}

let rec = new Recolector('1234','cofradia',true,'desierto');

console.log(rec.piloto);

})();

Funcionarà?

22 of 140

(() =>{

function imprimirConsola(constructorClase: Function){

console.log(constructorClase);

}

@imprimirConsola // cal descomentar experimentaldecorators en tsconfig

class Recolector {

constructor(

public identificador: string,

public propietario: string,

public buenEstado: boolean = true,

private lugar?: string

){}

}

let rec = new Recolector('1234','cofradia',true,'desierto');

})();

Nosaltres no crearem decoradors, però Angular els utilitza.

23 of 140

‘Hola Mon’ en Angular

24 of 140

  • sudo npm install -g @angular/cli [--force]
  • ng new my-app
  • cd my-app
  • ng serve -o

<p>Hola Mundo</p>

<ul><li>{{nombre}}</li><li>{{apellido}}</li></ul>

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

@Component({

selector: 'app-root',

templateUrl: './app.component.html',

styleUrls: ['./app.component.css']

})

export class AppComponent {

title = 'my-app';

nombre = 'Pepe';

apellido = 'Lopez';

}

25 of 140

  • Generant elements del framework:
    • Component: ng g component my-new-component
    • Directive: ng g directive my-new-directive
    • Pipe: ng g pipe my-new-pipe
    • Service: ng g service my-new-service
    • Class: ng g class my-new-class
    • Interface: ng g interface my-new-interface
    • Enum: ng g enum my-new-enum
    • Module: ng g module my-new-module
    • Guard: ng g guard my-new-guard
  • Afegint noves biblioteques i integrant-les:
    • ng add @angular/material
  • Actualitzant Angular
    • ng update --all
  • Compilant l’Aplicació amb Webpack
    • ng build [--prod]
  • Creant un projecte
    • ng new angular-projecte

26 of 140

Estructura del projecte

Per a fer proves

Mòduls de node instal·lats en npm per Angular. No cal enviar-la a github ni com a part del projecte. Es pot regenerar fent npm install

src Codi font de l'aplicació

El que no pujarà a Github

Configuració de l’aplicació

Es crea automàticament. Indica cóm es va a passar a producció. Utilitzat per Webpack

Configuració de TypeScript

App és el primer component que carregarà

Web estàtica

Recursos estàtics

Variables d’ambient

Primer codi que executarà.

27 of 140

Components

  • Una aplicació Angular està formada per varis components.
  • Objectes que compleixen una funció específica: Menú de navegació, barra lateral, contingut, footer…
  • Es defineixen com a classes normals de JS amb decoradors.

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

@Component({

selector: 'app-root',

standalone: true,

imports: [CommonModule, RouterOutlet],

templateUrl: './app.component.html',

styleUrl: './app.component.css'

})

export class AppComponent {

title = 'hola-mon';

}

Importa el decorador

<app-root>

Una classe normal decorada

Els components standalone tenen imports

28 of 140

Estructura d’un component

Fulla d’estils sols per al component

HTML ‘plantilla’ per al component.

Per a fer proves

Propietats del mòdul (si no és standalone)

Lògica del component. Serà el que més modificarem

29 of 140

Primera aplicació: Primer component

  • ng g component components/header
  • Si no és standalone, observem que s’ha creat l’importació en app.module.ts
  • Si és standalone, fem l’import en app.component.ts
  • Fiquem <app-header></app-header> en app.component.html
  • El component ‘app’ inclou el component ‘header’.

30 of 140

Elements del Framework

31 of 140

Mòduls Angular

  • Tota aplicació Angular no standalone ha de tenir almenys un mòdul principal.
  • Per defecte és src/app/app.module.ts
  • Dividir l’aplicació en mòduls permet programar més eficientment.
  • Els mòduls es poden carregar sols quan fan falta per no ocupar memoria (Lazy Loading)
  • Els mòduls contenen components, servicis, pipes…
  • En una aplicació menuda no cal fer un NgModule principal i sempre es pot fer després si es necessita.

32 of 140

NgModule vs Standalone

  • En versions anteriors de Angular, per defecte AppComponent estava definit amb el decorador @NgModule.
  • Això és totalment vàlid, encara que sols el recomanen per a projectes grans o per a fer biblioteques de components o altres elements.
  • La tendència actual dels creados d’Angular és a utilitzar components Standalone.
  • Els components Standalone no estan dins d’un NgModule i tenen els imports que necessiten al decorador.
  • Són més fàcils de programar i de reutilitzar.

33 of 140

Càrrega inicial de l’aplicació

  • S’anomena Bootstrap (no confondre amb les biblioteques d’estils)
  • El Bootstrap principal és AppComponent i és el primer que carrega l’aplicació. Tots els demés components s’executen dins d’aquest.
  • L’applicació sap que el mòdul principal és AppModule perquè ho especifica en src/main.ts
  • Index.html és únic HTML que es carrega. A partir d’ell, Angular i les seues rutes prenen el control.

34 of 140

Reactivitat

35 of 140

{{ Interpolacions }}

  • Permet ficar qualsevol codi JS
  • Normalment sols són per a assignar valors de variables.
  • Es pot ficar en qualsevol part de l’HTML <img src="{{urlImagen}}">
  • Es pot ficar el resultat d’una funció {{ metodoComponente() }}
  • Són dinàmiques, el que vol dir que si canvia en el Javascript, canvia en l’HTML.
    • Però sols són en direcció JS -> HTML. Les expressions o funcions que modifiquen el JS no són bones pràctiques i poden ser ignorades per Angular.
  • El context de les expressions és el component i no poden accedir a coses globals.
  • No poden tindre if, for, while…
  • Cal mantenir la simplicitat.
  • Si la variable té codi HTML, cal vincular amb [innerHTML]

36 of 140

[Vincular atributs]

<img [src]="product.imageUrl" alt=""> → Millor!

<img src={{product.imageUrl}} alt="">

<div [style.height.px]="imageHeight"></div>

  • ngStyle i ngClass !Importar CommonModule!

<td [ngStyle]="{'background-color': isEven?'red':'green'}">...</td>

<td [ngStyle]="{'width.px': width}">...</td> <!--width és un atribut de TS-->

<td [ngStyle]="styleObject">...</td> <!-- Atribut de TS -->

<td [ngClass]="{'even': isEven, 'last': isLast}"></td>

<td [ngClass]="{'even': isEven, 'last active': isLast}"></td>

<td [ngClass]="classObject"></td> <!-- Atribut de TS-->

37 of 140

[ngStyle] i [ngClass]

  • Directiva d’atribut.
  • Permet manipular l’estil a partir de variables.
  • Accepta un objecte literal o en una variable.
  • Cal importar CommonModule

<p [ngStyle]="{'font-size': tamano+'px'}">Hola Mundo</p>

<p [ngStyle]="{'font-size.px': tamano}">Hola Mundo</p>

<p [style.fontSize.px]="tamano">Hola Mundo</p>

<p [ngClass]="clase">Hola Mundo</p>

<p [ngClass]="[clase, claseParrafo ]">Hola Mundo</p>

<p [ngClass]="{'text-danger': danger, 'text-info': !danger}">Hola Mundo</p>

38 of 140

Vinculació bidireccional [(ngModel)]

  • Utilitzat generalment en elements <input>
  • Cal importar el mòdul FormsModule d’Angular en el mòdul principal de l’aplicació o en els components standalone.
  • Si és un sol input, sense form: [ngModelOptions]="{ standalone: true }"

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

<input type="text" [(ngModel)]="filterSearch" class="form-control"

name="filterDesc" id="filterDesc" placeholder="Filter...">

39 of 140

(Vincular esdeveniments)

  • (click), (mouseenter),(keypress)...
  • Sols són en direcció HTML -> TS

<button class="btn btn-sm"

[ngClass]="{'btn-danger': showImage, 'btn-primary': !showImage}"

(click)="toggleImage()">

{{showImage?'Ocultar':'Mostrar'}} imagen

</button>

<img [src]="product.imageUrl" *ngIf="showImage" alt=""

[title]="product.desc">

Observem també el ngClass

40 of 140

Directives

41 of 140

Directives estructurals

  • Instruccions que li diuen al HTML el que ha de fer.
  • *ngIf: Mostra o oculta (ni el crea) elements HTML en funció d’una condició.
  • *ngFor: Mostra una serie d’elements en funció d’una iteració.

<h2>Proves *ngIf</h2>

<div style="width: 400px;" *ngIf="mostrar">

<h3>{{frase.autor}}</h3>

<p> {{frase.mensaje}}</p></div>

<button (click) = "mostrar = !mostrar">Mostra/Ocultar</button>

<h2>Proves *ngFor</h2>

<ul>

<li *ngFor="let cita of citas; let i = index">{{ i }} {{ cita }}</li>

</ul>

index: number

first: boolean

last: boolean

even: boolean

odd: boolean

42 of 140

ngIf, else, ngSwitch

<div *ngIf="show; else elseBlock">La condición es verdadera</div>

<ng-template #elseBlock>La condición es falsa</ng-template>

<span [ngSwitch]="property">

<span *ngSwitchCase="'val1'">Value 1</span>

<span *ngSwitchCase="'val2'">Value 2</span>

<span *ngSwitchCase="'val3'">Value 3</span>

<span *ngSwitchDefault>Other value</span>

</span>

43 of 140

Combinar directives

<ng-container *ngFor="let person of persons"> <!-- Desaparece -->

<ng-container *ngIf="person.age >= 18"> <!-- Desaparece -->

<p>{{person | json}}</p> <!-- Sólo quedará este elemento -->

</ng-container>

</ng-container>

44 of 140

  • Una forma més moderna a partir de Angular 17
  • No cal importar res.
  • track necessita valors únics i pot ser $index o algun altre id. És per optimitzar l’actualització reactiva del DOM
  • També està @let per fer variables al codi.

@if (names.length > 0) {

@for (name of names; track $index) {

<p>

{{$index}} {{ name }}

</p>

} } @else {

<p>No names</p>

}

@for (name of names; track $index;

let first = $first, last = $last;

let odd = $odd, even = $even;

let count = $count) {

<p>

{{$index}} {{ name }} {{ first }}: {{ last }} {{ odd }}: {{ even }} {{ count }}

</p>

} @empty {

<p>No items</p>

}

45 of 140

Interficies

  • És convenient que Angular conega l’estructura que han de tindre els objectes.

ng g interface interfaces/i-product

Es pot utilitzar l’extensió Paste JSON as Code en Visual Studio per objectes complexos

export interface IProduct {

id: number;

description: string;

price: number;

available: Date;

imageUrl: string;

rating: number;

}

products: IProduct[] = [{

id: 1,

description: 'SSD hard drive',

available: new Date('2016-10-03'),

price: 75,

imageUrl: 'assets/ssd.jpg',

rating: 5

},{...

46 of 140

Components

47 of 140

Components

  • Un component és un controlador d’una vista HTML associada.
  • Els components són entitats independents i reutilitzables.
  • Els components poden anidar altres components en una relació pare-fill.
  • Representen les parts d’una aplicació.
  • El primer component està en app.component.ts

48 of 140

Decorador dels Components

@Component

  • selector: Etiqueta HTML amb el prefix de l’aplicació i el nom del component. <app-root> (Es pot canviar el prefix en angular.json o al crear el projecte en --prefix)
  • templateUrl: Arxiu amb l’HTML del component.
  • template: Encara que és millor separar HTML de TS, es pot ficar dirèctament HTML en una cadena amb template string ` `.
  • styleUrls: Els CSS de l'aplicació.

49 of 140

Estils en els components

  • Es pot ficar dirèctament en el decorador en styles: []
  • És millor que estiga en un fitxer separat amb styleUrls: []
  • Angular canviarà aleatòriament els selectors de l’HTML final i dels CSS per a que no puguen coincidir en altres.
  • Si volem estils globals poden estar en src/styles.css o incloure’ls a angular.json
  • Amb --style al crear el projecte o en angular.json després es pot dir si van a ser css, sass, less o scss.

50 of 140

  • Constructor: És el constructor de la classe i es poden ficar les injeccions de dependències.
  • ngOnInit: Sols una vegada al ser creat el component. Es sol utilitzar per obtindre dades del servidor.
  • ngAfterContentInit, ngAfterViewInit: S’executen quan ja està preparat per a ser mostrat i quan ja ha sigut renderitzat completament.
  • ngOnchanges: Cada vegada que canvia un valor d’una propietat amb interpolacions, vinculacions d’atributs, @Input…
  • ngDoCheck: Canvis que Angular no està preparat per a detectar. No es recomana perquè s’executa molt a sovint.
  • ngOnDestroy: Quan el component desapareix de la vista.

51 of 140

Implementar OnInit

@Component({

selector: 'app-product-detail',

templateUrl: './product-detail.component.html',

})

export class ProductDetailComponent implements OnInit {

constructor( private activatedRoute: ActivatedRoute,

private productsService: ProductsService ) { }

ngOnInit(): void {

this.activatedRoute.params.subscribe( params => {

});

}

}

OnInit és una interfície i obliga a implementar ngOnInit()

52 of 140

Components niuats

  • Un component niuat o fill representa un fragment en la vista global.
  • En l’exemple veiem una llista de productes en la que cada producte és un component que té un altre component de la puntuació.
  • Els components pares envien informació als fills amb el decorador @input() al fill. i del fills als pares amb @output()

<h1>Catalogue</h1>

<div class="row row-cols-1 row-cols-md-2 g-4">

<div class="col">

<app-product-item [p]="product"

*ngFor="let product of products">

</app-product-item> </div>

</div>

...

@Input() p: Product;

// podem ficar p! si falla TS

53 of 140

@Input(), diferents opcions

@Input({ required: true, }) name: string; // Requerit, si el pare no el té dona error

@Input() // Amb getters i setters� get courses() {� return this.myCourses;� }� set courses(courses: Course[]) { this.myCourses = courses; }

@Input("userName") name: string; // Amb “Alias”

54 of 140

Property has no initializers…

  • Typescript no accepta que per @Input() es declaren dades.
  • És correcte, ja que, pot ser que el pare no envie eixes dades.
  • Formes de solucionar-ho:
    • Ficar ! en la variable (no recomanable, ja que indiques que sempre tindrà eixes dades)
    • Ficar | undefined o ? (recomanable, ja que indiques que pot ser no les tinga)
    • Ficar "strictPropertyInitialization": false en el tsconfig.json (no recomanable perquè no avisarà del posible error)

@Input() products?: IProduct[]�// o�@Input() products: IProduct[] | undefined

55 of 140

  • Si volem que Angular no fique un “Wrapper” al voltant del component fill.
  • Es poden afegir en una etiqueta HTML existent:
  • No es recomana, ja que es pot confondre en una directiva.
  • Utilitzar sols en tr o altres elements que necessiten ser fills directes d’un altre

@Component({

selector: '[app-edifici]',

<tr app-edifici

*ngFor="let punto of datos; let parell = even"

[ngClass]="{ parell: parell }"

[punto]="punto"

></tr>

El selector està entre [ ]

No és el nom de l’etiqueta, és l’atribut.

56 of 140

@Output: del component fill al pare

  • Els components fills poden emetre un esdeveniment personalitzat.
  • Els components pares el poden arreplegar amb ().

@Output() rattingChanged = new EventEmitter<number>();

...

puntuar(i: number): void {

this.rattingChanged.emit(this.auxRatting);

}

(rattingChanged)="changeRatting($event, product)

changeRatting(stars: number, p: Product): void {

p.ratting = stars;

}

57 of 140

Rutes

58 of 140

Rutes

  • Angular sol ser per a SPA.
  • Els SPA es tenen que comportar de manera pareguda a les webs tradicionals en els URI.
    • Necessitem poder fer referència externa a les diferents parts de l’aplicació.
    • Es necessita poder tornar enrere en el navegador.
    • Es necessiten rutes virtuals.
  • Per defecte estan en app-routing.module.ts
  • Les rutes són un objecte amb el path i el component al que fa referència.
  • Les pàgines són components.
  • El router carrega les rutes en <router-outlet> dins de la plantilla principal.

59 of 140

Rutes en #

  • Una manera de fer SPA sense manipular el servidor és ficar una # al principi de la ruta:

http://localhost:4200/#/home

  • És més antic, funciona en tots els navegadors, és més simple enviar paràmetres i no cal manipular el servidor.
  • Per a que funcione, cal afegir
    • providers: [provideRouter(routes, withHashLocation())]
  • Si no utilitzem hash, cal tindre aquesta línia en index.html

<base href="/">

60 of 140

Crear les rutes

const routes: Routes = [

{path: 'home', component: HomeComponent},

{path: 'planets', canActivate: [AuthGuard], component: PlanetListComponent},

{path: 'suns', canActivate: [AuthGuard], component: SunComponent},

{path: 'planet/:id', canActivate: [AuthGuard], component: PlanetDetailComponent},

{path: 'planet/edit/:id', canActivate: [AuthGuard],

resolve: {planet: PlanetResolveService},

component: PlanetEditComponent},

{path: 'login', component: LoginComponent},

{path: '**', pathMatch: 'full', redirectTo: 'home'}

];

Ruta bàsica: path i component que activa

Ruta amb Guard

Ruta amb paràmetres

Ruta amb resolve

Ruta per defecte

61 of 140

Cridar a les rutes

  • En els enllaços, canviem els href per [routerLink]

<a class="nav-link active" aria-current="page" [routerLink]="['home']">Home</a>

  • Si la ruta té més nivells seran elements de l’array.
  • Amb routerLinkActive es pot indicar que aplique la classe ‘active’ quan siga el menú actual:

<a class="nav-link" aria-current="page"

[routerLink]="['home']"

[routerLinkActive]="['active']"

>Home</a>

  • routerLinkActive pot estar sense [] i en l’element pare de l’enllaç si cal.
  • Importar imports: [RouterLink, RouterLinkActive],

62 of 140

Cridar a les rutes per codi

  • Cal importar el router:

import { Router } from '@angular/router';

  • La classe tindrà un atribut privat per al router:

constructor( private router: Router ) {}

  • Es cridarà a la funció navigate() del router amb els paràmetres adequats.

detailsProduct(id: number): void{

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

}

Typescript: Podem afegir un atribut declarant-lo com a paràmetre del constructor.

Angular: El router s’afegeix com una injecció de dependències.

63 of 140

Obtindre paràmetres de les rutes (Antic)

  • Les rutes poden tindre un paràmetre ( id, per exemple ).
  • Importem ActivatedRoute:

import { ActivatedRoute } from '@angular/router';

  • L’afegim al constructor:

constructor( private activatedRoute: ActivatedRoute) { }

  • Exemple de cóm utilitzar els paràmetres:

ngOnInit(): void {

this.activatedRoute.params.subscribe( params => { console.log(params)})

}

Params és un observable al que ens subscrivim.

64 of 140

Obtindre paràmetres de les rutes

  • A partir d’Angular 16 podem configurar el router per acceptar els paràmetres per @Input() amb withComponentInputBinding()

provideRouter(routes , withComponentInputBinding()),

@Input('id') recipeID?: string;

65 of 140

providers: [

provideRouter(

[

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

{ path: ':count', component: Counter },

],

withViewTransitions(),

withComponentInputBinding()

),

],

@keyframes rotate-out {

to {

transform: rotate(90deg);

}

}

@keyframes rotate-in {

from {

transform: rotate(-90deg);

}

}

::view-transition-old(count),

::view-transition-new(count) {

animation-duration: 200ms;

animation-name: -ua-view-transition-fade-in, rotate-in;

}

::view-transition-old(count) {

animation-name: -ua-view-transition-fade-out, rotate-out;

}

Afegir withViewTransitions()

Al CSS es pot personalitzar

(2024) sols funciona completament a Chrome

66 of 140

onViewTransitionCreated

withViewTransitions({

onViewTransitionCreated: ({transition}) => {

const router = inject(Router);

const targetUrl = router.getCurrentNavigation()!.finalUrl!;

// Skip the transition if the only thing

// changing is the fragment and queryParams

const config = {

paths: 'exact',

matrixParams: 'exact',

fragment: 'ignored',

queryParams: 'ignored',

};

if (router.isActive(targetUrl, config)) {

transition.skipTransition();

}

},

}),

Accepta un objecte amb la funció onViewTransitionCreated

En aquest cas serveix per a cancel·lar l’animació

67 of 140

Servicis

68 of 140

Servicis

  • Donen informació a qui la demane.
  • Típicament fan peticions CRUD (Create, Read, Update, Delete)
  • Mantenen les dades de forma persistent
  • Són un recurs reutilitzable per a l’aplicació.
  • Les classes tenen el decorador @Injectable() per a indicar l’injector de dependències que ha de proveir un objecte d’aquesta classe quan es necessite.
  • Tindran funcions pròpies que donen aquesta informació.
  • En app.module.ts es claven dins de providers
  • Els creem en ng g service services/nomdelservici

69 of 140

  • Si un component necessita un servici el pot declarar en el constructor amb new.
    • El codi queda menys llegible.
    • Si canvia la manera de crear en servici, té que canviar la manera de declarar-lo.
  • Angular ja fa el servicis Injectables automàticament.
  • Si fiquem el servici en els paràmetres del constructor, Angular i el seu motor d’Injecció ja el crea.
  • Ha de tindre el decorador @Injectable.
  • Angular crea un Singleton del servici per a tots els components.
  • Si està en providers en el mòdul, sols està disponible per al mòdul.
  • Si en el decorador diu providedIn: ‘root’ no cal que estiga en providers.

70 of 140

HttpClientModule

  • Els servicis normalment obtindran les dades per HTTP del servidor.
  • Ací es veu com importar el mòdul per a ser client HTTP al app.config.ts:

import { provideHttpClient } from '@angular/common/http';

providers: [

provideHttpClient()

]

71 of 140

  • Els servicis poden tindre una injecció de dependències d’altres servicis.
  • El servici que proporciona les dades als components utilitzarà el servici HttpClient.
  • Qualsevol cridada al client retorna un Observable.
  • Abans de l’Observable, es poden manipular les dades en pipe i map, reduce, filter...

constructor(private http: HttpClient) { }

getProducts(): Observable<Product[]>{

return this.http.get<{products: Product[]}>(this.productURL).pipe(

map(response => response.products)

);

}

Retornem un Observable que emet arrays de productes.

Abans de retornar l’observable es manipulen les dades.

map() és de la llibreria RxJS

Injecció de dependències

72 of 140

Post per HttpClient

import { HttpClient, HttpHeaders } from '@angular/common/http';�…� private httpOptions = {� headers: new HttpHeaders({� 'Content-Type': 'application/json',� })� };�…�this.http.post<{first: string, last: string}>(this.loginURL,JSON.stringify(datos),httpOptions) ...

73 of 140

Observables

  • Es pot treballar en promeses per a obtindre les dades, però en Angular es treballa per defecte en una Llibreria anomenada RxJS que implementa una versió més avançada anomenada Observables.
    • Una promesa retorna un sols valor, els observables poden retornar varis valors al llarg del temps.
    • Una promesa comença en quan es crea, un Observable sols quan algú es subscriu (Lazy Loading)
    • Un observable es pot cancelar quan es cancelen les subscripcions.
    • Els observables tenen operadors (map, filter, reduce).
  • L’operador map fa alguna manipulació a les dades i les retorna. L’operador filter sols deixa passar les dades que passen un filtre.
  • Els operadors s’apliquen com a paràmetres del mètode pipe de la classe Observable

74 of 140

Processant respostes dels Observables

  • Un observable pot tindre varis subscriptors i sols comença a emetre dades quan es subscriu algú.
  • El subscriptor executa el mètode subscribe() que accepta tres funcions com a paràmetres:
    • La primera funció rep el resultat tornat per l’observable ja processat per mètodes intermitjos.
    • La segona funció (opcional) s’executa si falla l’observable o mètodes intermitjos.
    • La tercera (opcional) s’executarà sempre.

products: Product[] = [];

ngOnInit(): void {

this.productsService.getProducts().subscribe(

{ // Observer literal

next: prods => this.products = prods,

error: (err) => console.error('Observer got an error: ' + err),

complete: () => console.log('Observer got a complete notification'),

} ); }

75 of 140

Mostrar dades asíncrones

  • Normalment tarden en ser mostrades. Si en aquest estat, Angular intenta accedir pot ocórrer un error.
  • Es pot crear un objecte amb dades buides per a que no done l’error.
  • Es pot ficar un @if per no mostrar les dades fins que no carreguen.
  • Si fiquem un ? farem que no es carregue fins que no tinga un valor vàlid {{product?.description}}

76 of 140

  • Les signals són més simples i menys potents que els Observables.
  • Per a tasques reactives simples són, a partir d’Angular 17, la millor opció.
  • Es pot també exportar o ser retornat per una funció.

constructor(){

effect(()=>{console.log(`Valor de num: ${this.num()}`); });

}

num = signal(0);

updateNum(){this.num.update((n:number)=> n+1);}

ngOnInit(): void {this.num.set(1) }

77 of 140

Resolver

  • De vegades volem obtindre les dades del servidor abans d’accedir a una ruta.
  • Es pot fer un tipus especial de servici que es diu un Resolver.

@Injectable({ providedIn: 'root'})

export class ProductResolver implements Resolve<Product> {

constructor(private productsService: ProductsService, private router: Router) { }

resolve(route: ActivatedRouteSnapshot,

state: RouterStateSnapshot): Product | Observable<Product> | Promise<Product>

{

return this.productsService.getProduct(route.params.id).pipe(

catchError(error => {this.router.navigate(['/products']);

return of(null); })

); }}

{ path: 'product/edit/:id',

canActivate: [ProductDetailGuard],

canDeactivate: [LeavePageGuard],

resolve: { product: ProductResolver},

component: ProductEditComponent}

Es crea com un servici i implementa resolve

Fa ús del servici real d’obtindre productes

78 of 140

Cache

79 of 140

Pipes

80 of 140

Pipes (filtres)

  • Els filtres ajuden a la interpolació per transformar la informació abans de mostrar-la.
  • Angular ja té alguns filtres prefefinits.
  • Els filtres van darrere de la variable separats per | (com en Linux)
  • No canvien la variable original
  • Alguns admeten paràmetres amb :

<tr *ngFor="let product of products">

<td>

<img [src]="product.imageUrl"

*ngIf="showImage" alt=""

[title]="product.desc | uppercase">

</td>

<td>{{ product.description }}</td>

<td>{{ product.price | currency:'EUR':'symbol'}}</td>

<td>{{ product.available | date:'dd/MM/y' }}</td>

</tr>

81 of 140

Fer els nostres Pipes

ng g pipe pipes/<nom>

import { Pipe, PipeTransform } from '@angular/core';

import { Product } from '../product/product';

@Pipe({

name: 'productFilter'

})

export class ProductFilterPipe implements PipeTransform { //al implementar PipeTransform cal fer la funció Transform

transform(products: Product[], filterBy: string): Product[] { // el primer argument és el que cal filtrar i després una llista d'arguments

// en aquest cas sols és un, el criteri de búsqueda

const filter = filterBy ? filterBy.toLocaleLowerCase() : null; // passem el filtre a minúscules o a null si no està

return filter ? // Si no és null filtra

products.filter(p => p.name.toLocaleLowerCase().includes(filter))

: products; // si és null trau l'array sense filtre

}}

<tr *ngFor="let product of productos | productFilter: filtre;">

82 of 140

Autenticació

83 of 140

Autenticació

  • Si la web està allotjada al mateix lloc que el servidor es poden utilitzar cookies.
  • En cas d’utilitzar un servici extern cal autenticar en Tokens. Poden ser de format JWT
  • Si utilitzem tokens, can enviar-los en cada petició.
  • Per no tindre que implementar aquest token en cada petició es poden utilitzar interceptors.
  • Un interceptor “intercepta” cada petició HTTP i la manipula.
  • En realitat l’autentificació depen del servidor, però el client pot mostrar o no uns menús i fer que funcionen o no les rutes.
  • El que no pot fer el client és garantir que l’usuari no intentarà accedir d’altres maneres a les dades.
  • En Angular utilitzem Guards junt en Cookies, LocalStorage o variables globals.

84 of 140

Guards

  • Són un tipus de servici que es pot utilitzar per el router.
  • El de permetre entrar a parts amb una condició es diu CanActivate

ng g guard product/guards/product-detail

  • El de permetre eixir es diu CanDeactivate

{ path: 'product/:id',

canActivate: [supabaseLoginGuard],

component: ProductDetailComponent},

export const supabaseLoginGuard: CanActivateFn = (route, state) => {

const router: Router = inject(Router);

const supabaseService: SupabaseService = inject(SupabaseService);

const urlTree: UrlTree = router.parseUrl('./main');

return supabaseService.loggedSubject.getValue() ? true : urlTree;

};

85 of 140

86 of 140

Autenticació: Servicis, Tokens i Guards

  • El component amb el formulari de Login demana a un servici que es connecte al server i li envíe l’usuari i contrasenya.
  • Si el server autentica pot enviar un token per a mantindre la sessió.
  • En cas de ser el mateix servidor no cal, perquè pot enviar una cookie en la capçalera de HTTP i el navegador la afegeix.
  • El servici guarda el token en localStorage
  • Un guard consulta al servici que manté el localStorage per saber si pot anar o no a una ruta.
  • En localstorage o variables d’entorn es pot guardar l’identificador d’usuari per fer consultes al servidor o dades similars.

87 of 140

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

import { HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';

import { Observable } from 'rxjs';

@Injectable({ providedIn: 'root'})

export class AuthInterceptorService {

intercept(req: HttpRequest<any>, next: HttpHandler):Observable<HttpEvent<any>> {

const token = localStorage.getItem('idToken'); // Token de locastorage

if (token) {

// Clonem la petició i afegiem el sufix

const authReq = req.clone({url: req.url.concat(`?auth=${token}`)});

// Enviem la petició en token

return next.handle(authReq); }

return next.handle(req);

// Sense tokens enviem la petició original

}}

providers: [

{

provide: HTTP_INTERCEPTORS,

useClass: AuthInterceptorService,

multi: true,

}],

88 of 140

Variables com a Observable

  • En l'autenticació, per exemple, necessitem que els components reaccionen al fet de fer login sense tindre que refrescar la web.
  • Podem fer una variable de tipus BehaviorSubject o Subject que es comporta com un Observable i informa als seus subscriptors dels canvis.
  • Subject no necessita valor inicial.

private loguedInfo: BehaviorSubject<boolean>;

isLogued(): Observable<boolean>{

return this.loguedInfo.asObservable()

}

constructor(private http: HttpClient) {

this.loguedInfo = new BehaviorSubject<boolean>(false);

}

ngOnInit(): void {

this.logued = this.auth.isAuth();

this.auth.isLogued().subscribe(

l => this.logued = l

)

}

89 of 140

  • Supabase té un SDK compatible amb TypeScript. Cal instal·lar-ho:

npm install @supabase/supabase-js

  • En enviroment.ts: (ng generate environments)

export const environment = {� production: false,� supabaseUrl: 'YOUR_SUPABASE_URL',� supabaseKey: 'YOUR_SUPABASE_KEY',�}

  • El SDK funciona en promeses. Es pot treballar en promeses sense problemes i es poden convertir a Observables amb from()

90 of 140

Directives

91 of 140

Directives

  • Elements o atributs que modifiquen les plantilles HTML
  • Directives de component: Es defineixen amb el selector en el decorador @Component (les que hem fet fins ara)
  • Directives d’atribut: Per a modificar el comportament d’un element. NgClass o NgStyle són exemples.
  • Directives estructurals: Per manipular el DOM. Normalment són per a definir si un element es mostra en el DOM o no. ngIf, ngFor, ngSwitch

92 of 140

Crear directives d’atribut

ng g directive directives/resaltado

import { Directive, ElementRef, HostListener, Input } from '@angular/core';

@Directive({

selector: '[appResaltado]'

})

export class ResaltadoDirective {

constructor( private el:ElementRef) {}

@Input('appResaltado') nuevoColor:string;

@HostListener('mouseenter')

entrarMouse(){this.el.nativeElement.style.backgroundColor = this.nuevoColor;}

@HostListener('mouseleave')

saleMouse(){this.el.nativeElement.style.backgroundColor = null;}}

<div class="col-md-4" [appResaltado]="color" >

93 of 140

ElementRef vs Renderer2

  • ElementRef ens dona una referència directa a un element del DOM una vegada renderitzat.
  • Renderer2 ens dona una abstracció més i no depèn de l’accés al DOM. Funciona en aplicacions de servidor i amb Webworkers.
  • Angular pot ser multiplataforma i ElementRef sols funciona en el fil principal del navegador.
  • ElementRef pot donar problemes de seguretat i no separa la lògica de la vista tan correctament com Renderer2.

constructor(private e: ElementRef, private r: Renderer2) { }

@Input() elementMostrar!: any;

@HostListener('mouseenter')

entrarMouse(){this.r.setStyle(this.elementMostrar,'display','');}

@HostListener('mouseleave')

saleMouse(){ this.r.setStyle(this.elementMostrar,'display','none');}

94 of 140

Template Reference Variables

<li appMostrarMes

[elementMostrar]="ocultar">

<p #ocultar style="margin-bottom: 0; display:none ">

Producció: <span *appForDelay="100; let p of placa.production">{{p}}W </span>

</p>

...

95 of 140

Crear directives estructurals

  • Les directives que coneguem són ngIf, ngFor i ngSwitch.
  • Tenen el * perquè en realitat simplifiquen una etiqueta <ng-template> pare de l'etiqueta que estem manipulant.

@Directive({

selector: '[appForDelay]'

})

export class ForDelayDirective {

constructor(private templateRef: TemplateRef<any>,

private viewContainer: ViewContainerRef) {}

items: Array<number> = [];

delay = 0;

@Input() set appForDelay(t: number){this.delay = t;}

@Input() set appForDelayOf(array: number[]){this.items = array;}

ngOnInit(): void {

let i = 0;

this.items.forEach(n => {

...}}

<span *appForDelay="100; let p of placa.production">{{p}}W </span>

96 of 140

Esdeveniments

  • La manera més simple és amb ( ) en l’element HTML que corresponga. Això és una subscripció externa.
  • La subscripció interna és amb @HostListener

@HostListener("window:scroll", ['$event'])

homeScroll($event:Event){

let scrollOffset = window.scrollY;

this.imgY = 100 - scrollOffset/2;

if (this.imgY < 0) { this.imgY = 20}

}

97 of 140

Formularis

98 of 140

Plantilla vs Reactius

99 of 140

Formularis de plantilla

  • Els formularis necessiten importar el mòdul FormsModule
  • Els inputs poden tindre validadors de HTML5. I no cal ni utilitzar Angular per a la majoria de les validacions.
  • En funció d’aquests validadors, s’apliquen unes classes automàticament:

<input

type="text"

class="form-control"

[(ngModel)]="product.description"

minlength="5"

maxlength="600"

required name=”description”

/>

100 of 140

Modificar estils al validar

A banda de la tècnica anterior + CSS personalitzat es pot fer una referència amb # i fer coses en ngClass:

<input type="text" name="description" class="form-control

[(ngModel)]="product.description" minlength="5" maxlength="600" required #descriptionModel="ngModel"

[ngClass]="{'is-valid': descriptionModel.touched && descriptionModel.valid,

'is-invalid': descriptionModel.touched && !descriptionModel.valid}"

/>

</div>

<div>{{ product | json }}</div>

<div>Dirty: {{ descriptionModel.dirty }}</div>

<div>Valid: {{ descriptionModel.valid }}</div>

<div>Value: {{ descriptionModel.value }}</div>

<div>Errors: {{ descriptionModel.errors | json }}</div>

Sempre que tenim [(ngModel)] necessitem que tinga name

Una variable de referència a ngModel per utilitzar en ngClass

La variable de referència indica si és vàlid i pot ser utilitzada per al que necessitem

101 of 140

Manipular l’entrada de l’usuari en temps real

  • Cal separar el [(ngModel)] en la vinculació i l’esdeveniment:
  • $event és el contingut de l’input

<input

type="text"

class="form-control"

[ngModel]="product.description"

(ngModelChange)="product.description = $event.toUpperCase()"

/>

102 of 140

ngForm

  • Per defecte, tots els formularis tenen aquesta directiva.
  • Podem fer una variable de referència a la directiva per observar les propietats generals del formulari
  • Es recomana indicar la directiva novalidate per desactivar la validació del navegador i deixar a Angular tota la tasca. Farà que l’aplicació funcione igual en tots els navegadors.
  • La variable value conté tots els fields del formulari.

<form #productForm="ngForm" novalidate>

<input type="text" name="description" ... />

</form>

</div>

<div>Touched: {{ productForm.touched }}</div>

<div>Valid: {{ productForm.valid }}</div>

<div>Value: {{ productForm.value | json }}</div>

<div>Descripción: {{productForm.control.get('description').value | json}}</div>

103 of 140

Mostrar els errors

  • En aquest exemple barregem estils Bootstrap, validació HTML5, ngIF i la referència a l’Input que està validant-se:

<input

type="email"

name="email"

[(ngModel)]="email"

class="form-control"

required

minlength="5"

maxlength="600"

id="exampleInputEmail1"

aria-describedby="emailHelp"

#emailInput="ngModel"

[ngClass]="{

'is-valid': emailInput.touched && emailInput.valid,

'is-invalid': emailInput.touched && !emailInput.valid

}"

/>

@if ( emailInput.touched && !emailInput.valid) {

<div

class="alert alert-danger">

Email requerit (entre 5 y 60 caracteres)</div>

}

<div id="emailHelp" class="form-text">

We'll never share your email with anyone else.

</div>

104 of 140

Fer els nostres validadors

@Directive({

selector: '[appMinPrice]',

providers: [{provide: NG_VALIDATORS, useExisting: MinPriceDirective,

multi: true}]

})

export class MinPriceDirective implements Validator {

@Input('appMinPrice') minPrice;

constructor() { }

validate( c: AbstractControl): { [key: string]: any}{

if(this.minPrice && c.value) { // si rebem algun valor

if(this.minPrice > c.value) {

return { minPrice: true } //tornem el error

} } return null; // sense error }}

Angular accepta el rol de validadora d’aquesta directiva al registrar-se com a tal.

105 of 140

Enviar el formulari

  • Podem acabar de validar el formulari abans d’enviar.
  • Podem protegir el botó per no deixar pulsar fins que no estiga complet (Validació desde la plantilla)
  • Podem obtindre el formulari en el component amb �@ViewChild i # o utilitzant la variable de referència

editar(productForm:NgForm){

// Les validacions que calguen

this.productService.editProduct(this.product).subscribe(

ok => this.router.navigate(['/product/',this.product.id])

)

}

<button type="submit" class="btn btn-primary" [disabled]="productForm.invalid"> Submit </button>

<form #productForm="ngForm" (ngSubmit)="editar(productForm)" novalidate #editForm>

@ViewChild('editForm', {static: true}) editForm: NgForm;

...

console.log(this.editForm.valid);

106 of 140

Les dades dels inputs

  • Si utilitzem [(ngModel)] tindrem una variable per a cada input.
  • Si utilitzem en els inputs ngModel no cal la variable i podem obtindre les dades del NgForm (Recomanat)

editar(productForm:NgForm){

// Les validacions que calguen

this.productService.editProduct(productForm.value).subscribe(

ok => this.router.navigate(['/product/',this.product.id])

)

}

107 of 140

Formularis reactius

  • En realitat si utilitzem [(ngModel)] ja estem fent que qualsevol formulari tinga un comportament reactiu.
  • Angular anomena formularis reactius a una serie de tècniques que permeten controlar millor el formulari des de codi i no tant des de la plantilla.
  • Es recomana sols utilitzar formularis reactius o al menys sols utilitzar els de plantilla en formularis molt simples (search, login...)
  • Necessitem el mòdul ReactiveFormsModule en imports del component
  • Els dos tipus de formularis tenen FormControl, FormGroup i FormArray però els reactius el declaren dirèctament en el codi.

108 of 140

Primeres configuracions en Formularis reactius

  • Crearem un formulari HTML quasi pur.
  • En el component necessitem una variable de tipus FormGroup inicialitzada en el constructor. El grup és un conjunt de FormControls.
  • Utilitzarem un servici injectat anomenat FormBuilder per construir el formulari.
  • FormBuilder necessita un objecte que relacione els inputs amb un valor inicial i validadors.
  • En el HTML sols ficarem [formGroup] i formControlName en cada input.

formulario: FormGroup;

crearFormulario(){

this.formulario = this.formBuilder.group({

name: [''],

price: [0],

description: [''],

}); }

constructor(private formBuilder: FormBuilder) {this.crearFormulario();}

<form [formGroup]="formulario"

(ngSubmit)="crear()">

...

<input type="text" class="form-control"

id="inputName" formControlName="name">

...

109 of 140

Validacions síncrones en formularis reactius

  • No es recomana fer-les en la plantilla HTML5
  • Angular ens dona validadors per a la majoria de casos amb Validators
  • Per a la part visual es poden fer getters

crearFormulario(){

this.formulario = this.formBuilder.group({

name: ['', [Validators.required,

Validators.minLength(5),

Validators.pattern('.*[a-zA-Z].*')]],

price: [0, Validators.min(0.01)],

description: [''],

});

}

get nameNotValid(){

return this.formulario.get('name').invalid && this.formulario.get('name').touched

}

<input type="text" class="form-control" id="inputName" formControlName="name"

[ngClass]="nameNotValid ? 'is-invalid': 'is-valid'">

110 of 140

Crear Validadors

  • És més senzill que en els de plantilla, ja que no necessitem de directives.
  • La funció que declarem és una factory que retorna una funció de validació.

import { AbstractControl, ValidatorFn } from '@angular/forms';

...

function minDateValidator(minInputDate: string): ValidatorFn {

return (c: AbstractControl): { [key: string]: any } => {

if (c.value) {

const minDate = newDate(minInputDate);

const inputDate = newDate(c.value);

console.log(minDate, inputDate);

return minDate <= inputDate ? null : { 'minDate': minDate.toLocaleDateString() }; }

return null; };

}

Funció factory que retorna una ValidatorFn

Funció fletxa tipus ValidatorFn perquè accepta un control i retorna un objecte de la validació.

El que ha escrit l’usuari

El paràmetre que li fiquem al validator

Validation Error Object. Normalment el nom del validador i el missatge a mostrar en l’error.

111 of 140

Cross-Field Validation

  • Podem fer validadors personalitzats per a avaluar un field en funció d’altres.

this.registerForm = this.formBuilder.group({

passsword: ['',

[Validators.required,

Validators.minLength(3),

Validators.maxLength(10)]],

confirm_passsword: ['',

[Validators.required,

Validators.minLength(3),

Validators.maxLength(10)]],

}, {

validators:

passwordValidator

});

passwordValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {

const ps = control.get('password');

const ps2 = control.get('confirm_password');

return ps && ps2 && ps.value === ps2.value ? null : { passwordValidator: true };

};

Validadors de grup

112 of 140

Agrupar camps

this.formulario = this.formBuilder.group({

name: ['', [Validators.required,

Validators.minLength(5),

Validators.pattern('.*[a-zA-Z].*')]],

price: [0, Validators.min(0.01)],

description: [''],

address: this.formBuilder.group({

street: [''],

city: ['']

})

});

<div class="form-group row mb-3" formGroupName="address">

<label for="inputDescription" class="col-sm-2 col-form-label">Address</label>

<div class="form-row col">

<div class="col">

<input class="form-control" id="inputStreet" placeholder="Street"

rows="3" formControlName="street">

</div>

<div class="col">

<input class="form-control" id="inputCity" placeholder="City"

rows="3" formControlName="city">

</div> </div> </div>

113 of 140

Carregar les dades en el formulari

  • Si estem fent un formulari d’edició cal donar els valors als FormControl
  • Es pot fer enviant un objecte literal o un objecte que ja tinguem
  • L’objecte ha de tenir tots els camps que tinga el form.
  • En cas de voler fer un reset es pot executar la funció reset() del FormGroup que també accepta un objecte de valors per defecte.
  • En setValue() necessita totes les dades, en reset() no les necessita totes.
  • En patchValue() es poden modificar algunes dades.

this.formPlaca.setValue(this.placa);

114 of 140

Detectar canvis

  • Es FormGroup i FormControl contenen un Observable anomenar valueChanges que retorna el valor o grup de valors quan canvia.
  • Ens poden subscriure a aquest observable:

export class AppComponent implements OnInit {

...

ngOnInit() {

...

this.userForm.get('notifications').valueChanges

.subscribe(notif => this.updateNotifMethod(notif)); }

}

115 of 140

Formularis dinamics

  • Els objectes FormArray ens permeten crear en temps d’execució qualsevol objecte de formulari.

ingredients: this.formBuilder.array([]),

...

getIngredientControl(): FormControl {

const control = this.formBuilder.control('');

control.setValidators(Validators.required);

return control;

}

get IngredientsArray(): FormArray {

return <FormArray>this.mealForm.get('ingredients');

}

addIngredient() {

(<FormArray>this.mealForm.get('ingredients')).push(this.getIngredientControl());

}

delIngredient(i: number) {

(<FormArray>this.mealForm.get('ingredients')).removeAt(i);

}

Retorna un AbstracControl i cal indicar que és un FormArray

116 of 140

FormArray en la plantilla

<div formArrayName="ingredients">

@for (ingredient of IngredientsArray.controls; track $index) {

<div class="input-group mb-3">

<input

type="text"

class="form-control"

id="ingredient{{ $index }}"

name="ingredient{{ $index }}"

[formControlName]="$index"

placeholder="ingredient"

[ngClass]="{

'is-valid': ingredient.valid && ingredient.touched,

'is-invalid': ingredient.invalid && ingredient.touched

}"

/>

<button class="btn btn-outline-danger" (click)="delIngredient($index)">Borrar</button>

</div>

117 of 140

Arquitectura

118 of 140

Mòduls

  • Quan l’aplicació es fa molt gran no va sols en el mòdul app i components en directoris.
  • Un mòdul és una classe amb decorador @NgModule
  • Es permet la càrrega en diferit de mòduls per fer més lleugera l’aplicació.

119 of 140

Els arrays del decorador @NgModule

  • Bootstrap: Sols per el mòdul app, per a dir quin és el component inicial.
  • Declarations: Per a components, directives i pipes. Sols poden estar en un mòdul, però es poden exportar per a utilitzar-los en altres. Han d’estar per a poder ser compilats i, per tant, utilitzats.
  • Exports: Permet compartir els components, directives i pipes en altres mòduls. Es poden exportar mòduls per fer un meta-mòdul que importe altres. No es poden exportar servicis ja que ja són injectables a nivell global per defecte.
  • Imports: Els altres mòduls de tercers o nostres que importem. El que importem és el que el mòdul exporte.
  • Providers: Normalment sols en appModule per a que siga accessible per tots el mòduls. no es pot declarar en dos mòduls el mateix servici. Els servicis es poden autoinjectar amb el provideIn: ‘root’ del seu decorador.

120 of 140

Criteris per separar l’aplicació

  • Mòduls de domini: Per separar codi simplement, no representen una possible ruta. Exemple: un menú.
  • Mòduls de secció: Una secció, ruta o pàgina de l’aplicació. Per exemple: Productes, Clients, Factures…
  • Mòduls de servicis: Per separar codi en els servicis, sols importats per el mòdul app. Exemple: HttpClientModule
  • Mòduls de components: Components que poden ser utilitzats en diversos mòduls de l’aplicació. Exemple: Vista modal d’una imatge en gran.

121 of 140

Què ferramenta s'utilitza?

  • Fer parts de l’HTML que tenen entitat: Components
  • Enllaços dins de l’aplicació: rutes
  • Mostrar variables: {{}}
  • Canviar estils: ngStyle, ngClass o [style]
  • Formularis: [()] i/o Formularis reactius
  • Esdeveniments individuals: ()
  • Dades utilitzades en varis components o servicis: Interfaces
  • Esdeveniments recurrents en varis components: Directives d’atribut.
  • Protegir enllaços: Guards
  • Guardar i servir les dades en en navegador i el servidor: Services i Resolvers
  • Variables globals per a tots els components:
    • Enviroment (Si les volem consultar)
    • Servici amb un Observable (Si volem reaccionar als seus canvis)
    • RxJS Redux NgRx
  • Parts diferenciades de l’aplicació i possiblement utilitzables en altres projectes: Mòduls

122 of 140

Arquitectura

  • Es preten:
    • Que qualsevol que veja el codi per primera vegada, trobe els elements de l’aplicació.
    • Reduir la complexitat de l’aplicació.
    • Reduir els bugs al evitar codi duplicat.
  • Guia d’estils
  • Millor Programació funcional i reactiva front a orientació a objectes o imperativa.
  • Millor composició front a herència en la POO.
  • Single Responsibility Principle:
    • Mantindre els fitxers menuts.
    • Separar un fitxer per cada classe, component, interface…
    • DRY (don’t repeat yourself)

123 of 140

Components

  • Mantindrer-los menuts.
  • Fer components fills és millor que components complexos.
  • Els components són sols per a mostrar o editar dades.
  • Els components poden ser Dumb/Smart:
    • Dumb: Arrepleguen les dades per @Input i les tornen per @Output.
    • Smart: Es connecten a servicis per obtindre o enviar dades. Les rutes sempre aniran a Smart Components.

124 of 140

Directoris

125 of 140

126 of 140

Deployment

127 of 140

  • ng build
    • Per a fer proves, encara informa dels errors.
  • ng build --prod
    • Per a enviar a producció.
    • Afegeix noms aleatoris als fitxers per prevenir el cache del navegador.
    • Si tenim variables en Enviroment, cal que estiguen en enviroment.prod.ts

128 of 140

Desplegament en Apache

  • Si es puja a una subcarpeta cal modificar el <base href=”/”>
    • ng build --base-href=’’
  • Per a que Apache no interprete les rutes:

129 of 140

Biblioteques de tercers

130 of 140

Boostrap

  • La forma més simple és utilitzar el CDN de la web oficial de Bootstrap.
    • 👍 Pot estar en la cache del navegador del client
    • 👍 Sempre estarà actualitzat.
    • 👎 No sempre ho necessitem tot.
    • 👎 Necessites internet.
  • Descarregar l’arxiu comprimit i pegar-ho en el directori assets
  • Instal·lar-ho en npm
    • npm install bootstrap
    • npm i --save-dev @types/bootstrap

"styles": [

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

"src/styles.scss"],

"scripts": [

"node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"]

angular.json

131 of 140

  • ng add @ng-bootstrap/ng-bootstrap
  • No necessites JQuery perquè ja funciona en les biblioteques d’Angular.
  • Si vas a interactuar de Typescript a Bootstrap serà més fàcil.
  • Amb Bootstrap 5 ja no necessites JQuery:
    • npm install bootstrap@next --save
  • Ara en cada component que necessite la part de Javascript de Bootstrap, es pot importar:
    • import Bootstrap from 'bootstrap/dist/js/bootstrap';

132 of 140

Angular Material

133 of 140

  • npm install @swimlane/ngx-charts --save
  • En app.module.ts, en imports : NgxChartsModule
  • Mirar els exemples de la web oficial

134 of 140

Telèfons mòbils

135 of 140

Angular en telèfons mòbils

  • Pàgina o Aplicació web responsive.
  • PWA
  • App híbrida amb Ionic

136 of 140

Pàgina web responsive

  • Si utilitzem Angular Material o Bootstrap correctament, podem fer al mateix temps la pàgina web per a navegador com per a mòbil.
  • Firefox i altres permeten veure cóm quedarà en un mòbil, encara que és recomanable provar-ho en un de veritat.
  • Un 60% del tràfic de moltes webs ja és a través de mòbil.

137 of 140

PWA

  • Progresiva: Inclús en navegadors antics o limitats, ha de funcionar.
  • Responsiva
  • Més ràpida una vegada carregada.
  • Connectivitat independent: Funciona sense xarxa o amb xarxa lenta gràcies als service workers.
  • Com una app: L’usuari la sent com si fora una App, no una web.
  • S’actualiza amb els service workers.
  • Detectable com una App per el seu manifest.json i el registre del service worker.
  • Reconectable
  • Instalable
  • Vinculable a través d’una URL i sense instal·lacions complexes.
  • No necessites App Store ni Google Play
  • Funciona mínimament sense connexió.
  • Té una icona PNG
  • Utilitza gràfics vectorials.

138 of 140

App híbrida

  • Tenen el seu propi “navegador” en un SDK
  • Poden no necessitar en absolut un servidor en Internet.
  • S’instal·len.
  • Tenen més accés al hardware que les PWA o és més fàcil.
  • Pot compartir codi amb l’aplicació web tradicional de navegador.
  • Tecnologies:
    • Ionic amb Angular, React o Vue.js damunt de Cordova o Capacitor

139 of 140

App quasi nativa

  • Perd la relació en la pàgina web.
  • Té accés al hardware quasi com una App nativa.
  • Es “compila” a codi natiu.
  • Tecnologies:
    • React Native
    • Flutter amb Dart

140 of 140

Coses no vistes