Explicación: Firebase
| Sitio: | Campus virtual DAW - damiansu |
| Curso: | Desarrollo Web en Entorno Cliente |
| Libro: | Explicación: Firebase |
| Imprimido por: | Invitado |
| Día: | domingo, 8 de marzo de 2026, 07:14 |
1. Preparando el entorno
Crear el proyecto Angular (standalone + routing)
ng new app-angular --standalone --routing --style=css
cd mi-app
ng serve -o
Instalar Firebase SDK
npm i firebase2. Consola Firebase
Creamos una app



Creamos la ruta: src/environments/environment.ts
Y ponemos solo la configuración, hay que adaptarlo:
export const environment = {
production: false,
firebase: {
apiKey: "AIzaSyDqyX70BWSKHAPYd5vJUWxCm527Es_DHX8",
authDomain: "proyectoangular-49b1d.firebaseapp.com",
projectId: "proyectoangular-49b1d",
storageBucket: "proyectoangular-49b1d.firebasestorage.app",
messagingSenderId: "836824243584",
appId: "1:836824243584:web:ac79de943fc4cc66667565"
}
};
Modificar main.ts: src/main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { initializeApp } from 'firebase/app';
import { environment } from './environments/environment';
// Inicializamos Firebase ANTES de Angular
initializeApp(environment.firebase);
bootstrapApplication(AppComponent)
.catch(err => console.error(err));
Comprobamos que no hay errores
3. Crear AuthService (login/registro)
Activa Email/Password en Firebase


Crea esta ruta: src/app/core/auth/
Crear auth.service.ts: src/app/core/auth/auth.service.ts
import { Injectable, signal, computed } from '@angular/core';
import {
getAuth,
onAuthStateChanged,
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
signOut,
User,
} from 'firebase/auth';
@Injectable({ providedIn: 'root' })
export class AuthService {
private auth = getAuth();
// Estado (Angular signals)
user = signal<User | null>(null);
loading = signal(true);
// Helpers
isLoggedIn = computed(() => !!this.user());
constructor() {
// Escucha cambios de sesión (persistencia incluida por Firebase)
onAuthStateChanged(this.auth, (u) => {
this.user.set(u);
this.loading.set(false);
});
}
// Registro
register(email: string, password: string) {
return createUserWithEmailAndPassword(this.auth, email, password);
}
// Login
login(email: string, password: string) {
return signInWithEmailAndPassword(this.auth, email, password);
}
// Logout
logout() {
return signOut(this.auth);
}
// JWT (ID Token) para tu backend (Spring/FastAPI)
async getIdToken(): Promise<string | null> {
const u = this.auth.currentUser;
if (!u) return null;
return u.getIdToken(); // JWT
}
}4. Login
LoginComponent
Crea el Guard: src/app/core/auth/auth.guard.ts
import { CanActivateFn, Router } from '@angular/router';
import { inject } from '@angular/core';
import { AuthService } from './auth.service';
export const authGuard: CanActivateFn = () => {
const auth = inject(AuthService);
const router = inject(Router);
// Si aún está cargando el estado, dejamos pasar y que el componente decida (simple)
if (auth.loading()) return true;
if (auth.isLoggedIn()) return true;
router.navigateByUrl('/login');
return false;
};
Crea LoginComponent (ReactiveForms)
src/app/pages/login/
ng g c pages/login --standalone
src/app/pages/login/login.ts
import { Component,inject } from '@angular/core';
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { AuthService } from '../../core/auth/auth.service';
@Component({
selector: 'app-login',
imports: [ReactiveFormsModule],
templateUrl: './login.html',
styleUrl: './login.css',
})
export class LoginComponent {
private fb = inject(FormBuilder);
private auth = inject(AuthService);
private router = inject(Router);
error = '';
form = this.fb.nonNullable.group({
email: ['', [Validators.required, Validators.email]],
password: ['', [Validators.required, Validators.minLength(6)]],
});
get email() { return this.form.controls.email; }
get password() { return this.form.controls.password; }
async onLogin() {
this.error = '';
if (this.form.invalid) {
this.form.markAllAsTouched();
return;
}
const { email, password } = this.form.getRawValue();
try {
await this.auth.login(email, password);
this.router.navigateByUrl('/admin');
} catch (e: any) {
this.error = e?.message ?? 'Error en login';
}
}
async onRegister() {
this.error = '';
if (this.form.invalid) {
this.form.markAllAsTouched();
return;
}
const { email, password } = this.form.getRawValue();
try {
await this.auth.register(email, password);
this.router.navigateByUrl('/admin');
} catch (e: any) {
this.error = e?.message ?? 'Error en registro';
}
}
}
src/app/pages/login/login.component.html
<h2>Login</h2>
<form [formGroup]="form" (ngSubmit)="onLogin()" class="card">
<label>
Email
<input type="email" formControlName="email" />
</label>
@if (email.touched && email.errors?.['required']) {
<small class="err">Email obligatorio</small>
}
@if (email.touched && email.errors?.['email']) {
<small class="err">Email no válido</small>
}
<label>
Contraseña
<input type="password" formControlName="password" />
</label>
@if (password.touched && password.errors?.['required']) {
<small class="err">Contraseña obligatoria</small>
}
@if (password.touched && password.errors?.['minlength']) {
<small class="err">Mínimo 6 caracteres</small>
}
<button type="submit" [disabled]="form.invalid">Entrar</button>
</form>
<div class="row">
<button type="button" (click)="onRegister()" [disabled]="form.invalid">
Crear cuenta
</button>
</div>
@if (error) {
<p class="err">{{ error }}</p>
}
src/app/pages/login/login.component.css
.card { display: grid; gap: 10px; max-width: 360px; padding: 16px; border: 1px solid #ddd; border-radius: 10px; }
label { display: grid; gap: 6px; }
input { padding: 10px; border: 1px solid #ccc; border-radius: 8px; }
button { padding: 10px; border-radius: 8px; border: 1px solid #ccc; cursor: pointer; }
button[disabled] { opacity: 0.6; cursor: not-allowed; }
.row { margin-top: 10px; display: flex; gap: 10px; }
.err { color: #b00020; margin-top: 10px; }
5. AdminComponent (protegido)
Zona admin: src/app/pages/admin/
ng g c pages/admin --standalone
src/app/pages/admin/admin.component.ts
import { Component, inject } from '@angular/core';
import { Router } from '@angular/router';
import { AuthService } from '../../core/auth/auth.service';
@Component({
selector: 'app-admin',
imports: [],
templateUrl: './admin.html',
styleUrl: './admin.css',
})
export class AdminComponent {
auth = inject(AuthService);
private router = inject(Router);
token = '';
async showToken() {
this.token = (await this.auth.getIdToken()) ?? '';
}
async logout() {
await this.auth.logout();
this.router.navigateByUrl('/login');
}
}
css
.token {
white-space: pre-wrap;
word-break: break-all;
margin-top: 12px;
}
.logout {
margin-top: 12px;
}
admin.html
<h2>Admin</h2>
@if (auth.user()) {
<p>Sesión: {{ auth.user()?.email }}</p>
}
<button (click)="showToken()">Ver ID Token</button>
@if (token) {
<pre class="token">{{ token }}</pre>
}
<button class="logout" (click)="logout()">Logout</button>
Rutas
src/app/app.routes.ts
iimport { Routes } from '@angular/router';
import { authGuard } from './core/auth/auth.guard';
export const routes: Routes = [
{ path: '', redirectTo: 'login', pathMatch: 'full' },
{
path: 'login',
loadComponent: () => import('./pages/login/login').then(m => m.LoginComponent),
},
{
path: 'admin',
canActivate: [authGuard],
loadComponent: () => import('./pages/admin/admin').then(m => m.AdminComponent),
},
{ path: '**', redirectTo: 'login' },
];
Tu app.html (para navegar)
src/app/app.ts
import { Component, signal } from '@angular/core';
import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
@Component({
selector: 'app-root',
imports: [RouterOutlet,RouterLink, RouterLinkActive],
templateUrl: './app.html',
styleUrl: './app.css'
})
export class App {
protected readonly title = signal('app-angular');
}
src/app/app.html
<nav class="nav">
<a routerLink="/login" routerLinkActive="active">Login</a>
<a routerLink="/admin" routerLinkActive="active">Admin</a>
</nav>
<router-outlet></router-outlet>
src/app/app.css
.nav { display: flex; gap: 12px; padding: 12px 0; }
a { text-decoration: none; }
.active {
font-weight: bold;
text-decoration: underline;
}