Angular

Guia Completa de Desenvolupament

Novembre 2025

Un framework JavaScript per a aplicacions escalables

Contingut

  • TypeScript i Conceptes Base
  • Elements del Framework
  • Formularis i Validació
  • Arquitectura i Patterns
  • Desplegament i Producció
  • Biblioteques i Ecosistema
  • Aplicacions Mòbils

Requisits Previs

# Instal·lació de dependències
sudo apt install nodejs
sudo apt install npm

# Instal·lar Angular CLI
sudo npm install -g @angular/cli

# Actualitzar Node.js
sudo npm install -g n
sudo n stable
sudo npm install -g npm

TypeScript

Tipat Estàtic per a JavaScript

Què és TypeScript?

  • Superset de JavaScript amb tipat estàtic
  • Cal transpilar-lo a JavaScript
  • Els navegadors no l'entenen directament

Avantatges

  • Detecta errors en temps d'edició
  • Autocompletació inteligent
  • Evita variables no definides
  • Millor documentació

Tipus de Dades

// Assignació explícita
let nombre: string = 'Joaquin';
let numero: number = 123;
let booleano: boolean = true;
let hoy: Date = new Date();

// Arrays
let personajes: string[] = ['Paul', 'Jessica'];
let numeros: Array<number> = [1, 2, 3];

// Objectes
interface Persona {
  nombre: string;
  edad: number;
  familia?: string;  // Opcional
}

Funcions i Paràmetres

function saludar(
  quien: string,                    // Obligatori
  momento?: string,                  // Opcional
  objeto: string = 'la mano'        // Valor per defecte
): string {
  return `${quien} saluda con ${objeto}`;
}

// Funcions fletxa
const multiplicar = (x: number, y: number): number => x * y;

Context de 'this' en Funcions Fletxa

❌ PROBLEMA:

const toptero = {
  posicion: 'aire',
  comunica() {
    setTimeout(function() {
      console.log(this.posicion); // undefined
    }, 1000);
  }
};

✅ SOLUCIÓ:

const toptero = {
  posicion: 'aire',
  comunica() {
    setTimeout(() => {
      console.log(this.posicion); // 'aire'
    }, 1000);
  }
};

Classes i Modificadors

class Recolector {
  private piloto: string = 'fremen';
  
  constructor(
    public identificador: string,
    public propietario: string,
    private lugar?: string
  ) {}
  
  public getPiloto(): string {
    return this.piloto;
  }
}

const rec = new Recolector('1234', 'cofradia');
console.log(rec.getPiloto()); // ✅ Correcte
console.log(rec.piloto);      // ❌ Error: privat

Elements del Framework

Components, Servicis, Rutes...

Crear una Aplicació Angular

# Crear projecte
ng new my-app

# Navegar al directori
cd my-app

# Executar en desenvolupament
ng serve -o

S'obri automàticament en http://localhost:4200

Estructura d'un Component

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

@Component({
  selector: 'app-root',
  standalone: true,
  templateUrl: './app.component.html',
  styleUrl: './app.component.css'
})
export class AppComponent {
  title = 'hola-mon';
  nombre = 'Pepe';
}

Component HTML: <app-root></app-root>

Vinculacions (Bindings)

<!-- Interpolació -->
<p>{{nombre}}</p>

<!-- Vincular propietats -->
<img [src]="urlImagen" alt="">

<!-- ngClass -->
<div [ngClass]="{'active': isActive, 'error': hasError}">
  Contenido
</div>

<!-- Vicnulació bidireccional -->
<input [(ngModel)]="filterSearch" class="form-control">

<!-- Eventos -->
<button (click)="toggleImage()">Click</button>

Directives Estructurals

<!-- ngIf -->
<div *ngIf="mostrar">Es visible</div>

<!-- ngFor -->
<ul>
  <li *ngFor="let item of items; let i = index">
    {{i}} - {{item}}
  </li>
</ul>

<!-- ngSwitch -->
<span [ngSwitch]="property">
  <span *ngSwitchCase="'val1'">Valor 1</span>
  <span *ngSwitchDefault>Altre</span>
</span>

Directives Modernes (Angular 17+)

@if (items.length > 0) {
  @for (item of items; track $index) {
    <p>{{$index}}: {{item}}</p>
  }
} @else {
  <p>Cap element</p>
}
  • Més optimitzat
  • Mejor control de rendering
  • Sintaxi més neta

Components Niuats (@Input, @Output)

// Fill
@Component({
  selector: 'app-product-item'
})
export class ProductItemComponent {
  @Input({ required: true }) product: Product;
  @Output() ratingChanged = new EventEmitter<number>();
  
  puntuar(stars: number): void {
    this.ratingChanged.emit(stars);
  }
}
<!-- Pare -->
<app-product-item 
  [product]="prod"
  (ratingChanged)="changeRating($event, prod)">
</app-product-item>

Interfaces

// Crear interfície
ng g interface interfaces/i-product

// Definició
export interface IProduct {
  id: number;
  description: string;
  price: number;
  available: Date;
  imageUrl: string;
  rating: number;
}

// Ús
export class ProductComponent {
  products: IProduct[] = [...];

Rutes i Navegació

Definir Rutes

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

export const routes: Routes = [
  { path: 'home', component: HomeComponent },
  
  { 
    path: 'products', 
    component: ProductListComponent,
    canActivate: [AuthGuard] 
  },
  
  { 
    path: 'product/:id', 
    component: ProductDetailComponent 
  },
  
  { path: '**', redirectTo: 'home' }  // Catch-all
];

Navegar a les Rutes

<!-- HTML -->
<a [routerLink]="['home']" 
   [routerLinkActive]="['active']">
  Home
</a>

<a [routerLink]="['product', product.id]">
  {{product.name}}
</a>
// TypeScript
export class ProductComponent {
  constructor(private router: Router) { }
  
  viewProduct(id: number): void {
    this.router.navigate(['/product', id]);
  }
}

Obtindre Paràmetres de Ruta

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

export class ProductDetailComponent implements OnInit {
  productId: number;
  
  constructor(private route: ActivatedRoute) { }
  
  ngOnInit(): void {
    // Observable
    this.route.params.subscribe(params => {
      this.productId = params['id'];
    });
  }
}

Servicis i HTTP

Crear un Servici

ng g service services/product
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class ProductService {
  private apiUrl = 'http://api.example.com/products';
  
  constructor(private http: HttpClient) { }
  
  getProducts(): Observable<IProduct[]> {
    return this.http.get<IProduct[]>(this.apiUrl);
  }
}

Injecció de Dependències

export class ProductComponent implements OnInit {
  products: IProduct[] = [];
  
  // Angular injecta automàticament el servici
  constructor(private productService: ProductService) { }
  
  ngOnInit(): void {
    this.productService.getProducts().subscribe(
      prods => this.products = prods,      // Èxit
      error => console.error(error),        // Error
      () => console.log('Carregat')        // Completat
    );
  }
}

Operacions CRUD

export class ProductService {
  getProducts(): Observable<IProduct[]> {
    return this.http.get<IProduct[]>(this.apiUrl);
  }
  
  getProduct(id: number): Observable<IProduct> {
    return this.http.get<IProduct>(`${this.apiUrl}/${id}`);
  }
  
  createProduct(product: IProduct): Observable<IProduct> {
    return this.http.post<IProduct>(this.apiUrl, product);
  }
  
  updateProduct(product: IProduct): Observable<IProduct> {
    return this.http.put<IProduct>(
      `${this.apiUrl}/${product.id}`, 
      product
    );
  }
  
  deleteProduct(id: number): Observable<void> {
    return this.http.delete<void>(`${this.apiUrl}/${id}`);
  }
}

Observables vs Signals

Comparativa

Característica Observable Signal Promesa
Múltiples valors
Cancelable
Lazy
Operadors

Observables (RxJS)

import { map, filter } from 'rxjs/operators';

this.dataService.getNumbers().pipe(
  map(nums => nums.map(n => n * 2)),
  filter(nums => nums.length > 0)
).subscribe(
  result => console.log(result),
  error => console.error(error)
);

Signals (Angular 17+)

import { signal, computed } from '@angular/core';

export class CounterComponent {
  count = signal(0);
  doubleCount = computed(() => this.count() * 2);
  
  increment(): void {
    this.count.update(c => c + 1);
  }
  
  reset(): void {
    this.count.set(0);
  }
}
<p>Count: {{count()}}</p>
<p>Double: {{doubleCount()}}</p>
<button (click)="increment()">+1</button>

Guards i Protecció

CanActivate Guard

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

export const authGuard = (route: any, state: any) => {
  const authService = inject(AuthService);
  const router = inject(Router);
  
  if (authService.isLoggedIn()) {
    return true;
  }
  
  router.navigate(['/login']);
  return false;
};

// Ruta
{ 
  path: 'products',
  canActivate: [authGuard],
  component: ProductListComponent 
}

Formularis

Plantilla vs Reactius

Comparativa de Formularis

Aspecte Plantilla Reactiu
Validació HTML5 + Angular TypeScript
Complexitat Simple Avançada
Control Template Component
Recomanat Login simple Formularis complexos

Formularis de Plantilla

<form #form="ngForm" (ngSubmit)="create(form)">
  <input type="text"
         name="description"
         [(ngModel)]="product.description"
         minlength="5"
         required
         #descModel="ngModel"
         [class.is-invalid]="descModel.invalid && descModel.touched">
  
  <div *ngIf="descModel.invalid && descModel.touched">
    Description required (5-600 chars)
  </div>
  
  <button type="submit" [disabled]="form.invalid">
    Submit
  </button>
</form>

Formularis Reactius

import { FormBuilder, FormGroup, Validators } from '@angular/forms';

export class ProductFormComponent {
  formulario: FormGroup;
  
  constructor(private fb: FormBuilder) {
    this.formulario = this.fb.group({
      name: ['', [Validators.required, Validators.minLength(5)]],
      price: [0, Validators.min(0.01)],
      description: ['']
    });
  }
  
  get nameNotValid(): boolean {
    return this.formulario.get('name')?.invalid &&
           this.formulario.get('name')?.touched;
  }
  
  create(): void {
    if (this.formulario.valid) {
      console.log(this.formulario.value);
    }
  }
}

Validadors Personalitzats

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

function minPriceValidator(min: number): ValidatorFn {
  return (control: AbstractControl): ValidationErrors | null => {
    if (control.value && control.value < min) {
      return { minPrice: { min } };
    }
    return null;
  };
}

// Ús
this.formulario = this.fb.group({
  price: [0, [Validators.required, minPriceValidator(0.01)]]
});

FormArray (Formularis Dinàmics)

export class DynamicFormComponent {
  formulario: FormGroup;
  
  constructor(private fb: FormBuilder) {
    this.formulario = this.fb.group({
      name: [''],
      items: this.fb.array([this.createItem()])
    });
  }
  
  createItem(): FormGroup {
    return this.fb.group({
      description: ['', Validators.required]
    });
  }
  
  get items(): FormArray {
    return this.formulario.get('items') as FormArray;
  }
  
  addItem(): void {
    this.items.push(this.createItem());
  }
  
  removeItem(index: number): void {
    this.items.removeAt(index);
  }
}

Pipes (Filtres)

Pipes Built-in

<!-- Text -->
<p>{{text | uppercase}}</p>
<p>{{text | lowercase}}</p>

<!-- Números -->
<p>{{3.14159 | number:'1.2-3'}}</p>
<p>{{100 | currency:'EUR':'symbol'}}</p>

<!-- Dates -->
<p>{{date | date:'dd/MM/yyyy'}}</p>

<!-- Array -->
<p>{{[1,2,3] | json}}</p>

Pipes Personalitzats

ng g pipe pipes/product-filter
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'productFilter',
  standalone: true
})
export class ProductFilterPipe implements PipeTransform {
  transform(
    products: IProduct[],
    filterBy: string
  ): IProduct[] {
    const filter = filterBy?.toLocaleLowerCase();
    return filter
      ? products.filter(p =>
          p.description.toLocaleLowerCase().includes(filter)
        )
      : products;
  }
}

// Ús
<tr *ngFor="let p of products | productFilter:search"></tr>

Autenticació i Seguretat

Flux d'Autenticació

  1. Formulari de login demana credencials
  2. Servici es connecta al servidor
  3. Servidor retorna token (JWT)
  4. Servici guarda el token en localStorage
  5. Interceptor afegeix token a cada petició HTTP
  6. Guard verifica si l'usuari està autenticat

Servici d'Autenticació

export class AuthService {
  private isLoggedIn = new BehaviorSubject<boolean>(false);
  
  login(email: string, password: string): Observable<any> {
    return this.http.post<any>('http://api.example.com/login', {
      email, password
    }).pipe(
      map(response => {
        if (response.token) {
          localStorage.setItem('token', response.token);
          this.isLoggedIn.next(true);
        }
        return response;
      })
    );
  }
  
  logout(): void {
    localStorage.removeItem('token');
    this.isLoggedIn.next(false);
  }
  
  isAuthenticated(): Observable<boolean> {
    return this.isLoggedIn.asObservable();
  }
}

HTTP Interceptor

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

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  intercept(
    req: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    const token = localStorage.getItem('token');
    
    if (token) {
      const authReq = req.clone({
        headers: req.headers.set('Authorization', `Bearer ${token}`)
      });
      return next.handle(authReq);
    }
    
    return next.handle(req);
  }
}

Arquitectura i Patterns

Selecció de Ferramentes

Necessitat Ferramenta
Parts diferenciades del HTML Components
Navegació dins l'app Rutes
Mostrar variables {{}} Interpolació
Canviar estils dinàmicament [style], ngStyle, ngClass
Formularis [(ngModel)] o Reactius
Dades globals Servicis, BehaviorSubject

Components Smart vs Dumb

Smart (Contenidor) Dumb (Presentació)
Responsabilitat Lògica de negoci Presentar dades
Accés a Servicis No
@Input/@Output No
Reutilitzable No

Desplegament i Producció

Build per a Producció

# Compilació estàndard (amb debug)
ng build

# Producció (minificat, optimitzat)
ng build --prod

# Noms aleatoris (evitar cache del navegador)
# Automàtic amb --prod

# Base path (per a URLs amb prefix)
ng build --base-href='/app/'

Variables d'Entorn

Desenvolupament: src/environments/environment.ts

export const environment = {
  production: false,
  apiUrl: 'http://localhost:3000/api'
};

Producció: src/environments/environment.prod.ts

export const environment = {
  production: true,
  apiUrl: 'https://api.example.com'
};

Desplegament en Apache

Fitxer .htaccess:

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteBase /
  RewriteRule ^index\.html$ - [L]
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteRule . /index.html [L]
</IfModule>

Biblioteques i Ecosistema

Bootstrap

# Opció 1: CDN (recomanat)
# Afegir a index.html
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>

# Opció 2: npm
npm install bootstrap

Angular Material

ng add @angular/material

# Documentació i ejemples
# https://material.angular.io/
  • Components pre-dissenyats (Buttons, Forms, etc.)
  • Temes Material Design
  • Animacions incloses
  • Accessibilitat integrada

Ng-bootstrap

ng add @ng-bootstrap/ng-bootstrap

# Funciona sense jQuery (Bootstrap 5+)
# Components natives d'Angular

Aplicacions Mòbils

Tipus d'Aplicacions

Tipus Tecnologia Avantatges
Web Responsive HTML/CSS/JS Senzill, SEO friendly
PWA Angular + Service Workers Offline, com app
Híbrida Ionic / Capacitor Accés hardware

Progressive Web App (PWA)

Característiques:

  • Progressiva: Funciona en navegadors antics
  • Responsiva: S'adapta a qualsevol dispositiu
  • Connectivitat independent: Funciona offline
  • Com una app: Experiència similar a aplicació nativa
  • Segura: Servida per HTTPS
  • Instal·lable: Sense App Store

App Híbrida amb Ionic

# Instal·lar Ionic
npm install -g ionic

# Crear app Angular + Ionic
ionic start myApp --type=angular

# Build per a Android/iOS
ionic build
ionic capacitor build android
ionic capacitor build ios

Recursos Addicionals

  • Documentació oficial: https://angular.dev
  • Internacionalització: https://angular.dev/guide/i18n
  • Testing: Jasmine, Jest
  • Performance: Lazy Loading, Code Splitting
  • Estado avanzado: NgRx, Akita
  • DevTools: https://angular.io/guide/devtools

Conclusió

Angular és un framework poderós per a construir aplicacions web escalables

Recomanacions Finals:

  1. Domina els conceptes bàsics
  2. Practica amb projectes petits
  3. Aprèn les millors pràctiques
  4. Estableix estàndards de codi
  5. Mantingut el codi simple i llegible

¡Gràcies!

Preguntes?

Angular Novembre 2025