Tutorial completo: Crear una aplicación CRUD con Angular y Firebase
Aprende paso a paso cómo construir una aplicación CRUD (Crear, Leer, Actualizar, Eliminar) utilizando Angular y Firebase. Este tutorial está dirigido a desarrolladores principiantes e intermedios que desean aprender cómo integrar Angular con Firebase para autenticación y base de datos Firestore, creando así una aplicación moderna y serverless.
Índice
- Introducción
- Requisitos previos
- Configuración del proyecto Angular
- Integración con Firebase
- Creación del servicio para Firestore
- Componentes CRUD en Angular
- Despliegue básico de la aplicación
- Conclusión y buenas prácticas
Introducción
En este tutorial aprenderás a:
- Crear un proyecto Angular desde cero.
- Integrar Firebase para autenticación y manejo de datos con Firestore.
- Construir componentes que implementen el flujo CRUD.
- Desplegar la aplicación para que esté accesible online.
Esta guía es ideal para quienes buscan un proyecto práctico para aprender desarrollo frontend moderno con Angular y un backend serverless con Firebase.
Requisitos previos
- Node.js instalado (versión 14 o superior recomendada)
- Angular CLI instalado (
npm install -g @angular/cli
) - Cuenta de Firebase y proyecto creado (Firebase Console)
- Editor de código como VSCode
Configuración del proyecto Angular
- Abre tu terminal o consola.
- Crea un nuevo proyecto Angular con routing y CSS:
1 2 |
ng new angular-firebase-crud --routing --style=css |
- Navega al directorio del proyecto:
1 2 |
cd angular-firebase-crud |
- Ejecuta la aplicación para confirmar que todo funciona:
1 2 |
ng serve |
Abre tu navegador en http://localhost:4200
para verificar.
Integración con Firebase
Configuración de Firebase
- Accede a https://console.firebase.google.com/ y crea un nuevo proyecto.
- En el Dashboard, agrega una nueva aplicación web y copia las configuraciones de Firebase, que lucen así:
1 2 3 4 5 6 7 8 9 |
const firebaseConfig = { apiKey: "API_KEY", authDomain: "PROJECT_ID.firebaseapp.com", projectId: "PROJECT_ID", storageBucket: "PROJECT_ID.appspot.com", messagingSenderId: "SENDER_ID", appId: "APP_ID" }; |
-
En Firebase, dentro del apartado “Authentication > Sign-in method”, habilita la autenticación por correo electrónico y contraseña.
-
En “Firestore Database”, crea una base de datos en modo “modo de producción” o “modalidad de pruebas” (para aprender, modo pruebas está bien).
Instalación de AngularFire
AngularFire es la librería oficial que conecta Angular con Firebase.
Ejecuta:
1 2 |
npm install firebase @angular/fire --save |
Añadir configuración en Angular
Edita el archivo src/environments/environment.ts
para agregar la configuración:
1 2 3 4 5 6 7 8 9 10 11 12 |
export const environment = { production: false, firebase: { apiKey: "API_KEY", authDomain: "PROJECT_ID.firebaseapp.com", projectId: "PROJECT_ID", storageBucket: "PROJECT_ID.appspot.com", messagingSenderId: "SENDER_ID", appId: "APP_ID" } }; |
Reemplaza con tus datos reales.
En app.module.ts
, importa AngularFire y Firestore:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import { AngularFireModule } from '@angular/fire/compat'; import { AngularFirestoreModule } from '@angular/fire/compat/firestore'; import { AngularFireAuthModule } from '@angular/fire/compat/auth'; import { environment } from '../environments/environment'; @NgModule({ declarations: [/* tus componentes*/], imports: [ BrowserModule, AppRoutingModule, AngularFireModule.initializeApp(environment.firebase), AngularFirestoreModule, AngularFireAuthModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { } |
Configuración de autenticación
Habilita la autenticación por correo y contraseña en Firebase, y usaremos AngularFireAuth para login/logout.
Creación del servicio para Firestore
Crea un servicio para manejar las operaciones CRUD y autenticación.
Genera el servicio:
1 2 |
ng generate service services/firebase |
Edita src/app/services/firebase.service.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 |
import { Injectable } from '@angular/core'; import { AngularFirestore, AngularFirestoreCollection } from '@angular/fire/compat/firestore'; import { AngularFireAuth } from '@angular/fire/compat/auth'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; export interface Item { id?: string; nombre: string; descripcion: string; } @Injectable({ providedIn: 'root' }) export class FirebaseService { private itemsCollection: AngularFirestoreCollection<Item>; constructor(private afs: AngularFirestore, private auth: AngularFireAuth) { this.itemsCollection = this.afs.collection<Item>('items'); } // CRUD Firestore // Crear addItem(item: Item) { return this.itemsCollection.add(item); } // Leer todos getItems(): Observable<Item[]> { return this.itemsCollection.snapshotChanges().pipe( map(actions => actions.map(a => { const data = a.payload.doc.data() as Item; const id = a.payload.doc.id; return { id, ...data }; })) ); } // Actualizar updateItem(id: string, item: Item) { return this.itemsCollection.doc(id).update(item); } // Eliminar deleteItem(id: string) { return this.itemsCollection.doc(id).delete(); } // Autenticación login(email: string, password: string) { return this.auth.signInWithEmailAndPassword(email, password); } logout() { return this.auth.signOut(); } getUser() { return this.auth.authState; } } |
Explicación:
- Se define una interface
Item
para manejar nuestros datos. - Métodos para añadir, obtener, actualizar y eliminar.
- Manejo básico de autenticación con login y logout.
Componentes CRUD en Angular
Componente para listar datos
Genera el componente:
1 2 |
ng generate component components/item-list |
Edita src/app/components/item-list/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 |
import { Component, OnInit } from '@angular/core'; import { FirebaseService, Item } from '../../services/firebase.service'; import { Observable } from 'rxjs'; @Component({ selector: 'app-item-list', templateUrl: './item-list.component.html', styleUrls: ['./item-list.component.css'] }) export class ItemListComponent implements OnInit { items$: Observable<Item[]>; constructor(private firebaseService: FirebaseService) { } ngOnInit(): void { this.items$ = this.firebaseService.getItems(); } deleteItem(id: string) { if(confirm('¿Estás seguro de eliminar este ítem?')) { this.firebaseService.deleteItem(id).then(() => { console.log('Item eliminado'); }); } } } |
Edición de la plantilla 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 22 23 24 |
<h2>Lista de ítems</h2> <table> <thead> <tr> <th>Nombre</th> <th>Descripción</th> <th>Acciones</th> </tr> </thead> <tbody> <tr *ngFor="let item of items$ | async"> <td>{{ item.nombre }}</td> <td>{{ item.descripcion }}</td> <td> <button [routerLink]="['/edit', item.id]">Editar</button> <button (click)="deleteItem(item.id!)">Eliminar</button> </td> </tr> </tbody> </table> <a routerLink="/add">Crear nuevo ítem</a> |
Componente para crear y actualizar
Genera el componente:
1 2 |
ng generate component components/item-form |
Edita 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 |
import { Component, OnInit } from '@angular/core'; import { FirebaseService, Item } from '../../services/firebase.service'; import { ActivatedRoute, Router } from '@angular/router'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; @Component({ selector: 'app-item-form', templateUrl: './item-form.component.html', styleUrls: ['./item-form.component.css'] }) export class ItemFormComponent implements OnInit { form: FormGroup; id: string | null = null; constructor( private fb: FormBuilder, private firebaseService: FirebaseService, private route: ActivatedRoute, private router: Router ) { this.form = this.fb.group({ nombre: ['', Validators.required], descripcion: ['', Validators.required] }); } ngOnInit(): void { this.id = this.route.snapshot.paramMap.get('id'); if (this.id) { this.firebaseService.getItems().subscribe(items => { const item = items.find(i => i.id === this.id); if (item) { this.form.patchValue(item); } }); } } async onSubmit() { if (this.form.invalid) return; const item: Item = this.form.value; if (this.id) { await this.firebaseService.updateItem(this.id, item); alert('Ítem actualizado correctamente'); } else { await this.firebaseService.addItem(item); alert('Ítem creado correctamente'); } this.router.navigate(['/']); } } |
Plantilla item-form.component.html
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<h2>{{ id ? 'Editar Ítem' : 'Crear Ítem' }}</h2> <form [formGroup]="form" (ngSubmit)="onSubmit()"> <label for="nombre">Nombre:</label> <input id="nombre" formControlName="nombre" /> <div *ngIf="form.get('nombre')?.invalid && form.get('nombre')?.touched">El nombre es obligatorio.</div> <label for="descripcion">Descripción:</label> <textarea id="descripcion" formControlName="descripcion"></textarea> <div *ngIf="form.get('descripcion')?.invalid && form.get('descripcion')?.touched">La descripción es obligatoria.</div> <button type="submit" [disabled]="form.invalid">{{ id ? 'Actualizar' : 'Crear' }}</button> <button routerLink="/">Cancelar</button> </form> |
No olvides importar ReactiveFormsModule
en app.module.ts
:
1 2 3 4 5 6 7 8 9 |
import { ReactiveFormsModule } from '@angular/forms'; @NgModule({ imports: [ // otros imports ReactiveFormsModule ], }) |
Componente para autenticación (login)
Genera:
1 2 |
ng generate component components/login |
login.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 |
import { Component } from '@angular/core'; import { FirebaseService } from '../../services/firebase.service'; import { Router } from '@angular/router'; @Component({ selector: 'app-login', templateUrl: './login.component.html', styleUrls: ['./login.component.css'] }) export class LoginComponent { email = ''; password = ''; error = ''; constructor(private firebaseService: FirebaseService, private router: Router) { } async onLogin() { try { await this.firebaseService.login(this.email, this.password); this.router.navigate(['/']); } catch (err:any) { this.error = err.message; } } } |
login.component.html
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<h2>Login</h2> <form (ngSubmit)="onLogin()"> <label for="email">Correo electrónico:</label> <input id="email" [(ngModel)]="email" name="email" type="email" required /> <label for="password">Contraseña:</label> <input id="password" [(ngModel)]="password" name="password" type="password" required /> <button type="submit">Ingresar</button> <div *ngIf="error" style="color: red;">{{ error }}</div> </form> |
Asegúrate de importar FormsModule
en app.module.ts
para manejo de ngModel:
1 2 3 4 5 6 7 8 9 |
import { FormsModule } from '@angular/forms'; @NgModule({ imports: [ // otros imports FormsModule ], }) |
Configuración de rutas
En app-routing.module.ts
, configura:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
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'; import { LoginComponent } from './components/login/login.component'; const routes: Routes = [ { path: '', component: ItemListComponent }, { path: 'add', component: ItemFormComponent }, { path: 'edit/:id', component: ItemFormComponent }, { path: 'login', component: LoginComponent }, { path: '**', redirectTo: '' } ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { } |
Despliegue básico de la aplicación
Para desplegar tu app Angular en Firebase Hosting:
- Instala la CLI de Firebase si no la tienes:
1 2 |
npm install -g firebase-tools |
- En la terminal del proyecto, inicia sesión:
1 2 |
firebase login |
- Inicializa Firebase Hosting:
1 2 |
firebase init |
- Selecciona Hosting
- Elige tu proyecto Firebase
- Define
dist/angular-firebase-crud
como directorio público (reemplaza según el nombre real de carpetadist
que genera Angular) - Configura para SPA (respondiendo “sí” para
rewrite all URLs to index.html
)
- Genera la versión de producción:
1 2 |
ng build --prod |
- Finalmente, sube a Firebase Hosting:
1 2 |
firebase deploy |
Al terminar, la terminal mostrará la URL pública donde puedes acceder a tu app.
Conclusión y buenas prácticas
¡Felicidades! Ahora tienes una aplicación CRUD completa con Angular y Firebase que incluye autenticación y almacenamiento en Firestore.
Buenas prácticas a considerar:
- Manejar errores adecuadamente en cada operación (mostrar mensajes amigables al usuario).
- Utilizar Guards de Angular para proteger rutas y controlar acceso de usuarios autenticados.
- Añadir validaciones y formularios más robustos.
- Optimizar las consultas a Firestore con paginación o filtros.
- Mantener las dependencias actualizadas y seguir las recomendaciones de seguridad.
Te invitamos a profundizar en Angular y Firebase para crear aplicaciones aún más potentes y escalables.
¿Listo para aplicar lo aprendido? Comienza creando nuevas funcionalidades, agrega estilos con Angular Material o Bootstrap, y personaliza tu app.
¡Empieza ya a crear aplicación CRUD Angular y domina desarrollo app Angular Firestore!