Explicación de Inyección de dependencias, formularios y validaciones

Sitio: Campus virtual DAW - damiansu
Curso: Desarrollo Web en Entorno Cliente
Libro: Explicación de Inyección de dependencias, formularios y validaciones
Imprimido por: Invitado
Día: jueves, 22 de enero de 2026, 05:06

1. Descripción

Lo que vamos a realizar a continuación es:

  1. Proyecto nuevo (ng new tema7-angular --standalone).

  2. Servicio ClientesService inyectado en un componente (ListaColoresComponent) usando inject() y @for.

  3. Formulario Template-Driven con FormsModule, ngForm, ngModel y @if para errores.

  4. Formulario Reactivo básico con ReactiveFormsModule, FormGroup, FormControl.

  5. Formulario Reactivo con validaciones:

    • Built-in (required, email, pattern).

    • Personalizado (rangoEdadValidator(18, 65)).

    • Errores visibles en plantilla.

    • Botón deshabilitado si el formulario no es válido.

    • Uso de estados (valid, invalid, touched, etc.) como en el tema.

2. Crear el proyecto Angular

Creamos el proyecto

npm install -g @angular/cli
ng new tema7-angular --standalone
cd tema7-angular
ng serve
src/main.ts  (te lo genera Angular)
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';

bootstrapApplication(AppComponent)
  .catch(err => console.error(err));

Lo ponemos un poco básico:

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

@Component({
  selector: 'app-root',
  standalone: true,
  template: `
    <h1>Tema 7 – Angular</h1>
    <p>Vamos a ir añadiendo componentes aquí.</p>
  `
})
export class AppComponent {}

3. Inyección de dependencias (DI)

 
Inyección de dependencias (DI) – Servicio + Componente

3.1. Crear el servicio

Creamos un servicio que tenga un array:

ng generate service services/clientes --skip-tests

Ponemos un array de colores

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

@Injectable({ providedIn: 'root' })
export class ClientesService {
  private colores: string[] = ['blue', 'red', 'violet', 'yellow', 'green'];

  getColores(): string[] {
    return this.colores;
  }
}

3.2. Crear el componente que inyecta el servicio

Creamos el componente:

ng generate component componentes/lista-colores --standalone --skip-tests

Aquí ya estamos aplicando inyección de dependencias: el componente no crea los datos, los pide al servicio

import { Component, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ClientesService } from '../../services/clientes.service';

@Component({
  selector: 'app-lista-colores',
  standalone: true,
  imports: [CommonModule],
  template: `
    <h2>Inyección de dependencias (colores)</h2>

    @for (color of colores; track color) {
      <p>{{ color }}</p>
    }
  `
})
export class ListaColoresComponent {
  // Inyección moderna
  private clientesService = inject(ClientesService);

  colores = this.clientesService.getColores();
}

3.3. Mostrarlo en AppComponent

Vamos a nuestro src/app/app.component.ts

import { Component } from '@angular/core';
import { ListaColoresComponent } from './componentes/lista-colores/lista-colores.component';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [ListaColoresComponent],
  template: `
    <h1>Tema 7 – Angular</h1>

    <app-lista-colores></app-lista-colores>
  `
})
export class AppComponent {}

4. Formularios de tipo Template

En Angular moderno, en vez de meter FormsModule en un módulo, lo importamos en el componente standalone.

4.1. Crear el componente de formulario template

Creamos el compoenente

ng generate component componentes/form-template --standalone --skip-tests

Y retocamos src/app/componentes/form-template/form-template.component.ts

import { Component } from '@angular/core';
import { FormsModule, NgForm } from '@angular/forms';

@Component({
  selector: 'app-form-template',
  standalone: true,
  imports: [FormsModule],
  template: `
    <h2>Formulario Template-driven</h2>

    <form #personaForm="ngForm" (ngSubmit)="onSubmit(personaForm)">
      <div>
        <label>Nombre</label>
        <input
          type="text"
          name="nombre"
          ngModel
          required
        >
        @if (personaForm.submitted && personaForm.controls['nombre']?.invalid) {
          <small style="color:red">El nombre es obligatorio</small>
        }
      </div>

      <div>
        <label>Email</label>
        <input
          type="email"
          name="email"
          ngModel
        >
      </div>

      <button type="submit">Enviar</button>
    </form>

    <h3>Datos del formulario</h3>
    <pre>{{ personaForm.value | json }}</pre>
  `
})
export class FormTemplateComponent {

  onSubmit(form: NgForm) {
    console.log('Template form enviado:', form.value);
  }
}

4.2. Añadirlo a AppComponent

Lo añadimos

import { ListaColoresComponent } from './componentes/lista-colores/lista-colores.component';
import { FormTemplateComponent } from './componentes/form-template/form-template.component';

@Component({
  ...
  imports: [ListaColoresComponent, FormTemplateComponent],
  template: `
    <h1>Tema 7 – Angular moderno</h1>

    <app-lista-colores></app-lista-colores>
    <hr>

    <app-form-template></app-form-template>
  `
})
export class AppComponent {}

5. Formularios de tipo Model (Reactive Forms)

Aquí pasamos a formularios reactivos, donde la definición está en la clase (TS).

Creamos el componente

ng generate component componentes/form-reactivo --standalone --skip-tests

Retocamos src/app/componentes/form-reactivo/form-reactivo.component.ts

import { Component } from '@angular/core';
import { ReactiveFormsModule, FormGroup, FormControl } from '@angular/forms';

@Component({
  selector: 'app-form-reactivo',
  standalone: true,
  imports: [ReactiveFormsModule],
  template: `
    <h2>Formulario Reactivo (Model-driven)</h2>

    <form [formGroup]="form" (ngSubmit)="onSubmit()">
      <div>
        <label>Nombre</label>
        <input type="text" formControlName="nombre">
      </div>

      <div>
        <label>Apellidos</label>
        <input type="text" formControlName="apellidos">
      </div>

      <div>
        <label>Edad</label>
        <input type="number" formControlName="edad">
      </div>

      <div>
        <label>Dirección</label>
        <input type="text" formControlName="direccion">
      </div>

      <button type="submit">Enviar</button>
    </form>

    <h3>Datos del formulario</h3>
    <pre>{{ form.value | json }}</pre>
  `
})
export class FormReactivoComponent {
  form = new FormGroup({
    nombre: new FormControl(''),
    apellidos: new FormControl(''),
    edad: new FormControl(''),
    direccion: new FormControl('')
  });

  onSubmit() {
    console.log('Formulario reactivo:', this.form.value);
  }
}

5.1. Añadirlo a AppComponent

Lo añadimos

import { FormReactivoComponent } from './componentes/form-reactivo/form-reactivo.component';

@Component({
  ...
  imports: [
    ListaColoresComponent,
    FormTemplateComponent,
    FormReactivoComponent
  ],
  template: `
    <h1>Tema 7 – Angular moderno</h1>

    <app-lista-colores></app-lista-colores>
    <hr>

    <app-form-template></app-form-template>
    <hr>

    <app-form-reactivo></app-form-reactivo>
  `
})
export class AppComponent {}

6. Validaciones en Angular (Built-in y personalizadas)

Ahora ampliamos el formulario reactivo con:

  • Validadores built-in (required, email, pattern, etc.)

  • Validador personalizado para la edad

  • Mostrar errores con @if

  • Deshabilitar el botón si el formulario es inválido

7. Crear componente de formulario validado

Creamos el componente

ng generate component componentes/form-validado --standalone --skip-tests

Retocamos src/app/componentes/form-validado/form-validado.component.ts.

Más un validador custom que devuelve null si todo va bien o un objeto con el mensaje de error si no.
Angular añade clases CSS como ng-invalid, ng-valid, ng-touched, etc

ng-invalid -> campo inválido

ng-valid -> campo válido

ng-touched -> campo tocado

ng-pristine -> no modificado

import { Component } from '@angular/core';
import {
  ReactiveFormsModule,
  FormGroup,
  FormControl,
  Validators,
  AbstractControl,
  ValidationErrors
} from '@angular/forms';

@Component({
  selector: 'app-form-validado',
  standalone: true,
  imports: [ReactiveFormsModule],
  template: `
    <h2>Formulario Reactivo con Validaciones</h2>

    <form [formGroup]="form" (ngSubmit)="onSubmit()">

      <!-- NOMBRE -->
      <div>
        <label>Nombre</label>
        <input type="text" formControlName="nombre">

        @if (nombre.invalid && nombre.touched) {
          @if (nombre.errors?.['required']) {
            <small style="color:red">El nombre es obligatorio.</small>
          }
          @if (nombre.errors?.['pattern']) {
            <small style="color:red">El nombre contiene caracteres no válidos.</small>
          }
        }
      </div>

      <!-- EMAIL -->
      <div>
        <label>Email</label>
        <input type="email" formControlName="email">

        @if (email.invalid && email.touched) {
          @if (email.errors?.['required']) {
            <small style="color:red">El email es obligatorio.</small>
          }
          @if (email.errors?.['email']) {
            <small style="color:red">El email no tiene formato válido.</small>
          }
        }
      </div>

      <!-- EDAD (validador personalizado) -->
      <div>
        <label>Edad</label>
        <input type="number" formControlName="edad">

        @if (edad.invalid && edad.touched) {
          @if (edad.errors?.['required']) {
            <small style="color:red">La edad es obligatoria.</small>
          }
          @if (edad.errors?.['rangoEdad']) {
            <small style="color:red">
              {{ edad.errors['rangoEdad'].message }}
            </small>
          }
        }
      </div>

      <button type="submit" [disabled]="form.invalid">
        Enviar
      </button>
    </form>

    <hr>

    <h3>Estado del control "nombre"</h3>
    <pre>
dirty: {{ nombre.dirty }}
pristine: {{ nombre.pristine }}
touched: {{ nombre.touched }}
untouched: {{ nombre.untouched }}
valid: {{ nombre.valid }}
invalid: {{ nombre.invalid }}
    </pre>
  `
})
export class FormValidadoComponent {
  form = new FormGroup({
    nombre: new FormControl('', [
      Validators.required,
      Validators.pattern('[\\w\\-\\s\\/]+')
    ]),
    email: new FormControl('', [
      Validators.required,
      Validators.email
    ]),
    edad: new FormControl('', [
      Validators.required,
      this.rangoEdadValidator(18, 65)
    ])
  });

  get nombre() { return this.form.controls.nombre; }
  get email() { return this.form.controls.email; }
  get edad() { return this.form.controls.edad; }

  // Validador personalizado (igual idea que en el PDF)
  rangoEdadValidator(min: number, max: number) {
    return (control: AbstractControl): ValidationErrors | null => {
      const raw = control.value;
      if (raw === null || raw === '') return null; // lo gestiona required
      const edad = Number(raw);

      if (Number.isNaN(edad)) {
        return { rangoEdad: { message: 'La edad debe ser un número' } };
      }

      if (edad >= min && edad <= max) {
        return null;
      } else {
        return { rangoEdad: { message: `La edad debe estar entre ${min} y ${max}` } };
      }
    };
  }

  onSubmit() {
    if (this.form.valid) {
      console.log('Formulario válido:', this.form.value);
    } else {
      console.log('Formulario inválido');
    }
  }
}

7.1. (Opcional) CSS para ng-valid / ng-invalid

Editamos (si no está lo creamos) src/app/componentes/form-validado/form-validado.component.css, 

Angular añade clases CSS como ng-invalid, ng-valid, ng-touched, etc

input.ng-invalid.ng-touched {
  border: 1px solid red;
}

input.ng-valid.ng-touched {
  border: 1px solid green;
}

7.2. Añadirlo a AppComponent

Lo añadimos

import { FormValidadoComponent } from './componentes/form-validado/form-validado.component';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [
    ListaColoresComponent,
    FormTemplateComponent,
    FormReactivoComponent,
    FormValidadoComponent
  ],
  template: `
    <h1>Tema 7 – Angular moderno</h1>

    <h2>1. Inyección de dependencias</h2>
    <app-lista-colores></app-lista-colores>
    <hr>

    <h2>2. Formulario Template-driven</h2>
    <app-form-template></app-form-template>
    <hr>

    <h2>3. Formulario Reactivo básico</h2>
    <app-form-reactivo></app-form-reactivo>
    <hr>

    <h2>4. Formulario Reactivo con validaciones</h2>
    <app-form-validado></app-form-validado>
  `
})
export class AppComponent {}