Tutorial Angular: Crea una Aplicación CRUD con Angular y TypeScript Paso a Paso
¡Bienvenido a este tutorial Angular! Aquí aprenderás a construir una aplicación CRUD (Crear, Leer, Actualizar y Eliminar) utilizando Angular y TypeScript, ideal para desarrolladores principiantes e intermedios que deseen adentrarse en el desarrollo frontend con tecnologías modernas. Te guiaré paso a paso, cubriendo desde la configuración del entorno con Angular CLI, la creación de componentes y servicios, hasta la integración con una API REST simulada, manejo del enrutamiento y validación de formularios.
Este tutorial Angular está diseñado para ser fácil de seguir, educativo y práctico, fomentando buenas prácticas en Angular mientras construyes una aplicación funcional. ¡Empecemos!
Índice
- Configuración del entorno con Angular CLI
- Creación del proyecto Angular
- Estructura del proyecto y buenas prácticas
- Creación de componentes
- Creación de servicios para manejar datos
- Simulación de una API REST
- Manejo del enrutamiento
- Validación de formularios
- Conclusión y próximos pasos
Configuración del entorno con Angular CLI
Antes de comenzar, necesitas preparar tu entorno de desarrollo.
Paso 1: Instalar Node.js y npm
Angular requiere Node.js y npm (Node Package Manager). Descárgalos e instálalos desde:
- https://nodejs.org/
Para verificar que la instalación fue exitosa, abre una terminal y ejecuta:
1 2 3 |
node -v npm -v |
Paso 2: Instalar Angular CLI
Angular CLI facilita la creación y gestión de proyectos Angular.
Ejecuta el siguiente comando para instalar Angular CLI globalmente:
1 2 |
npm install -g @angular/cli |
Verifica la instalación con:
1 2 |
ng version |
Así, tendrás configurado el entorno base para desarrollar tu aplicación Angular usando TypeScript.
Creación del proyecto Angular
Vamos a crear un nuevo proyecto que usaremos para la aplicación CRUD.
Ejecuta el siguiente comando en la terminal:
1 2 |
ng new angular-crud-app |
Durante la creación, Angular CLI te preguntará:
- Agregar enrutador Angular?: responde
Sí
(yes). - Estilo para las hojas de estilo: selecciona
CSS
oSCSS
según prefieras.
Una vez creado el proyecto, navega al directorio:
1 2 |
cd angular-crud-app |
Para iniciar la aplicación y verificar que todo funcione, ejecuta:
1 2 |
ng serve |
Abre tu navegador en http://localhost:4200/
y verás la página inicial de Angular.
Estructura del proyecto y buenas prácticas
Angular crea una estructura base que facilita la escalabilidad.
Algunos directorios importantes:
src/app/
: Aquí estarán los componentes, servicios y módulos.src/assets/
: Archivos estáticos como imágenes y estilos.
Buenas prácticas iniciales:
- Usa módulos para organizar funcionalidades.
- Mantén los servicios separados para la lógica de datos.
- Usa rutas para navegar entre componentes.
- Define interfaces para modelos de datos.
Creación de componentes
Los componentes son las piezas visuales de la app. Crearemos tres:
- Listado de ítems (leer datos).
- Formulario para agregar y editar.
- Componente raíz (ya creado por Angular).
Paso 1: Crear componente para la lista
1 2 |
ng generate component components/item-list |
Paso 2: Crear componente para el formulario
1 2 |
ng generate component components/item-form |
Observa que organizamos los componentes en la carpeta components
, ayuda a la claridad.
Creación de servicios para manejar datos
Los servicios Angular manejan la lógica de negocio y la comunicación con APIs.
Paso 1: Crear un servicio de ítems
1 2 |
ng generate service services/item |
Paso 2: Definir la interfaz del modelo Item
Crea un archivo src/app/models/item.ts
para definir qué es un Item
:
1 2 3 4 5 6 7 |
// src/app/models/item.ts export interface Item { id: number; name: string; description: string; } |
Paso 3: Implementar el servicio con datos simulados
Por ahora, simularemos los datos sin backend real (más adelante integraremos una API REST simulada).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
// src/app/services/item.service.ts import { Injectable } from '@angular/core'; import { Item } from '../models/item'; import { Observable, of } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class ItemService { // Datos simulados private items: Item[] = [ { id: 1, name: 'Item 1', description: 'Descripción del Item 1' }, { id: 2, name: 'Item 2', description: 'Descripción del Item 2' }, ]; constructor() { } // Obtener todos los ítems getItems(): Observable<Item[]> { return of(this.items); } // Crear un nuevo ítem addItem(item: Item): Observable<Item> { item.id = this.generateId(); this.items.push(item); return of(item); } // Actualizar ítem existente updateItem(updatedItem: Item): Observable<Item | undefined> { const index = this.items.findIndex(item => item.id === updatedItem.id); if (index !== -1) { this.items[index] = updatedItem; return of(updatedItem); } else { return of(undefined); } } // Eliminar ítem por id deleteItem(id: number): Observable<boolean> { const index = this.items.findIndex(item => item.id === id); if (index !== -1) { this.items.splice(index, 1); return of(true); } return of(false); } // Obtener ítem por id getItemById(id: number): Observable<Item | undefined> { const item = this.items.find(item => item.id === id); return of(item); } // Generar un nuevo ID único private generateId(): number { return this.items.length > 0 ? Math.max(...this.items.map(i => i.id)) + 1 : 1; } } |
Este servicio usa Observable
y of
de RxJS para simular llamadas asíncronas, algo fundamental en programación Angular.
Simulación de una API REST
Para simular una API REST sin crear backend propio, usaremos Angular In-memory Web API.
Paso 1: Instalar la dependencia
1 2 |
npm install angular-in-memory-web-api --save |
Paso 2: Crear la clase simuladora de API
Crea src/app/services/in-memory-data.service.ts
:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import { InMemoryDbService } from 'angular-in-memory-web-api'; import { Item } from '../models/item'; export class InMemoryDataService implements InMemoryDbService { createDb() { const items: Item[] = [ { id: 1, name: 'Item 1', description: 'Descripción del Item 1' }, { id: 2, name: 'Item 2', description: 'Descripción del Item 2' }, ]; return { items }; } } |
Paso 3: Importar el InMemoryWebApiModule en el módulo principal
Modifica src/app/app.module.ts
para agregar el módulo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { HttpClientModule } from '@angular/common/http'; import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api'; import { AppComponent } from './app.component'; import { InMemoryDataService } from './services/in-memory-data.service'; @NgModule({ declarations: [ AppComponent // agrega aquí otros componentes... ], imports: [ BrowserModule, HttpClientModule, HttpClientInMemoryWebApiModule.forRoot(InMemoryDataService, { dataEncapsulation: false }), ], providers: [], bootstrap: [AppComponent] }) export class AppModule { } |
Paso 4: Adaptar el servicio para usar HttpClient
Actualiza el servicio ItemService
para hacer llamadas HTTP reales a la API simulada:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
import { Injectable } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Observable, of } from 'rxjs'; import { catchError } from 'rxjs/operators'; import { Item } from '../models/item'; @Injectable({ providedIn: 'root' }) export class ItemService { private itemsUrl = 'api/items'; // URL a la API simulada httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json' }) }; constructor(private http: HttpClient) { } getItems(): Observable<Item[]> { return this.http.get<Item[]>(this.itemsUrl) .pipe(catchError(this.handleError<Item[]>('getItems', []))); } getItemById(id: number): Observable<Item | undefined> { const url = `${this.itemsUrl}/${id}`; return this.http.get<Item>(url).pipe(catchError(this.handleError<Item>(`getItemById id=${id}`))); } addItem(item: Item): Observable<Item> { return this.http.post<Item>(this.itemsUrl, item, this.httpOptions) .pipe(catchError(this.handleError<Item>('addItem'))); } updateItem(item: Item): Observable<any> { return this.http.put(this.itemsUrl, item, this.httpOptions) .pipe(catchError(this.handleError<any>('updateItem'))); } deleteItem(id: number): Observable<Item> { const url = `${this.itemsUrl}/${id}`; return this.http.delete<Item>(url, this.httpOptions) .pipe(catchError(this.handleError<Item>('deleteItem'))); } private handleError<T>(operation = 'operation', result?: T) { return (error: any): Observable<T> => { console.error(error); return of(result as T); }; } } |
Manejo del enrutamiento
El enrutamiento permite navegar entre diferentes vistas en tu aplicación.
Paso 1: Configurar las rutas
Agrega las rutas al archivo src/app/app-routing.module.ts
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { ItemListComponent } from './components/item-list/item-list.component'; import { ItemFormComponent } from './components/item-form/item-form.component'; const routes: Routes = [ { path: '', redirectTo: '/items', pathMatch: 'full' }, { path: 'items', component: ItemListComponent }, { path: 'items/add', component: ItemFormComponent }, { path: 'items/edit/:id', component: ItemFormComponent }, { path: '**', redirectTo: '/items' } ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { } |
Paso 2: Importar el módulo de rutas
En app.module.ts
importa AppRoutingModule
:
1 2 3 4 5 6 7 8 9 10 |
import { AppRoutingModule } from './app-routing.module'; // Agregar a imports: imports: [ BrowserModule, HttpClientModule, HttpClientInMemoryWebApiModule.forRoot(InMemoryDataService, { dataEncapsulation: false }), AppRoutingModule ], |
Paso 3: Añadir links en el template principal
En app.component.html
crea un menú simple:
1 2 3 4 5 6 7 |
<nav> <a routerLink="/items" routerLinkActive="active">Lista de Items</a> | <a routerLink="/items/add" routerLinkActive="active">Agregar Item</a> </nav> <hr /> <router-outlet></router-outlet> |
El <router-outlet>
renderiza el componente activo según la ruta.
Validación de formularios
La validación es esencial para una mejor experiencia de usuario y para evitar datos erróneos.
Paso 1: Importar ReactiveFormsModule
En app.module.ts
agrega:
1 2 3 4 5 6 7 8 |
import { ReactiveFormsModule } from '@angular/forms'; // En imports imports: [ // otros módulos ... ReactiveFormsModule ], |
Paso 2: Crear formulario reactivo en ItemFormComponent
En item-form.component.ts
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import { ItemService } from '../../services/item.service'; import { Item } from '../../models/item'; @Component({ selector: 'app-item-form', templateUrl: './item-form.component.html', styleUrls: ['./item-form.component.css'] }) export class ItemFormComponent implements OnInit { itemForm!: FormGroup; isEditMode = false; itemId!: number; constructor( private fb: FormBuilder, private itemService: ItemService, private router: Router, private route: ActivatedRoute ) { } ngOnInit(): void { this.itemForm = this.fb.group({ name: ['', [Validators.required, Validators.minLength(3)]], description: ['', [Validators.required, Validators.minLength(5)]] }); this.route.paramMap.subscribe(params => { const idParam = params.get('id'); if (idParam) { this.isEditMode = true; this.itemId = +idParam; this.loadItem(this.itemId); } }); } loadItem(id: number) { this.itemService.getItemById(id).subscribe(item => { if (item) { this.itemForm.patchValue({ name: item.name, description: item.description }); } else { alert('Item no encontrado'); this.router.navigate(['/items']); } }); } onSubmit() { if (this.itemForm.invalid) { return; } const formValue = this.itemForm.value; if (this.isEditMode) { const updatedItem: Item = { id: this.itemId, ...formValue }; this.itemService.updateItem(updatedItem).subscribe(() => { alert('Item actualizado exitosamente'); this.router.navigate(['/items']); }); } else { this.itemService.addItem(formValue).subscribe(() => { alert('Item creado exitosamente'); this.router.navigate(['/items']); }); } } get name() { return this.itemForm.get('name'); } get description() { return this.itemForm.get('description'); } } |
Paso 3: Crear template con validación
En item-form.component.html
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
<h2>{{ isEditMode ? 'Editar Item' : 'Agregar Item' }}</h2> <form [formGroup]="itemForm" (ngSubmit)="onSubmit()" novalidate> <div> <label for="name">Nombre:</label> <input id="name" formControlName="name" /> <div *ngIf="name?.invalid && (name?.dirty || name?.touched)" class="error"> <small *ngIf="name?.errors?.['required']">El nombre es obligatorio.</small> <small *ngIf="name?.errors?.['minlength']">Mínimo 3 caracteres.</small> </div> </div> <div> <label for="description">Descripción:</label> <textarea id="description" formControlName="description"></textarea> <div *ngIf="description?.invalid && (description?.dirty || description?.touched)" class="error"> <small *ngIf="description?.errors?.['required']">La descripción es obligatoria.</small> <small *ngIf="description?.errors?.['minlength']">Mínimo 5 caracteres.</small> </div> </div> <button type="submit" [disabled]="itemForm.invalid"> {{ isEditMode ? 'Actualizar' : 'Guardar' }} </button> <button type="button" (click)="router.navigate(['/items'])">Cancelar</button> </form> <style> .error { color: red; } </style> |
Esto garantiza que el usuario solo pueda enviar datos válidos.
Component Listado de Items con funcionalidad CRUD
Finalmente, implementamos el componente para listar, eliminar y navegar para editar.
item-list.component.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { Item } from '../../models/item'; import { ItemService } from '../../services/item.service'; @Component({ selector: 'app-item-list', templateUrl: './item-list.component.html', styleUrls: ['./item-list.component.css'] }) export class ItemListComponent implements OnInit { items: Item[] = []; constructor( private itemService: ItemService, private router: Router ) { } ngOnInit(): void { this.loadItems(); } loadItems(): void { this.itemService.getItems().subscribe(items => this.items = items); } editItem(id: number): void { this.router.navigate(['/items/edit', id]); } deleteItem(id: number): void { if (confirm('¿Estás seguro de eliminar este item?')) { this.itemService.deleteItem(id).subscribe(success => { if (success) { alert('Item eliminado'); this.loadItems(); } else { alert('Error al eliminar'); } }); } } } |
item-list.component.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<h2>Lista de Items</h2> <table border="1" cellpadding="5"> <thead> <tr> <th>Nombre</th> <th>Descripción</th> <th>Acciones</th> </tr> </thead> <tbody> <tr *ngFor="let item of items"> <td>{{ item.name }}</td> <td>{{ item.description }}</td> <td> <button (click)="editItem(item.id)">Editar</button> <button (click)="deleteItem(item.id)">Eliminar</button> </td> </tr> </tbody> </table> |
Conclusión y próximos pasos
¡Felicidades! Has construido una aplicación CRUD básica en Angular usando TypeScript que incluye:
- Configuración completa con Angular CLI.
- Creación organizada de componentes y servicios.
- Simulación de API REST con Angular In-memory Web API.
- Enrutamiento para navegación entre vistas.
- Formularios reactivos con validación.
Buenas prácticas destacadas
- Modularidad y organización del código.
- Uso de servicios para separar lógica de negocio.
- Manejo de observables para datos asíncronos.
- Validaciones para mejorar experiencia y calidad de datos.
Próximos pasos recomendados
- Conectar con un backend real usando APIs REST.
- Implementar paginación o búsqueda en la lista.
- Añadir autenticación y manejo de estados.
- Mejorar estilos con Angular Material o Bootstrap.
- Escribir pruebas unitarias y e2e.
Si quieres profundizar en desarrollo frontend TypeScript o programación Angular para principiantes, sigue explorando y construyendo proyectos. La comunidad Angular es muy activa y encontrarás muchos recursos oficiales.
¡Gracias por leer este tutorial Angular! Si te ha sido útil, no olvides compartirlo y dejar tus comentarios con dudas o sugerencias.
Resumen rápido
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# Instalar Angular CLI npm install -g @angular/cli # Crear nuevo proyecto ng new angular-crud-app cd angular-crud-app # Crear componentes y servicios ng g c components/item-list ng g c components/item-form ng g s services/item # Instalar API simulada npm install angular-in-memory-web-api --save # Ejecutar app ng serve |
¡Manos a la obra y a programar Angular! 🚀