Salta el contingut

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

  1. Instal·lació d\'eines prèvies:
sudo npm install -g @angular/cli [--force]
ng new my-app
cd my-app 
ng serve -o
  1. 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\":

  1. Editant el component principal:
  • Modificar el contingut de l\' arxiu app.component.ts de 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 AppComponent amb un selector app-root. El selector indica com es farà servir aquest component a la plantilla HTML. El component té un títol definit com La meva primera aplicació Angular.

  1. Creant la plantilla HTML:
  • L\' arxiu app.component.html, dins les etiquetes <body>:
    <p>¡Hola Mundo!</p>
    
  1. 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 selector del decorador.
  • Plantilla: El codi HTML que defineix l\' estructura visual del component. Es defineix mitjançant la propietat temperateUrl del 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 styleUrls del 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, for o while.

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 innerHTML per 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 p està decorada amb @Input, la qual cosa permet que aquest component rebi un valor des del seu component pare.
  • p!: Product indica que pés una propietat obligatòria de tipus Product. El ! és una asserció de no nul/no indefinit, el que li diu a TypeScript que confiï que p serà 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:

  1. 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.

  1. Utilitzar | undefined o ?:
  • @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ó.

  1. Desactivar strictPropertyInitialization en tsconfig.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 esdeveniment rattingChanged que emetrà un número.
  • puntuar(i: number): void: Aquesta funció s\' anomena quan es canvia la puntuació i emet el nou valor usant rattingChanged.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\'esdeveniment rattingChanged del component fill i crida a la funció changeRatting, passant el valor de l\'esdeveniment $event i el producte corresponent product.
  • 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:

  1. Generar el Pipe: Utilitzar el comandament Angular CLI per generar l\' esquelet del pipe.
  • ng g pipe pipes/nombreDelPipe
  1. 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.
  1. 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:

  1. Directives de component: Es defineixen mitjançant el selector en el decorador @Component i són les que hem creat fins ara.
  2. Directives d\' atribut: Modifiquen el comportament d\' un element. Exemples comuns són NgClass i NgStyle.
  3. Directives estructurals: Manipulen l\' estructura del DOM, com ngIf, ngFor i ngSwitch, 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 'AppModule per evitar conflictes i assegurar que el servei estigui disponible de manera global. Els serveis poden ser autoinjectables amb providedIn: '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 és HttpClientModule.

  • 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 ProductListComponent i ProductDetailComponent perquè puguin ser utilitzats dins del mòdul.
  • imports: Importa CommonModule per obtenir accés a les directives comunes d\'Angular, com ngIf i ngFor, i FormsModuleper treballar amb formularis basats en plantilles.
  • exports: Exporta els components ProductListComponent i ProductDetailComponent perquè 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 { }