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 firebase

2. 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;
}