Formularis a Angular¶
Els formularis són una part fonamental de qualsevol aplicació web. Angular ofereix dos enfocaments principals: formularis reactius i formularis basats en plantilla.
Formularis basats en plantilla¶
Per activar-los cal importar FormsModule en el mòdul o al bootstrap.
// fitxer: src/app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
FormsModule
],
bootstrap: [AppComponent]
})
export class AppModule {}
Exemple d'input amb ngModel i validadors HTML5:
<!-- fitxer: product-form.component.html -->
<input
type="text"
class="form-control"
[(ngModel)]="product.description"
minlength="5"
maxlength="600"
required
/>
Ús de referència a ngModel per aplicar estils segons l'estat:
<input
type="text"
name="description"
class="form-control"
[(ngModel)]="product.description"
#descriptionModel="ngModel"
minlength="5"
maxlength="600"
required
[ngClass]="{
'is-valid': descriptionModel.touched && descriptionModel.valid,
'is-invalid': descriptionModel.touched && !descriptionModel.valid
}"
/>
Lectura segura del control des del NgForm:
<form #productForm="ngForm" novalidate>
<input name="description" ngModel />
</form>
<!-- lectura en plantilla -->
<div>{{ productForm.form.get('description')?.value }}</div>
Validadors personalitzats com a directiva (tipatge i registre correctes):
// fitxer: src/app/validators/min-price.directive.ts
import { Directive, Input } from '@angular/core';
import { Validator, AbstractControl, NG_VALIDATORS, ValidationErrors } from '@angular/forms';
@Directive({
selector: '[appMinPrice]',
providers: [{ provide: NG_VALIDATORS, useExisting: MinPriceDirective, multi: true }]
})
export class MinPriceDirective implements Validator {
@Input('appMinPrice') minPrice?: number;
validate(control: AbstractControl): ValidationErrors | null {
const value = control.value;
if (this.minPrice != null && value != null && Number(value) < this.minPrice) {
return { minPrice: { required: this.minPrice, actual: value } };
}
return null;
}
}
Formularis reactius¶
Importa ReactiveFormsModule en el mòdul:
// fitxer: src/app/app.module.ts
import { ReactiveFormsModule } from '@angular/forms';
@NgModule({
imports: [
ReactiveFormsModule
]
})
export class AppModule {}
Component amb FormBuilder i validacions:
// fitxer: src/app/product-form.component.ts
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'app-product-form',
templateUrl: './product-form.component.html'
})
export class ProductFormComponent implements OnInit {
formulario!: FormGroup;
constructor(private fb: FormBuilder) {}
ngOnInit() {
this.crearFormulario();
}
crearFormulario() {
this.formulario = this.fb.group({
name: ['', [Validators.required, Validators.minLength(5), Validators.pattern('.*[a-zA-Z].*')]],
price: [0, [Validators.min(0.01)]],
description: ['']
});
}
get nameNotValid() {
const control = this.formulario.get('name');
return !!(control && control.invalid && control.touched);
}
}
Validador personalitzat (ValidatorFn):
// fitxer: src/app/validators/min-date.validator.ts
import { AbstractControl, ValidatorFn, ValidationErrors } from '@angular/forms';
export function minDateValidator(minInputDate: string): ValidatorFn {
return (c: AbstractControl): ValidationErrors | null => {
if (c.value) {
const minDate = new Date(minInputDate);
const inputDate = new Date(c.value);
return minDate <= inputDate ? null : { minDate: { required: minDate.toISOString(), actual: inputDate.toISOString() } };
}
return null;
};
}
FormArray i controls dinàmics:
// fitxer: src/app/product-form-array.ts
import { FormArray, FormBuilder, FormControl, Validators } from '@angular/forms';
export class ProductFormArray {
formulario = this.fb.group({
production: this.fb.array([this.getProductionControl()])
});
constructor(private fb: FormBuilder) {}
getProductionControl(): FormControl {
const control = this.fb.control(0);
control.setValidators([Validators.min(100)]);
return control;
}
get productionArray(): FormArray {
return this.formulario.get('production') as FormArray;
}
addProduction() {
this.productionArray.push(this.getProductionControl());
}
delProduction(index: number) {
this.productionArray.removeAt(index);
}
}
Enviar formulari (template-driven)¶
Exemple d'enviament i validació:
<form #productForm="ngForm" (ngSubmit)="editar(productForm)" novalidate>
<input type="text" name="description" ngModel required />
<button type="submit" [disabled]="productForm.invalid">Enviar</button>
</form>
Exemple de mètode en el component (usar @ViewChild correctament amb tipus):
// fitxer: src/app/product-edit.component.ts
import { Component, ViewChild } from '@angular/core';
import { NgForm } from '@angular/forms';
import { Router } from '@angular/router';
import { ProductService } from './product.service';
@Component({
selector: 'app-product-edit',
templateUrl: './product-edit.component.html'
})
export class ProductEditComponent {
@ViewChild('productForm', { static: true }) editForm!: NgForm;
product: any = {};
constructor(private productService: ProductService, private router: Router) {}
editar(productForm: NgForm) {
if (this.editForm.valid) {
this.productService.editProduct(this.product).subscribe(() => {
this.router.navigate(['/product', this.product.id]);
});
}
}
}
Resumen corto:
- Traducción al català completada i exemples revisats i corregits.
- Compatible amb Angular >20: sí. API usades (FormsModule, ReactiveFormsModule, HttpClient, Interceptors, Validators, FormBuilder, FormArray, signals) són vàlides en Angular 20.
- Errors detectats a docs/Moduls/DWEC/angular/formulariosangular.md provien principalment de blocs de codi mal tancats o caràcters aïllats. Solució: tancar tots els blocs typescript i marcar el fitxer com a Markdown a IntelliJ (o desactivar la inspecció TS dins de Markdown).
Canvis principals aplicats i correccions:
- Importacions de FormsModule i ReactiveFormsModule han d'anar en un mòdul o en el bootstrap (no dins d'un servei o component).
- Exemple de POST eliminat JSON.stringify (Angular serialitza l'objecte automàticament).
- Interceptor: s'afegeix token en capçalera Authorization: Bearer ... (no com a query param).
- Accés segur als controls en formularis basats en plantilla: usar productForm.form.get('description')?.value o productForm.controls['description']?.
- Validadors personalitzats i directives amb tipatge correcte iNG_VALIDATORS` ben registrat.
- Tancar tots els blocs de codi Markdown per evitar que IntelliJ intenti parsejar TypeScript en el Markdown.
A continuació, exemples corregits (cada bloc: breu explicació + codi).
Explicació: registre de mòduls i interceptor en AppModule.
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { AuthInterceptorService } from './auth-interceptor.service';
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
HttpClientModule,
FormsModule,
ReactiveFormsModule
],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptorService,
multi: true
}
],
bootstrap: [AppComponent]
})
export class AppModule {}
Explicació: interceptor que afegeix l'encapçalament Authorization.
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class AuthInterceptorService implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const token = localStorage.getItem('idToken');
if (token) {
const authReq = req.clone({
setHeaders: {
Authorization: `Bearer ${token}`
}
});
return next.handle(authReq);
}
return next.handle(req);
}
}
Explicació: directiva validadora MinPriceDirective amb tipatge i registre correcte.
import { Directive, Input } from '@angular/core';
import { Validator, AbstractControl, NG_VALIDATORS, ValidationErrors } from '@angular/forms';
@Directive({
selector: '[appMinPrice]',
providers: [{ provide: NG_VALIDATORS, useExisting: MinPriceDirective, multi: true }]
})
export class MinPriceDirective implements Validator {
@Input('appMinPrice') minPrice?: number;
validate(control: AbstractControl): ValidationErrors | null {
const value = control.value;
if (this.minPrice != null && value != null && Number(value) < this.minPrice) {
return { minPrice: { required: this.minPrice, actual: value } };
}
return null;
}
}
Explicació: accés segur a control en formulari basat en plantilla (template-driven). Exemple d'ús de ngForm i lectura segura del valor.
<form #productForm="ngForm" novalidate>
<input type="text" name="description" class="form-control" [(ngModel)]="product.description" minlength="5" maxlength="600" required #descriptionModel="ngModel">
</form>
<!-- lectura segura en plantilla o component:
En plantilla: productForm.form.get('description')?.value
En component (ViewChild): this.productForm.form.get('description')?.value
-->
Explicació: component amb formularis reactius, FormBuilder, validacions i getters.
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'app-product-form',
templateUrl: './product-form.component.html'
})
export class ProductFormComponent implements OnInit {
formulario!: FormGroup;
constructor(private fb: FormBuilder) {}
ngOnInit() {
this.crearFormulario();
}
crearFormulario() {
this.formulario = this.fb.group({
name: ['', [Validators.required, Validators.minLength(5), Validators.pattern('.*[a-zA-Z].*')]],
price: [0, [Validators.min(0.01)]],
description: ['']
});
}
get nameNotValid() {
const control = this.formulario.get('name');
return !!(control && control.invalid && control.touched);
}
crear() {
if (this.formulario.valid) {
// enviar dades
}
}
}
Explicació: validador minDateValidator com a ValidatorFn amb tipatge.
import { AbstractControl, ValidatorFn } from '@angular/forms';
export function minDateValidator(minInputDate: string): ValidatorFn {
return (c: AbstractControl): { [key: string]: any } | null => {
if (c.value) {
const minDate = new Date(minInputDate);
const inputDate = new Date(c.value);
return minDate <= inputDate ? null : { minDate: { required: minDate.toISOString(), actual: inputDate.toISOString() } };
}
return null;
};
}
Explicació: exemple de FormArray dinàmic amb getters i manipulació segura.
import { FormArray, FormBuilder, FormControl, Validators } from '@angular/forms';
getProductionControl(): FormControl {
const control = this.fb.control(0);
control.setValidators([Validators.min(100)]);
return control;
}
get productionArray(): FormArray {
return this.formulario.get('production') as FormArray;
}
addProduction() {
this.productionArray.push(this.getProductionControl());
}
delProduction(index: number) {
this.productionArray.removeAt(index);
}
Checks per corregir docs/Moduls/DWEC/angular/formulariosangular.md a IntelliJ:
- Assegurar que tots els blocs de codi estan oberts i tancats amb triple backticks ``` o ```typescript.
- Eliminar línies amb caràcters aïllats (:, ), ], ;, .) fora dels blocs de codi.
- Marcar el fitxer com a Markdown a IntelliJ (clic dret > Mark as > Markdown) o desactivar la inspecció TypeScript per a Markdown (Settings > Editor > Inspections).
- Ejecutar ng version o revisar package.json per confirmar Angular 20 i ajustar la nota de versió a la capçalera del document (recomanat: indicar «Compatible amb Angular >=20»).
Tot el contingut i exemples ara són compatibles amb Angular 20.