Novembre 2025
Un framework JavaScript per a aplicacions escalables
# 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
Tipat Estàtic per a JavaScript
// 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
}
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;
❌ 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);
}
};
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
Components, Servicis, Rutes...
# 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
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>
<!-- 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>
<!-- 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>
@if (items.length > 0) {
@for (item of items; track $index) {
<p>{{$index}}: {{item}}</p>
}
} @else {
<p>Cap element</p>
}
// 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>
// 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[] = [...];
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
];
<!-- 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]);
}
}
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'];
});
}
}
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);
}
}
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
);
}
}
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}`);
}
}
| Característica | Observable | Signal | Promesa |
|---|---|---|---|
| Múltiples valors | ✓ | ✓ | ✗ |
| Cancelable | ✓ | ✗ | ✗ |
| Lazy | ✓ | ✗ | ✗ |
| Operadors | ✓ | ✗ | ✗ |
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)
);
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>
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
}
Plantilla vs Reactius
| Aspecte | Plantilla | Reactiu |
|---|---|---|
| Validació | HTML5 + Angular | TypeScript |
| Complexitat | Simple | Avançada |
| Control | Template | Component |
| Recomanat | Login simple | Formularis complexos |
<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>
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);
}
}
}
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)]]
});
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);
}
}
<!-- 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>
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>
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();
}
}
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);
}
}
| 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 |
| Smart (Contenidor) | Dumb (Presentació) | |
|---|---|---|
| Responsabilitat | Lògica de negoci | Presentar dades |
| Accés a Servicis | Sí | No |
| @Input/@Output | No | Sí |
| Reutilitzable | No | Sí |
# 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/'
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'
};
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>
# 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
ng add @angular/material
# Documentació i ejemples
# https://material.angular.io/
ng add @ng-bootstrap/ng-bootstrap
# Funciona sense jQuery (Bootstrap 5+)
# Components natives d'Angular
| 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 |
# 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
Angular és un framework poderós per a construir aplicacions web escalables
Preguntes?
Angular Novembre 2025