Introducció a Angular¶
Angular és un framework de desenvolupament web de codi obert, mantingut per Google, per a la creació d\'aplicacions web front-end dinàmiques. Es basa en TypeScript, una extensió de JavaScript que afegeix tipat estàtic, per millorar la llegibilitat, mantenibilitat i robustesa del codi.
Angular canvia totalment la manera de programar. Redueix significativament el codi que cal escriure a canvi de ser molt rígid en la manera de tractar amb els elements de l\' aplicació. Per tant, té una corba d\'aprenentatge una mica alta al principi.
Evolució del nom i versions
El framework ha experimentat canvis en el seu nom i esquema de versions al llarg del temps:
- Fins a la versió 1.7: Angular.js
- Des de la versió 2: Angular 2, 4, 5, 6, 7, ..., 18 (actual)
Cada 6 mesos allibera una nova versió. Solen ser bastant compatibles entre les que estan pròximes i les últimes són retrocompatibles, excepte entre la 1 i la 2, on va canviar gairebé tot.
Característiques principals
- Expressivitat a les plantilles HTML: Permet crear interfícies d\'usuari dinàmiques i declaratives utilitzant sintaxi HTML enriquida.
- Disseny modular i Lazy Loading: Promou l\' organització del codi en mòduls reutilitzables i la càrrega en diferit de components per optimitzar el rendiment.
- Facilitat per reutlitzar components: Fomenta la creació de components reutilitzables que encapsulen funcionalitat i lògica de negoci, millorant la modularitat i mantenibilitat del codi.
- Comunicació fluida amb el backend: Facilita la comunicació entre l\' aplicació Angular i el servidor backend, permetent diverses tecnologies com RESTful APIs, WebSockets i Server-Side Rendering.
- Eines de desenvolupament potents: Ofereix una completa suite d\'eines de desenvolupament, com Augury, Karma i Jasmine, per facilitar la creació, prova i depuració d\'aplicacions.
- Integració amb frameworks de disseny: S\' integra perfectament amb frameworks de disseny populars com Bootstrap, Angular Material i Ionic, permetent crear interfícies d\' usuari atractives i consistents.
- Single Page Applications (SPAs): Permet la creació d\'aplicacions web d\'una sola pàgina (SPAs).
- Arquitectura extensible: Posseeix un sistema modular extensible que facilita la incorporació de noves funcionalitats i biblioteques de tercers.
- Reactivitat simplificada: Simplifica la gestió de la reactivitat en la interfície d\' usuari, permetent que els canvis en les dades es reflecteixin automàticament en la vista.
- DOM virtual: Implementa un DOM virtual que optimitza el rendiment i redueix les manipulacions directes del DOM real.
- Pensat per a grans aplicacions: Està dissenyat per al desenvolupament d\'aplicacions web a gran escala, oferint característiques com l\'enrutat, la gestió d\'estat i la injecció de dependències.
Tipus d\' aplicacions web amb Angular
Angular es pot utilitzar per desenvolupar diferents tipus d\' aplicacions web:
- Pàgines web tradicionals: Permet generar pàgines web dinàmiques amb HTML generat en el servidor i Javascript per a la interacció i les peticions AJAX.
- Single Page Applications (SPAs): Facilita la creació de SPAs que generen l\'HTML en el client a partir de dades JSON o XML enviats pel servidor. Pot ser que aquest tipus d\'APPs siguin per a les que està més preparat.
- Web Applications (PWAs): Brinda suport per al desenvolupament de PWAs, que ofereixen una experiència similar a les aplicacions natives, amb la possibilitat de funcionar sense connexió i accés a les capacitats del dispositiu.
- Aplicacions híbrides: Permet la creació d\' aplicacions híbrides que s\' executen dins d\' un navegador web mínim embegut en l\' aplicació nativa, utilitzant tecnologies com Cordova o Ionic.
Requisits previs per començar amb Angular¶
Instal·lació d\'eines bàsiques
Abans d\'endinsar-nos en el món d\'Angular, cal comptar amb algunes eines essencials per al desenvolupament:
1. Node.js:
sudo apt install nodejs
2. npm:
sudo apt install npm
3. TypeScript:
sudo npm install -g typescript
4. CLI d\'Angular:
- La interfície de línia de comandaments d\'Angular (CLI) facilita la creació, desenvolupament i manteniment de projectes Angular.
sudo npm install -g @angular/cli
5. Editor de text:
- A Visual Studio
- Angular 2 TypeScript Emmet
- Angular Language Service
- Angular Snippets
- Material icon Theme
6. Navegador web:
https://angular.io/guide/devtools
8. Actualització de Node.js:
sudo npm install -g n
sudo n stable
sudo npm install -g npm
El \"contracte\" del framework:¶
És important tenir en compte que l\' adopció d\' un framework com Angular implica un compromís per part del desenvolupador. En utilitzar un framework, s\' accepten certes limitacions i responsabilitats:
- Inversió de temps: Es requereix un temps d\' aprenentatge per comprendre el funcionament del framework i les seves millors pràctiques.
- Canvi d\'hàbits: L\'ús d\'un framework implica un canvi en la forma d\'abordar el desenvolupament web, la qual cosa pot requerir l\'adaptació d\'hàbits i metodologies de treball prèvies.
- Restriccions en l\'ús de llibreries: Alguns frameworks poden limitar l\'ús de certes llibreries o eines externes, obligant a treballar dins de l\'ecosistema propi del framework.
- Abstracció i potencial manca de comprensió: L\' abstracció que ofereix el framework pot, en ocasions, dificultar la comprensió profunda del funcionament intern de les aplicacions.
- Dependència i actualitzacions: El desenvolupament queda subjecte a les actualitzacions i evolucions del framework, la qual cosa pot requerir canvis constants en el codi i l\' adaptació a noves versions.
- Compromís a llarg termini: L\'adopció d\'un framework implica un compromís a llarg termini amb la tecnologia triada, cosa que pot dificultar el canvi a un altre framework en el futur.
- Potencial pèrdua d\' habilitats bàsiques: L\' ús excessiu d\' un framework pot portar a la pèrdua de pràctica i domini de les habilitats de desenvolupament web bàsiques.
Sitges i portabilitat del codi:
És important recordar que l\'ús d\'un framework específic pot crear un \"sil\" en el desenvolupament, dificultant la reutilització del codi en altres projectes que no utilitzin el mateix framework.
Alternatives:
És important destacar que el desenvolupament web no es limita únicament a l\'ús de frameworks. L\'opció de treballar amb JavaScript pur (\"Vanilla JS\") juntament amb llibreries puntuals com JQuery, Lodash, Ramda, Mocha o RxJS continua sent una alternativa viable per a molts projectes.
https://javarome.medium.com/design-noframework-bbc00a02d9b3
Abans d\' embarcar-se en el desenvolupament amb Angular, és crucial avaluar acuradament les necessitats del projecte, les habilitats de l\' equip i les implicacions a llarg termini d\' adoptar un framework.
Primers passos amb Angular: ¡Hola Mundo!¶
En aquest capítol, donarem els nostres primers passos en el món d\'Angular creant una senzilla aplicació que mostri el clàssic missatge \"Hola Mundo\". Al llarg del camí, coneixerem alguns conceptes bàsics del framework i l\'estructura d\'un projecte Angular.
Preparació de l\' entorn
- Instal·lació d\'eines prèvies:
sudo npm install -g @angular/cli [--force]
ng new my-app
cd my-app
ng serve -o
- Creació d\' un nou projecte:
ng new mi-aplicacion
Aquest comandament crearà l\' estructura bàsica del projecte.
Estructura del projecte:
L\' estructura d\' un projecte Angular típic es compon de les carpetes següents:
- src: Conté el codi font de l\' aplicació, incloent components, serveis, mòduls i altres elements.
- assets: Emmagatzema recursos estàtics com imatges, fonts i arxius CSS.
- environments: Defineix les variables d\'entorn per a diferents configuracions (desenvolupament, producció, etc.).
- node_modules: Conté les dependències de tercers instal·lades mitjançant npm.
- package.json: L\' arxiu de configuració del projecte, on s\' especifiquen les dependències, scripts i metadades del projecte.
- tsconfig.json: La configuració de TypeScript per al projecte.
Creant l\'\"Hola Món\":
- Editant el component principal:
-
Modificar el contingut de l\' arxiu
app.component.tsde la manera següent:import { Component } from '@angular/core'; Importació del decorat @Component({ selector: 'app-root', standalone: true, imports: [CommonModule, RouterOutlet], Els components standalone tenen imports templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { Un component és una classe decorada title = 'La meva primera aplicació Angular'; }Aquest codi defineix un component anomenat
AppComponentamb un selectorapp-root. El selector indica com es farà servir aquest component a la plantilla HTML. El component té un títol definit comLa meva primera aplicació Angular.
- Creant la plantilla HTML:
- L\' arxiu
app.component.html, dins les etiquetes<body>:<p>¡Hola Mundo!</p>
- Executant l\' aplicació:
- A la terminal, executar el següent comandament per iniciar el servidor de desenvolupament local:
ng serve -o
Això obrirà un navegador web a l\'adreça http://localhost:4200.
Angular CLI¶
L\'Angular CLI (Command Line Interface) actua com un assistent que permet crear, generar, compilar, executar i mantenir un projecte Javascript. Es podria programar sense ella, però els seus comandaments despleguen unes plantilles d\'elements típic d\'Angular en un instant i eviten l\'error humà en coses rutinàries.
Generant elements del framework:¶
El CLI facilita la generació dels elements bàsics que componen una aplicació Angular. Vegem alguns dels comandaments més utilitzats:
- ng generate component : Crea un nou component amb el nom especificat.
- ng generate directive : Genera una directiva per modificar el comportament del DOM.
- ng generate pipe : Crea un pipe per transformar dades en la vista.
- ng genera\'t service : Genera un servei per encapsular lògica i compartir dades entre components.
- ng generate class : Genera una classe simple de TypeScript.
- ng generate interface : Crea una interfície per definir contractes de tipus.
- ng generate enum : Genera una enumeració per definir un conjunt de valors constants.
- ng generate module : Crea un nou mòdul per organitzar el codi de l\'aplicació.
- ng genera\'t guard : Genera un guard per controlar l\'accés a rutes a l\'aplicació.
generatepuede ser resumido con g, por ejemplo: ng g component
Afegint i integrant llibreries:¶
El CLI també permet incorporar llibreries externes al projecte de forma
senzilla. Per exemple, per afegir el popular @angular/material que
ofereix components de disseny predefinits:
ng add @angular/material
Actualitzant Angular:¶
ng update --all
Compilant l\' aplicació:¶
Per generar una versió optimitzada de l\' aplicació llista per a
producció, s\' utilitza el comando ng build. Pots afegir l\'opció
--prod per habilitar optimitzacions addicionals:
ng build [--prod]
Creant un nou projecte:
Per iniciar un nou projecte Angular, s\' utilitza el comando ng new:
ng new mi-projecte-angular
Components a Angular¶
Cada component encapsula una part específica de la interfície d\'usuari (UI) i la seva funcionalitat associada, promovent la modularitat i la reutilització de codi. A tall de blocs de construcció, els components s\' acoblen per crear aplicacions web dinàmiques i escalables.
Important el decorador fonamental:
Per declarar un component, cal importar el decorador Component des del
paquet @angular/core. Aquest decorador proporciona metadades
essencials sobre el component, com el seu selector, plantilla i estils.
import { Component } from '@angular/core';
Estructura d\' un component:
Un component es defineix com una classe TypeScript decorada amb
@Component. L\' estructura bàsica d\' un component inclou:
- Selector: Un identificador únic que defineix com s\' utilitzarà
el component a l\' HTML. S\' especifica mitjançant la propietat
selectordel decorador. - Plantilla: El codi HTML que defineix l\' estructura visual del
component. Es defineix mitjançant la propietat
temperateUrldel decorador, que apunta a un arxiu HTML extern. - Estils: Les regles CSS que defineixen l\' aparença del
component. Es defineixen mitjançant la propietat
styleUrlsdel decorador, que apunta a un arxiu CSS extern. - Lògica del component: El codi TypeScript que defineix el comportament del component, com propietats, mètodes i esdeveniments. Aquest codi s\' escriu dins la classe del component.
Components Standalone:
A Angular, els components poden existir de dues maneres: com a part d\'
un mòdul o com a components Standalone. Els components Standalone no
requereixen d\' un mòdul per funcionar i es defineixen de forma
independent. Per declarar un component Standalone, s\'utilitza la
propietat estàndard del decorador @Component i s\'estableix en
true.
Els propers punts, anem a explorar com fer components dinàmics i reactius i com comunicar components i els components amb altres elements del framework.
Reactivitat¶
La reactivitat web permet simplificar la manera com s\'actualitzen les
dades entre la interfície i l\'estat de l\'aplicació. Aquesta gestió de
la reactivitat, a Angular està basada en RxJS i actualment també en
Signals. En ocasions farem servir els elements de RxJS de forma
explícita, però en el cas de les plantilles dels components, es fa de
forma molt més simplificada mitjançant interpolacions.
Interpolació¶
La interpolació a Angular és una eina fonamental per injectar dades dinàmiques a les plantilles HTML, permetent que la interfícies d\'usuari siguin reactives i s\'actualitzin automàticament en funció dels canvis en les dades.
La interpolació permet incrustar expressions JavaScript dins de les
plantilles HTML utilitzant les claus dobles {{ }}. El valor de l\'
expressió s\' avalua i s\' insereix en el lloc corresponent.
La interpolació fa que les plantilles siguin dinàmiques, ja que el contingut s\' actualitza automàticament quan els valors de les variables o les expressions canvien en el codi TypeScript. Això permet crear interfícies d\'usuari que responen a esdeveniments, interaccions de l\'usuari o canvis en les dades.
Casos d\' ús comuns:
- Mostrar valors de variables: Pots mostrar el valor de qualsevol variable o propietat d\'un component dins d\'una plantilla. Per exemple:
<p>Nombre: {{ nombre }}</p>
<img src="{{ imagenURL }}">
- Executar mètodes de components: Pots trucar a mètodes del teu component dins de la plantilla i mostrar el resultat. Per exemple:
<p>Resultat: {{ metodoComponente() }}</p>
Limitacions:
- Direcció única: La interpolació només funciona en una direcció, des de JavaScript cap a HTML. No es poden modificar variables o propietats des de la plantilla.
- Expressions simples: Les expressions dins la interpolació han de
ser simples i no han de contenir instruccions de control de flux com
if,forowhile.
Context de les expressions:
Les expressions dins la interpolació s\' avaluen en el context del component actual. No tenen accés directe a variables o funcions globals.
Bones pràctiques:
- Mantenen la simplicitat: Utilitza expressions simples i evita codi complex dins de la interpolació.
- Usa
innerHTMLper a contingut HTML: Si la variable conté codi HTML, utilitza la directiva[innerHTML]per evitar problemes de seguretat.
Vincular atributs¶
Angular permet vincular atributs HTML a propietats de components
utilitzant la sintaxi []. Per exemple:
<img [src]="product.imageUrl" alt="">
<div [style.height.px]="imageHeight"></div>
Directives ngStyle i ngClass:
Les directives ngStyle i ngClass proporcionen més flexibilitat per
manipular estils i classes CSS a partir de variables o expressions.
Ambdues requereixen la importació del mòdul CommonModule.
ngStyle:
- Permet aplicar estils dinàmics a elements HTML.
- Accepta un objecte literal o una variable que contingui parells clau-valor per als estils.
<p [ngStyle]="{'font-size': tamano+'px'}">Hola Mundo</p>
<div [ngStyle]="styleObject">...</div>
ngClass:
- Permet aplicar classes CSS dinàmicament a elements HTML.
- Accepta un objecte literal, una variable o un arrelament que contingui les classes que s\' aplicaran.
<p [ngClass]="classe">Hola Mundo</p>
<p [ngClass]="[classe, classeParrafo ]">Hola Mundo</p>
<p [ngClass]="{'text-danger': danger, 'text-info': !danger}">Hola Mundo</p>
Vinculació bidireccional [(ngModel)]¶
La vinculació bidireccional permet sincronitzar el valor d\'un element
d\'entrada (com input o textarea) amb una propietat d\'un component.
Requereix la importació del mòdul FormsModule.
<input type="text" [(ngModel)]="filterSearch" class="form-control"
name="filterDesc" id="filterDesc" placeholder="Filter...">
Vincular esdeveniments¶
Els esdeveniments dels elements HTML es poden vincular a mètodes de
components utilitzant la sintaxi ().
<button class="btn btn-sm"
[ngClass]="{'btn-danger': showImage, 'btn-primary': !showImage}"
(click)="toggleImage()">
{{showImage?' Ocultar':'Mostrar'}} imatge
</button>
<img [src]="product.imageUrl" *ngIf="showImage" alt=""
[title]="product.desc">
Directives estructurals¶
Les directives estructurals a Angular permeten controlar el flux condicional de les plantilles HTML, mostrant o ocultant contingut en funció de diversos criteris.
Directiva @if:
La directiva @if funciona similar a la instrucció if a JavaScript,
permetent mostrar o amagar contingut en base a una condició booleana. La
seva sintaxi és la següent:
@if (expresiónBooleana) {
} else {
}
Exemple:
@if (names.length > 0) {
<ul>
<li *ngFor="let name of names">{{ name }}</li>
</ul>
} else {
<p>No hay nombres para mostrar</p>
}
Directiva @for:
La directiva @for itera sobre una col·lecció de dades, mostrant un
element HTML per cada element de la col·lecció. La seva sintaxi és la
següent:
@for (elemento of colección; trackBy clave) {
<p>{{ elemento }}</p>
}
Exemple:
@for (let nombre of nombres) {
<p>{{ nombre }}</p>
}
Directiva @switch:
La directiva @switch funciona com la instrucció switcha JavaScript,
permetent mostrar o amagar contingut en funció d\'una expressió que pot
tenir diversos valors. La seva sintaxi és la següent:
@switch (expressió) {
case valor1:
break;
case valor2:
break;
default:
}
Exemple:
@switch (tipusUsuari) {
case 'admin':
<button>Administrar usuaris</button>
break;
case 'user':
<button>Ver les meves dades</button>
break;
default:
<p>Usuari no reconegut</p>
}
Directives @empty i trackBy:
Les directives @empty i trackBysón opcionals i proporcionen
funcionalitats addicionals:
-
@empty: Defineix el contingut a mostrar quan la col·lecció de dades està buida. -
trackBy: Especifica un valor únic per a cada element de la col·lecció, la qual cosa optimitza l\'actualització del DOM en iterar sobre la col·lecció.
Modernització a partir d\' Angular 17:
A partir d\'Angular 17, les directives estructurals @if, @for i
@switch s\'han simplificat i no requereixen la importació de cap
llibreria addicional. A més, la sintaxi s\'ha fet més intuïtiva i fàcil
de llegir. És molt probable que trobem exemples amb ngFor, ngIf...
Aquests encara funcionen, però no es recomana fer-los servir ja.
\@let
Amb \@let podem crear variables en l\'entorn de les plantilles. Aquestes també tenen àmbit de bloc i no poden ser reassignades. Vegem algun exemple:
@let name = 'Frodo';
<h1>Dashboard for {{name}}</h1>
Hello, {{name}}
<!-- Use with a template variable referencing an element -->
<input #name>
@let greeting = 'Hello ' + name.value;
<!-- Use with an async pipe -->
@let user = user$ | async;
https://blog.angular.dev/introducing-let-in-angular-686f9f383f0f
Interfícies¶
Les interfícies a Angular són un mecanisme fonamental per definir l\'estructura d\'objectes de dades. Proporcionen un contracte que descriu les propietats que un objecte ha de tenir i els seus respectius tipus. Això millora la llegibilitat, mantenibilitat i seguretat del codi, ja que Angular pot detectar errors en temps de compilació si s\' intenta utilitzar un objecte que no compleix amb la interfície definida.
Typescript necessita saber el contingut dels objectes i el tipus de tot. Es pot fer servir en ocasions comptades, però sempre és millor crear la interfície.
Per què utilitzar interfícies?
- Claredat i definició: Les interfícies documenten de forma explícita l\' estructura dels objectes, millorant la comprensió del codi.
- Detecció primerenca d\' errors: Angular pot detectar en temps de compilació si s\' intenta utilitzar un objecte que no compleix amb la interfície, evitant errors en temps d\' execució.
- Refactorització segura: En modificar l\' estructura d\' una interfície, Angular t\' avisarà dels llocs on s\' utilitzen objectes afectats, facilitant la refactorització segura del codi.
- Autocompletat: Els editors de codi moderns utilitzen les interfícies per oferir autocompletat i suggeriments de tipus, millorant la productivitat del desenvolupador.
Creant una interfície:
Per crear una interfície, s\' utilitza la paraula clau
interfícieseguida pel nom de la interfície i un bloc amb claus que
defineix les seves propietats:
export interface IProduct {
id: number;
description: string;
price: number;
available: Date;
imageUrl: string;
ràting: number;
}
Cada propietat de la interfície es defineix amb el seu nom, seguit de
dos punts : i el tipus de dada esperada.
Utilitzant interfícies:
Un cop definida la interfície, pots utilitzar-la per declarar variables o propietats de components que han de contenir objectes amb aquesta estructura. Per exemple:
products: IProduct[] = [
{
id: 1,
description: 'SSD hard drive',
available: new Date('2016-10-03'),
price: 75,
imageUrl: 'assets/ssd.jpg',
ràting: 5
},
... altres productes
];
En aquest exemple, l\'arrelament està tipat com un arrelament
d\'objectes que compleixen amb la interfície IProduct. Això garanteix
que cada objecte de l\' arrelament tingui les propietats esperades i del
tipus correcte.
Cicle de vida dels components¶
Estructura d\' una aplicació web típica#
Els components són elements reutilitzables per naturalesa. Es poden importar i utilitzar en altres components, promovent la modularitat i evitant la duplicació de codi. Això facilita la creació d\' interfícies d\' usuari consistents i escalables.
Els components tenen un cicle de vida definit per una sèrie de mètodes que s\' anomenen en diferents moments de la seva existència. Alguns dels mètodes més importants són:
constructor: Es diu quan es crea una instància del component. El constructor s\' utilitza per injectar dependències i configurar valors inicials. No es recomana col·locar lògica d\'inicialització aquí perquè es pot executar abans que les propietats d\'entrada estiguin configurades.ngOnInit: S\'anomena una vegada que el component s\'ha inicialitzat i està llest per usar-se.ngAfterContentInit: S\' anomena després que s\' hagi inicialitzat el contingut del component.ngAfterViewInit: S\'anomena després que s\'hagi renderitzat el component completament.ngOnChanges: S\'anomena cada vegada que canvia una propietat d\'entrada del component. Es pot executar massa vegades.ngDoCheck: S\'anomena en cada cicle de detecció de canvis d\'Angular. Es fa servir rarament perquè s\'executa moltes vegades, només si els esdeveniments o canvis no són suficients.ngOnDestroy: Es diu quan el component es destrueix.
ngOnInit¶
OnInit és una interfície que obliga a implementar el mètode
ngOnInit(). Aquesta interfície forma part del cicle de vida d\' un
component, i el seu ús és essencial per inicialitzar el component un cop
Angular ha configurat les seves propietats d\' entrada.
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ProductsService } from '.. /products.service';
@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.subscriu(params => {
const productId = params['id'];
Aquí es pot fer servir el productId per obtenir els detalls del producte
des del servei de productes
this.productsService.getProductById(productId).(product => {
Maneig de les dades del producte
console.log(product);
});
});
}
}
El mètode ngOnInits\'anomena una vegada que la inicialització del
component està completa. És especialment útil per a qualsevol tipus d\'
inicialització que hagi d\' ocórrer després que Angular hagi configurat
totes les propietats d\' entrada del component.
En executar
ngOnInitnos assegurem que totes les dependències de dades d'entrada han estat configurades i obtingudes. És el moment que el component demani noves dades si les necessita. Gairebé tots els components que obtenen dades d'un servei l'implementen.
Comunicació entre components¶
Els components es poden anellar en una estructura jeràrquica, la qual
cosa permet crear interfícies d\' usuari complexes i modulars. Un
component pare pot contenir un o més components fills, i cada component
fill pot contenir al seu torn altres components fills. Tot això es fa
important el component i usant l\' etiqueta HTML definida a la propietat
selector.
Els components poden comunicar-se entre si mitjançant diferents mecanismes, com ara:
- Entrada i sortida de dades: Els components poden intercanviar dades a través de propietats d\' entrada i sortida \@Input i \@Output.
- Serveis: Els components poden compartir lògica i dades mitjançant serveis, que són classes que encapsulen funcionalitat reutilitzable.
- Esdeveniments: Els components poden emetre esdeveniments que altres components poden escoltar i respondre.
Al seu torn, aquesta comunicació sol ser internament reactiva mitjançant
Observables o Signals. Veurem com funciona la comunicació típica
entre components pares i fills i més endavant veurem com comunicar-se a
través d\'un servei.
\@Input¶
Diagrama de comunicació entre components pares i fills. #
Els components pares poden enviar dades als seus components fills mitjançant propietats d\' entrada, i els components fills poden emetre esdeveniments que els seus components pares poden escoltar.
HTML de la plantilla del component pare. Observa [p]="product":
<h1>Catalogue</h1>
<div class="row row-cols-1 row-cols-md-2 g-4">
<div class="col">
@for (product of products; track $index;){
<app-product-item [p]="product">
</app-product-item>
}
</div>
</div>
El component fill:
import { Component, Input } from '@angular/core';
...
@Component({
selector: 'app-product-item',
templateUrl: './product-item.component.html',
})
export class ProductItemComponent {
@Input() p!: Product;
}
- La propietat
pestà decorada amb@Input, la qual cosa permet que aquest component rebi un valor des del seu component pare. p!: Productindica quepés una propietat obligatòria de tipusProduct. El!és una asserció de no nul/no indefinit, el que li diu a TypeScript que confiï quepserà inicialitzada correctament.
El decorador @Input a Angular s\'utilitza per marcar una propietat
d\'un component com a pública, permetent que aquest rebi dades des d\'un
component pare, la qual cosa és essencial per a la comunicació entre
components a Angular i facilita el flux de dades de pares a fills.
Aquest enfocament promou la modularitat en facilitar la creació de
components reutilitzables i modulars, millora la claredat en fer
explícit quines dades necessita un component per funcionar i optimitza
el manteniment en separar clarament la lògica de la presentació de
dades, la qual cosa resulta en un codi més net i fàcil de gestionar en
aplicacions complexes.
Existeixen diverses opcions i formes d\' usar @Input, cadascuna amb
les seves característiques i beneficis.
Exemple 1: @Input Requerit
@Input({ required: true }) name: string;
En aquest exemple, la propietat name està marcada com a requerida. Si
el component pare no proporciona un valor per a name, Angular generarà
un error. Això assegura que tots els components fills tinguin les dades
necessàries per funcionar correctament.
Exemple 2: @Input amb Getters i Setters
private myCourses: Course[];
@Input()
get courses() {
return this.myCourses;
}
set courses(courses: Course[]) {
this.myCourses = courses;
}
Aquí es defineix una propietat courses amb @Input, però s\'utilitza
un enfocament més avançat amb getters i setters. Això permet controlar
la forma en què les dades s\' assignen i s\' hi accedeixen. En aquest
cas, myCourses és una propietat privada i courses és una propietat
pública que exposa myCourses de manera controlada. Cada vegada que s\'
assignen nous cursos, es pot agregar lògica addicional en el setter per
manejar l\' assignació de les dades.
Exemple 3: @Input amb Àlies
@Input('userName') name: string;
Aquest exemple mostra com es pot fer servir un àlies amb @Input. La
propietat pública del component fill serà name, però el component pare
pot passar-li dades fent servir el nom userName. Això és útil quan es
vol mantenir una convenció de noms específica en el component fill, però
es vol proporcionar un nom més descriptiu o clar per al component pare.
Property has no initializers...¶
A TypeScript, l\'error \"Property has no initializers...\" es produeix
quan es declara una propietat sense assignar-li un valor inicial. Això
és particularment rellevant quan s\' utilitza el decorador @Input() a
Angular, ja que indica que una propietat serà inicialitzada pel
component pare, la qual cosa podria no succeir sempre. És important
manejar aquest cas adequadament per evitar errors en temps d\' execució
i assegurar que el codi sigui robust.
Existeixen diverses formes de solucionar aquest problema a TypeScript:
- Utilitzar l\'operador
!:
-
@Input() products!: IProduct[];
Aquesta opció no és recomanable perquè l\'operador
!li diu a TypeScript que confiï que la propietat serà inicialitzada eventualment, cosa que podria no ser certa. Això pot ocultar errors potencials i portar a problemes difícils de depurar.
- Utilitzar
| undefinedo?:
-
@Input() products?: IProduct[]; o @Input() products: IProduct[] | undefined;
Aquesta és la solució més recomanable. Indica explícitament que la propietat pot no ser inicialitzada, permetent a TypeScript manejar correctament aquest cas. Això obliga a tractar la propietat com a potencialment
undefined, la qual cosa ajuda a prevenir errors en temps d\' execució.
- Desactivar
strictPropertyInitializationentsconfig.json:
-
{ "compilerOptions": { "strictPropertyInitialization": false } }
Aquesta opció no és recomanable perquè desactiva la verificació estricta d\' inicialització de propietats en tot el projecte. Això pot portar a passar per alt altres errors d\' inicialització, reduint la seguretat i robustesa del codi.
Per manejar adequadament la inicialització de propietats a Angular i
TypeScript, és important usar tècniques que indiquin clarament les
possibles opcions d\' inicialització. Usar | undefined o ? és la
millor pràctica, ja que proporciona una forma segura i explícita de
tractar amb propietats opcionals. S\'hauria d\'evitar l\'ús de
l\'operador !, llevat que sigui una propietat que, segur que ha
d\'arribar.
\@Output¶
A Angular, el decorador @Output s\'utilitza per permetre que un
component fill emeti esdeveniments personalitzats que puguin ser
capturats pel seu component pare. Això és útil per a la comunicació
d\'esdeveniments i dades des d\'un component fill cap al seu pare,
facilitant la interacció entre components.
L\'ús del decorador @Output a Angular permet una comunicació eficaç
entre components fill i pare mitjançant esdeveniments personalitzats. En
emetre esdeveniments des del fill i capturar-los en el pare, es pot
gestionar fàcilment la lògica de l\' aplicació i mantenir una separació
clara de responsabilitats entre els components.
En aquest exemple, un component fill emet un esdeveniment personalitzat
quan es produeix un canvi en la puntuació (ratting). El component pare
escolta aquest esdeveniment i actua en conseqüència.
Pas 1: Configuració al Component Fill¶
Primer, definim un EventEmitter en el component fill que emetrà
l\'esdeveniment quan canviï la puntuació:
import { Component, EventEmitter, Output } from '@angular/core';
@Component({
selector: 'app-rating',
templateUrl: './rating.component.html',
})
export class RatingComponent {
@Output() rattingChanged = new EventEmitter<number>();
auxRatting: number;
puntuar(i: number): void {
this.auxRatting = i;
this.rattingChanged.emit( this.auxRatting);
}
}
@Output() rattingChanged = new EventEmitter<number>();: Defineix un esdevenimentrattingChangedque emetrà un número.puntuar(i: number): void: Aquesta funció s\' anomena quan es canvia la puntuació i emet el nou valor usantrattingChanged.emit( this.auxRatting).
Pas 2: Captura de l\'Esdeveniment al Component Pare¶
En el component pare, s\' escolta l\' esdeveniment emès pel fill i es defineix una funció per manejar el canvi de puntuació:
<!-- product-list.component.html -->
<app-rating (rattingChanged)="changeRatting($event, product)"></app-rating>
import { Component } from '@angular/core';
import { Product } from './product.model';
@Component({
selector: 'app-product-list',
templateUrl: './product-list.component.html',
})
export class ProductListComponent {
products: Product[];
changeRatting(stars: number, p: Product): void {
p.ratting = stars;
}
}
(rattingChanged)="changeRatting($event, product)": El component pare escolta l\'esdevenimentrattingChangeddel component fill i crida a la funcióchangeRatting, passant el valor de l\'esdeveniment$eventi el producte corresponentproduct.changeRatting(stars: number, p: Product): void: Aquesta funció actualitza la puntuació (ratting) del producte amb el nou valor rebut des del component fill.
Pipes¶
Els Pipes a Angular permeten transformar les dades directament a les plantilles abans de mostrar-les. Igual que els filtres a Linux, els pipes prenen una entrada, la processen i retornen una sortida transformada. Angular proporciona diversos pipes predefinits, però també és possible crear pipes personalitzats per satisfer necessitats específiques.
Ús de Pipes Predefinits¶
A Angular, els pipes s\'utilitzen a les plantilles després de la
variable, separats pel símbol |. Un punt important és que els pipes no
modifiquen la variable original, sinó que en retornen una versió
transformada. Alguns pipes predefinits a Angular inclouen:
uppercase: Converteix el text a majúscules.currency: Formatea un número com una moneda.date: Formatea una data segons el format especificat.
Aquí hi ha un exemple de com usar alguns d\'aquests pipes predefinits en una taula que mostra una llista de productes:
<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>
En aquest exemple: - El pipe uppercasees fa servir per convertir la
descripció del producte a majúscules. - El pipe currency formatea el
preu del producte en euros. - El pipe date formatea la data de
disponibilitat del producte en el format dd/MM/y.
Creació de Pipes Personalitzats¶
Si els pipes predefinits no compleixen amb totes les necessitats de transformació de dades, Angular permet crear pipes personalitzats. La creació d\' un pipe personalitzat implica els següents passos:
- Generar el Pipe: Utilitzar el comandament Angular CLI per generar l\' esquelet del pipe.
- ng g pipe pipes/nombreDelPipe
- Implementar el Pipe: Definir la lògica de transformació en el pipe generat. Aquí hi ha un exemple d\'un pipe personalitzat que filtra productes basant-se en un criteri de recerca:
-
import { Pipe, PipeTransform } from '@angular/core'; import { Product } from '.. /product/product';
@Pipe({ name: 'productFilter' }) export class ProductFilterPipe implements PipeTransform { transform(products: Product[], filterBy: string): Product[] { const filter = filterBy ? filterBy.toLocaleLowerCase() : null; return filter ? products.filter( p = > p.name.toLocaleLowerCase().includes(filter)) : products; } }En aquest pipe personalitzat:
- La funció
va transformardos arguments: una llista de productes i un criteri de recerca. - Converteix el criteri de recerca a minúscules per realitzar una comparació insensible a majúscules.
- Filtra els productes el nom dels quals inclou el criteri de recerca.
- La funció
- Usar el Pipe Personalitzat: Aplicar el pipe a la plantilla per filtrar els productes segons el criteri de recerca.
- {{ product.name }} {{ product.price | currency:'EUR':'symbol' }} {{ product.available | date:'dd/MM/y' }}
Directives a Angular¶
Les directives a Angular són un mecanisme potent per manipular el DOM de manera declarativa. Poden modificar elements HTML i el seu comportament en funció de la lògica de l\' aplicació. Existeixen tres tipus principals de directives a Angular:
- Directives de component: Es defineixen mitjançant el selector en
el decorador
@Componenti són les que hem creat fins ara. - Directives d\' atribut: Modifiquen el comportament d\' un
element. Exemples comuns són
NgClassiNgStyle. - Directives estructurals: Manipulen l\' estructura del DOM, com
ngIf,ngForingSwitch, controlant si un element es mostra o no.
Crear directives d\' atribut¶
Podem crear les nostres pròpies directives d\'atribut per afegir
comportaments personalitzats als elements HTML. Vam crear una directiva
anomenada Ressaltat que canviarà el color de fons d\'un element quan
el ratolí passi sobre ell.
Primer, generem la directiva amb Angular CLI:
ng g directive directives/resaltado
Després, implementem la lògica a l\'arxiu resaltado.directive.ts:
import { Directive, ElementRef, HostListener, Input } from '@angular/core';
@Directive({
selector: '[appResaltado]'
})
export class ResaltadoDirective {
@Input('appResaltado') nouColor: string;
constructor(private el: ElementRef) {}
@HostListener('mouseenter') entrarMouse() {
this.el.nativeElement.style.backgroundColor = this.nouColor;
}
@HostListener('mouseleave') saleMouse() {
this.el.nativeElement.style.backgroundColor = null;
}
}
A l\'arxiu de plantilla HTML, utilitzem la nostra directiva Ressaltat:
<div class="col-md-4" [appResaltat]="color">
<!-- Contingut de l'element -->
</div>
ElementRef vs Renderer2¶
ElementRef ens dóna una referència directa a un element del DOM un cop
renderitzat. No obstant això, utilitzar ElementRef pot exposar riscos
de seguretat i no és la millor pràctica en aplicacions que necessiten
ser multiplataforma. En canvi, Renderer2 proporciona una abstracció
addicional, permetent manipular el DOM de manera segura i compatible amb
aplicacions de servidor i Web Workers.
Exemple amb Renderer2:
import { Directive, ElementRef, HostListener, Input, Renderer2 } from '@angular/core';
@Directive({
selector: '[appMostrar]'
})
export class MostrarDirective {
@Input() elementMostrar!: any;
constructor(private el: ElementRef, private renderer: Renderer2) {}
@HostListener('mouseenter') entrarMouse() {
this.renderer.setStyle( this.'display', '');
}
@HostListener('mouseleave') saleMouse() {
this.renderer.setStyle( this.'display', 'none');
}
}
A l\'arxiu de plantilla HTML, utilitzem la nostra directiva Mostrar:
<li appMostrar
[elementMostrar]="ocultar">
<!-- Contingut de l'element -->
</li>
<p #ocultar style="margin-bottom: 0; display:none;">
Producció: <span *appForDelay="100; let p of placa.production">{{p}}W</span>
</p>
Crear directives estructurals¶
Les directives estructurals, com ngIf, ngFor i ngSwitch, utilitzen
l\'asterisc (*) per simplificar la manipulació d\'etiquetes
<ng-template> que envolten l\'element que estan manipulant.
Vam crear una directiva estructural anomenada ForDelay que retarda la
visualització d\'elements en un bucle.
Primer, generem la directiva:
ng g directive directives/for-delay
Implementem la lògica a l\'arxiu for-delay.directive.ts:
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
@Directive({
selector: '[appForDelay]'
})
export class ForDelayDirective {
private items: Array<number> = [];
private delay = 0;
constructor(private templateRef: TemplateRef<any>, private viewContainer: ViewContainerRef) {}
@Input() set appForDelay(t: number) {
this.delay = t;
}
@Input() set appForDelayOf(array: number[]) {
this.items = array;
this.renderItems();
}
private async renderItems() {
for (let item of this.items) {
this.viewContainer.createEmbeddedView(this.templateRef, {
$implicit: item
});
await this.delayTime( this.delay);
}
}
private delayTime(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
A l\'arxiu de plantilla HTML, utilitzem la nostra directiva ForDelay:
<span *appForDelay="100; let p of placa.production">{{p}}W</span>
Esdeveniments¶
A Angular, la manera més simple de gestionar esdeveniments és amb la
sintaxi ( ) en l\'element HTML corresponent. Això crea una subscripció
externa. Per a una subscripció interna, fem servir @HostListener.
Exemple de subscripció interna amb @HostListener:
import { Directive, HostListener } from '@angular/core';
@Directive({
selector: '[appScroll]'
})
export class ScrollDirective {
imgY = 100;
@HostListener('window:scroll', ['$event'])
homeScroll($event: Event) {
let scrollOffset = window.scrollY;
this.imgY = 100 - scrollOffset / 2;
if (this.imgY < 0) {
this.imgY = 20;
}
}
}
A l\'arxiu de plantilla HTML, utilitzem la nostra directiva Scroll:
<div appScroll>
<!-- Contingut de l'element -->
</div>
Mòduls a Angular¶
A mesura que una aplicació d\'Angular creix en mida i complexitat, no és
sostenible mantenir tota la funcionalitat en un sol mòdul AppModule i
organitzar els components únicament en directoris. A Angular, els mòduls
(NgModules) són la manera recomanada d\'organitzar i estructurar
aplicacions grans i complexes. Un mòdul és una classe amb el decorador
@NgModule que defineix un contenidor per a un conjunt coherent de
components, directives, pipes i serveis.
Angular permet la càrrega diferida (lazy loading) de mòduls, cosa que ajuda a millorar el rendiment de l\'aplicació carregant mòduls només quan es necessiten. Aquesta tècnica és especialment útil per a grans aplicacions amb moltes rutes i funcionalitats diverses.
Arrays del Decorador \@NgModule¶
El decorador @NgModule defineix diversos arrays que especifiquen com
s\' organitza el mòdul:
-
bootstrap: Només s\' utilitza a l
' AppModule, defineix el component inicial que s\' ha de carregar en iniciar l\' aplicació. -
declarations: Conté els components, directives i pipes que pertanyen a aquest mòdul. Cada component, directiva o pipe només pot estar declarat en un únic mòdul, però pot ser exportat per ser utilitzat en altres mòduls. La declaració és necessària perquè aquests elements siguin compilats i utilitzats en l\' aplicació.
-
exports: Permet compartir els components, directives i pipes amb altres mòduls. També es poden exportar mòduls complets per crear un meta-mòdul que importi altres mòduls. No obstant això, no es poden exportar serveis, ja que aquests són injectables a nivell global per defecte.
-
imports: Conté altres mòduls (de tercers o propis) que el mòdul necessita. El que s\'importa són els elements que aquests mòduls exporten.
-
providers: Defineix els serveis que han d\' estar disponibles en tota l\' aplicació. Normalment, només es declara a l
'AppModuleper evitar conflictes i assegurar que el servei estigui disponible de manera global. Els serveis poden ser autoinjectables ambprovidedIn: 'root'en el seu decorador@Injectable.
Criteris per Separar l\' Aplicació en Mòduls¶
Separar una aplicació en mòduls ben definits facilita la gestió, el manteniment i l\' escalabilitat del codi. A continuació es detallen alguns criteris per organitzar els mòduls en una aplicació Angular:
-
Mòduls de Domini: S\' utilitzen per organitzar el codi sense representar necessàriament una ruta. Per exemple, un mòdul que conté components relacionats amb un menú de navegació.
-
Mòduls de Secció: Representen seccions, rutes o pàgines específiques de l\' aplicació. Per exemple, mòduls per gestionar productes, clients o factures.
-
Mòduls de Serveis: S\' utilitzen per organitzar serveis que seran utilitzats en tota l\' aplicació. Normalment, aquests mòduls només s\'importen a l
'AppModule. Un exemple típic ésHttpClientModule. -
Mòduls de Components: Contenen components que poden ser reutilitzats en diversos mòduls de l\' aplicació. Per exemple, un mòdul que conté una vista modal per mostrar imatges en mida completa.
Exemple d\'un Mòdul a Angular¶
A continuació, es mostra un exemple de com definir un mòdul a Angular.
Definició del Mòdul¶
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { ProductListComponent } from './product-list/product-list.component';
import { ProductDetailComponent } from './product-detail/product-detail.component';
@NgModule({
declarations: [
ProductListComponent,
ProductDetailComponent
],
imports: [
CommonModule,
FormsModule
],
exports: [
ProductListComponent,
ProductDetailComponent
]
})
export class ProductModule { }
Descripció del Mòdul¶
En aquest exemple, ProductModule és un mòdul que encapsula tota la
funcionalitat relacionada amb els productes en l\'aplicació. Els
components ProductListComponent i ProductDetailComponent són
declarats i exportats perquè puguin ser utilitzats en altres mòduls si
és necessari.
- declarations: Declara
ProductListComponentiProductDetailComponentperquè puguin ser utilitzats dins del mòdul. - imports: Importa
CommonModuleper obtenir accés a les directives comunes d\'Angular, comngIfingFor, iFormsModuleper treballar amb formularis basats en plantilles. - exports: Exporta els components
ProductListComponentiProductDetailComponentperquè puguin ser utilitzats en altres mòduls de l\' aplicació.
Organització de Mòduls en una Aplicació Gran¶
Per a una aplicació gran, és essencial seguir una estructura modular clara i ben definida. A continuació, es descriu una possible estructura de mòduls per a una aplicació complexa:
Mòdul Principal (AppModule)¶
L ' AppModuleés el mòdul principal de l\' aplicació i generalment
conté la configuració dels serveis globals, l\' enrutador principal i el
component arrel.
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HttpClientModule } from '@angular/common/http';
import { CoreModule } from './core/core.module';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule,
CoreModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Mòdul de Core¶
El CoreModulepot contenir serveis singleton que s\' utilitzen en tota
l\' aplicació i altres elements que haurien de ser únics i carregats una
sola vegada.
import { NgModule, Optional, SkipSelf } from '@angular/core';
import { CommonModule } from '@angular/common';
@NgModule({
imports: [
CommonModule
],
providers: [
Serveis singleton
],
exports: [
Components i directives compartits
]
})
export class CoreModule {
constructor(@Optional() @SkipSelf() parentModule: CoreModule) {
if (parentModule) {
throw new Error('CoreModule is already loaded. Import it in the AppModule only.');
}
}
}
Mòdul de Característica (Feature Module)¶
Els mòduls de característica encapsulen funcionalitat específica d\' una secció de l\' aplicació, com gestió de productes, clients o factures.
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ProductRoutingModule } from './product-routing.module';
import { ProductListComponent } from './product-list/product-list.component';
import { ProductDetailComponent } from './product-detail/product-detail.component';
@NgModule({
declarations: [
ProductListComponent,
ProductDetailComponent
],
imports: [
CommonModule,
ProductRoutingModule
]
})
export class ProductModule { }
Mòdul de Rutes (Routing Module)¶
El mòdul de rutes s\' encarrega de definir les rutes per a la navegació de l\' aplicació.
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ProductListComponent } from './product-list/product-list.component';
import { ProductDetailComponent } from './product-detail/product-detail.component';
const routes: Routes = [
{ path: 'products', component: ProductListComponent },
{ path: 'products/:id', component: ProductDetailComponent }
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class ProductRoutingModule { }