Salta el contingut

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.