1. Qué crearás
En este codelab, creará un blog itinerante con un mapa colaborativo en tiempo real con lo último de nuestra biblioteca Angular: AngularFire . La aplicación web final consistirá en un blog de viajes donde podrás subir imágenes de cada lugar al que hayas viajado.
AngularFire se utilizará para crear la aplicación web, Emulator Suite para pruebas locales, Autenticación para realizar un seguimiento de los datos del usuario, Firestore y Storage para conservar datos y medios, con tecnología de Cloud Functions y, finalmente, Firebase Hosting para implementar la aplicación.
lo que aprenderás
- Cómo desarrollar con productos Firebase localmente con Emulator Suite
- Cómo mejorar su aplicación web con AngularFire
- Cómo conservar tus datos en Firestore
- Cómo persistir los medios en el almacenamiento
- Cómo implementar su aplicación en Firebase Hosting
- Cómo utilizar Cloud Functions para interactuar con sus bases de datos y API
Lo que necesitarás
- Node.js versión 10 o superior
- Una cuenta de Google para la creación y gestión de tu proyecto Firebase
- Firebase CLI versión 11.14.2 o posterior
- Un navegador de tu elección, como Chrome
- Conocimientos básicos de Angular y Javascript.
2. Obtenga el código de muestra
Clona el repositorio GitHub del codelab desde la línea de comando:
git clone https://github.com/firebase/codelab-friendlychat-web
Alternativamente, si no tienes git instalado, puedes descargar el repositorio como un archivo ZIP .
El repositorio de Github contiene proyectos de muestra para múltiples plataformas.
Este codelab solo utiliza el repositorio de webframework:
- 📁 webframework : el código inicial que desarrollarás durante este codelab.
Instalar dependencias
Después de la clonación, instale las dependencias en la carpeta raíz y functions
antes de crear la aplicación web.
cd webframework && npm install
cd functions && npm install
Instalar Firebase CLI
Instale Firebase CLI usando este comando en una terminal:
npm install -g firebase-tools
Verifique que su versión de Firebase CLI sea superior a 11.14.2 usando:
firebase --version
Si su versión es inferior a 11.14.2, actualice usando:
npm update firebase-tools
3. Crea y configura un proyecto de Firebase
Crear un proyecto de Firebase
- Inicia sesión en Firebase .
- En Firebase console, haz clic en Agregar proyecto y luego nombra tu proyecto de Firebase <tu-proyecto> . Recuerde el ID del proyecto de su proyecto de Firebase.
- Haga clic en Crear proyecto .
Importante : su proyecto de Firebase se llamará <your-project> , pero Firebase le asignará automáticamente un ID de proyecto único con el formato <your-project>-1234 . Este identificador único es la forma en que realmente se identifica su proyecto (incluso en la CLI), mientras que <su-proyecto> es simplemente un nombre para mostrar.
La aplicación que vamos a crear utiliza productos de Firebase que están disponibles para aplicaciones web:
- Autenticación de Firebase para permitir que sus usuarios inicien sesión fácilmente en su aplicación.
- Cloud Firestore para guardar datos estructurados en la nube y recibir notificaciones instantáneas cuando los datos cambian.
- Cloud Storage para Firebase para guardar archivos en la nube.
- Firebase Hosting para alojar y servir sus activos.
- Funciones para interactuar con API internas y externas.
Algunos de estos productos necesitan configuraciones especiales o deben habilitarse mediante Firebase console.
Agregar una aplicación web de Firebase al proyecto
- Haga clic en el ícono web para crear una nueva aplicación web de Firebase.
- En el siguiente paso, verá un objeto de configuración. Copie el contenido de este objeto en el archivo
environments/environment.ts
.
Habilite el inicio de sesión de Google para la autenticación de Firebase
Para permitir que los usuarios inicien sesión en la aplicación web con sus cuentas de Google, utilizaremos el método de inicio de sesión de Google .
Para habilitar el inicio de sesión de Google :
- En la consola de Firebase, ubique la sección Construir en el panel izquierdo.
- Haga clic en Autenticación y luego haga clic en la pestaña Método de inicio de sesión (o haga clic aquí para ir directamente allí).
- Habilite el proveedor de inicio de sesión de Google y luego haga clic en Guardar .
- Establezca el nombre público de su aplicación en <nombre-de-su-proyecto> y elija un correo electrónico de soporte del proyecto en el menú desplegable.
Habilitar Cloud Firestore
- En la sección Generar de Firebase console, haz clic en Base de datos de Firestore .
- Haga clic en Crear base de datos en el panel de Cloud Firestore.
- Establezca la ubicación donde se almacenan sus datos de Cloud Firestore. Puede dejar esto como predeterminado o elegir una región cercana a usted.
Habilitar almacenamiento en la nube
La aplicación web utiliza Cloud Storage para Firebase para almacenar, cargar y compartir imágenes.
- En la sección Compilación de Firebase console, haz clic en Almacenamiento .
- Si no hay ningún botón Comenzar , significa que el almacenamiento en la nube ya está
habilitado y no necesita seguir los pasos a continuación.
- Haga clic en Comenzar .
- Lea el descargo de responsabilidad sobre las reglas de seguridad para su proyecto de Firebase y luego haga clic en Siguiente .
- La ubicación de Cloud Storage está preseleccionada con la misma región que eligió para su base de datos de Cloud Firestore. Haga clic en Listo para completar la configuración.
Con las reglas de seguridad predeterminadas, cualquier usuario autenticado puede escribir cualquier cosa en Cloud Storage. Haremos que nuestro almacenamiento sea más seguro más adelante en este codelab.
4. Conéctese a su proyecto de Firebase
La interfaz de línea de comandos (CLI) de Firebase le permite usar Firebase Hosting para servir su aplicación web localmente, así como para implementar su aplicación web en su proyecto de Firebase.
Asegúrese de que su línea de comando acceda al directorio webframework
local de su aplicación.
Conecte el código de la aplicación web a su proyecto de Firebase. Primero, inicie sesión en Firebase CLI en la línea de comando:
firebase login
Luego ejecute el siguiente comando para crear un alias de proyecto. Reemplace $YOUR_PROJECT_ID
con el ID de su proyecto de Firebase.
firebase use $YOUR_PROJECT_ID
Agregar fuego angular
Para agregar AngularFire a la aplicación, ejecute el comando:
ng add @angular/fire
Luego, siga las instrucciones de la línea de comando y seleccione las funciones que existen en su proyecto de Firebase.
Inicializar base de fuego
Para inicializar el proyecto de Firebase, ejecute:
firebase init
Luego, siguiendo las indicaciones de la línea de comando, seleccione las funciones y los emuladores que se utilizaron en su proyecto de Firebase.
Iniciar los emuladores
Desde el directorio webframework
, ejecute el siguiente comando para iniciar los emuladores:
firebase emulators:start
Al final deberías ver algo como esto:
$ firebase emulators:start
i emulators: Starting emulators: auth, functions, firestore, hosting, functions
i firestore: Firestore Emulator logging to firestore-debug.log
i hosting: Serving hosting files from: public
✔ hosting: Local server: http://localhost:5000
i ui: Emulator UI logging to ui-debug.log
i functions: Watching "/functions" for Cloud Functions...
✔ functions[updateMap]: firestore function initialized.
┌─────────────────────────────────────────────────────────────┐
│ ✔ All emulators ready! It is now safe to connect your app. │
│ i View Emulator UI at http://localhost:4000 │
└─────────────────────────────────────────────────────────────┘
┌────────────────┬────────────────┬─────────────────────────────────┐
│ Emulator │ Host:Port │ View in Emulator UI │
├────────────────┼────────────────┼─────────────────────────────────┤
│ Authentication │ localhost:9099 │ http://localhost:4000/auth │
├────────────────┼────────────────┼─────────────────────────────────┤
│ Functions │ localhost:5001 │ http://localhost:4000/functions │
├────────────────┼────────────────┼─────────────────────────────────┤
│ Firestore │ localhost:8080 │ http://localhost:4000/firestore │
├────────────────┼────────────────┼─────────────────────────────────┤
│ Hosting │ localhost:5000 │ n/a │
└────────────────┴────────────────┴─────────────────────────────────┘
Emulator Hub running at localhost:4400
Other reserved ports: 4500
Issues? Report them at https://github.com/firebase/firebase-tools/issues and attach the *-debug.log files.
Una vez que veas el ✔All emulators ready!
mensaje, los emuladores están listos para usar.
Deberías ver la interfaz de usuario de tu aplicación de viajes, que (¡todavía!) no funciona:
¡Ahora comencemos a construir!
5. Conecte la aplicación web a los emuladores.
Según la tabla en los registros del emulador, el emulador de Cloud Firestore escucha en el puerto 8080 y el emulador de autenticación escucha en el puerto 9099.
Abra la interfaz de usuario del emulador
En su navegador web, navegue hasta http://127.0.0.1:4000/ . Deberías ver la interfaz de usuario de Emulator Suite.
Dirija la aplicación para usar los emuladores.
En src/app/app.module.ts
, agregue el siguiente código a la lista de importaciones de AppModule
:
@NgModule({
declarations: [...],
imports: [
provideFirebaseApp(() => initializeApp(environment.firebase)),
provideAuth(() => {
const auth = getAuth();
if (location.hostname === 'localhost') {
connectAuthEmulator(auth, 'http://127.0.0.1:9099', { disableWarnings: true });
}
return auth;
}),
provideFirestore(() => {
const firestore = getFirestore();
if (location.hostname === 'localhost') {
connectFirestoreEmulator(firestore, '127.0.0.1', 8080);
}
return firestore;
}),
provideFunctions(() => {
const functions = getFunctions();
if (location.hostname === 'localhost') {
connectFunctionsEmulator(functions, '127.0.0.1', 5001);
}
return functions;
}),
provideStorage(() => {
const storage = getStorage();
if (location.hostname === 'localhost') {
connectStorageEmulator(storage, '127.0.0.1', 5001);
}
return storage;
}),
...
]
La aplicación ahora está configurada para usar emuladores locales, lo que permite realizar pruebas y desarrollo localmente.
6. Agregar autenticación
Ahora que los emuladores están configurados para la aplicación, podemos agregar funciones de autenticación para garantizar que cada usuario haya iniciado sesión antes de publicar mensajes.
Para hacerlo, podemos importar funciones signin
directamente desde AngularFire y rastrear el estado de autenticación de su usuario con la función authState
. Modifique las funciones de la página de inicio de sesión para que la página verifique el estado de autenticación del usuario al cargar.
Inyectar autenticación AngularFire
En src/app/pages/login-page/login-page.component.ts
, importe Auth
desde @angular/fire/auth
e inyéctelo en LoginPageComponent
. Los proveedores de autenticación, como Google, y funciones como signin
y signout
también se pueden importar directamente desde el mismo paquete y usarse en la aplicación.
import { Auth, GoogleAuthProvider, signInWithPopup, signOut, user } from '@angular/fire/auth';
export class LoginPageComponent implements OnInit {
private auth: Auth = inject(Auth);
private provider = new GoogleAuthProvider();
user$ = user(this.auth);
constructor() {}
ngOnInit(): void {}
login() {
signInWithPopup(this.auth, this.provider).then((result) => {
const credential = GoogleAuthProvider.credentialFromResult(result);
return credential;
})
}
logout() {
signOut(this.auth).then(() => {
console.log('signed out');}).catch((error) => {
console.log('sign out error: ' + error);
})
}
}
¡Ahora la página de inicio de sesión está funcional! Intente iniciar sesión y consulte los resultados en el Emulador de autenticación.
7. Configurando Firestore
En este paso, agregará funcionalidad para publicar y actualizar publicaciones de blogs de viajes almacenadas en Firestore.
Al igual que la autenticación, las funciones de Firestore vienen empaquetadas desde AngularFire. Cada documento pertenece a una colección y cada documento también puede tener colecciones anidadas. Es necesario conocer la path
del documento en Firestore para crear y actualizar una publicación de blog de viajes.
Implementación de TravelService
Dado que muchas páginas diferentes necesitarán leer y actualizar documentos de Firestore en la aplicación web, podemos implementar las funciones en src/app/services/travel.service.ts
, para abstenernos de inyectar repetidamente las mismas funciones de AngularFire en cada página.
Comience inyectando Auth
, similar al paso anterior, así como Firestore
en nuestro servicio. También es útil definir un objeto user$
observable que escuche el estado de autenticación actual.
import { doc, docData, DocumentReference, Firestore, getDoc, setDoc, updateDoc, collection, addDoc, deleteDoc, collectionData, Timestamp } from "@angular/fire/firestore";
export class TravelService {
firestore: Firestore = inject(Firestore);
auth: Auth = inject(Auth);
user$ = authState(this.auth).pipe(filter(user => user !== null), map(user => user!));
router: Router = inject(Router);
Agregar una publicación de viaje
Las publicaciones de viajes existirán como documentos almacenados en Firestore y, dado que los documentos deben existir dentro de las colecciones, la colección que contiene todas las publicaciones de viajes se denominará travels
. Por lo tanto, la ruta de cualquier puesto de viaje será travels/
Usando la función addDoc
de AngularFire, se puede insertar un objeto en una colección:
async addEmptyTravel(userId: String) {
...
addDoc(collection(this.firestore, 'travels'), travelData).then((travelRef) => {
collection(this.firestore, `travels/${travelRef.id}/stops`);
setDoc(travelRef, {... travelData, id: travelRef.id})
this.router.navigate(['edit', `${travelRef.id}`]);
return travelRef;
})
}
Actualizar y eliminar datos
Dado el uid de cualquier publicación de viaje, se puede deducir la ruta del documento almacenado en Firestore, que luego se puede leer, actualizar o eliminar usando las funciones updateFoc
y deleteDoc
de AngularFire:
async updateData(path: string, data: Partial<Travel | Stop>) {
await updateDoc(doc(this.firestore, path), data)
}
async deleteData(path: string) {
const ref = doc(this.firestore, path);
await deleteDoc(ref)
}
Leer datos como observables
Dado que las publicaciones de viaje y las paradas en el camino se pueden modificar después de la creación, sería más útil obtener objetos de documento como observables para suscribirse a cualquier cambio que se realice. Esta funcionalidad la ofrecen las funciones docData
y collectionData
de @angular/fire/firestore
.
getDocData(path: string) {
return docData(doc(this.firestore, path), {idField: 'id'}) as Observable<Travel | Stop>
}
getCollectionData(path: string) {
return collectionData(collection(this.firestore, path), {idField: 'id'}) as Observable<Travel[] | Stop[]>
}
Agregar paradas a una publicación de viaje
Ahora que las operaciones de la publicación de viajes están configuradas, es hora de considerar las paradas, que existirán en una subcolección de una publicación de viajes como esta: travels/ /stops/
travels/ /stops/
Esto es casi idéntico a crear una publicación de viajes, así que desafíate a implementarlo por tu cuenta o consulta la implementación a continuación:
async addStop(travelId: string) {
...
const ref = await addDoc(collection(this.firestore, `travels/${travelId}/stops`), stopData)
setDoc(ref, {...stopData, id: ref.id})
}
¡Lindo! Las funciones de Firestore se implementaron en el servicio de Viajes, por lo que ahora puedes verlas en acción.
Usar las funciones de Firestore en la aplicación
Navegue a src/app/pages/my-travels/my-travels.component.ts
e inyecte TravelService
para usar sus funciones.
travelService = inject(TravelService);
travelsData$: Observable<Travel[]>;
stopsList$!: Observable<Stop[]>;
constructor() {
this.travelsData$ = this.travelService.getCollectionData(`travels`) as Observable<Travel[]>
}
Se llama a TravelService
en el constructor para obtener una matriz observable de todos los viajes.
En el caso de que solo se necesiten los viajes del usuario actual, utilice la función query
.
Otros métodos para garantizar la seguridad incluyen la implementación de reglas de seguridad o el uso de Cloud Functions con Firestore, como se explora en los pasos opcionales a continuación.
Luego, simplemente llame a las funciones implementadas en TravelService
.
async createTravel(userId: String) {
this.travelService.addEmptyTravel(userId);
}
deleteTravel(travelId: String) {
this.travelService.deleteData(`travels/${travelId}`)
}
¡Ahora la página Mis viajes debería estar funcional! Mira lo que sucede en tu emulador de Firestore cuando creas una nueva publicación de viaje.
Luego, repita para las funciones de actualización en /src/app/pages/edit-travels/edit-travels.component.ts
:
travelService: TravelService = inject(TravelService)
travelId = this.activatedRoute.snapshot.paramMap.get('travelId');
travelData$: Observable<Travel>;
stopsData$: Observable<Stop[]>;
constructor() {
this.travelData$ = this.travelService.getDocData(`travels/${this.travelId}`) as Observable<Travel>
this.stopsData$ = this.travelService.getCollectionData(`travels/${this.travelId}/stops`) as Observable<Stop[]>
}
updateCurrentTravel(travel: Partial<Travel>) {
this.travelService.updateData(`travels${this.travelId}`, travel)
}
updateCurrentStop(stop: Partial<Stop>) {
stop.type = stop.type?.toString();
this.travelService.updateData(`travels${this.travelId}/stops/${stop.id}`, stop)
}
addStop() {
if (!this.travelId) return;
this.travelService.addStop(this.travelId);
}
deleteStop(stopId: string) {
if (!this.travelId || !stopId) {
return;
}
this.travelService.deleteData(`travels${this.travelId}/stops/${stopId}`)
this.stopsData$ = this.travelService.getCollectionData(`travels${this.travelId}/stops`) as Observable<Stop[]>
}
8. Configurar el almacenamiento
Ahora implementará Almacenamiento para almacenar imágenes y otros tipos de medios.
Cloud Firestore se utiliza mejor para almacenar datos estructurados, como objetos JSON. Cloud Storage está diseñado para almacenar archivos o blobs. En esta aplicación, la utilizará para permitir a los usuarios compartir sus fotografías de viajes.
Del mismo modo, con Firestore, almacenar y actualizar archivos con Storage requiere un identificador único para cada archivo.
Implementemos las funciones en TraveService
:
Subiendo un archivo
Navegue a src/app/services/travel.service.ts
e inyecte Almacenamiento desde AngularFire:
export class TravelService {
firestore: Firestore = inject(Firestore);
auth: Auth = inject(Auth);
storage: Storage = inject(Storage);
E implemente la función de carga:
async uploadToStorage(path: string, input: HTMLInputElement, contentType: any) {
if (!input.files) return null
const files: FileList = input.files;
for (let i = 0; i < files.length; i++) {
const file = files.item(i);
if (file) {
const imagePath = `${path}/${file.name}`
const storageRef = ref(this.storage, imagePath);
await uploadBytesResumable(storageRef, file, contentType);
return await getDownloadURL(storageRef);
}
}
return null;
}
La principal diferencia entre acceder a documentos desde Firestore y archivos desde Cloud Storage es que, aunque ambos siguen rutas estructuradas de carpetas, la combinación de URL base y ruta se obtiene a través de getDownloadURL
, que luego se puede almacenar y usar en un archivo.
Usando la función en la aplicación
Navegue a src/app/components/edit-stop/edit-stop.component.ts
y llame a la función de carga usando:
async uploadFile(file: HTMLInputElement, stop: Partial<Stop>) {
const path = `/travels/${this.travelId}/stops/${stop.id}`
const url = await this.travelService.uploadToStorage(path, file, {contentType: 'image/png'});
stop.image = url ? url : '';
this.travelService.updateData(path, stop);
}
Cuando se carga la imagen, el archivo multimedia en sí se cargará en el almacenamiento y la URL se almacenará en consecuencia en el documento en Firestore.
9. Implementación de la aplicación
¡Ahora estamos listos para implementar la aplicación!
Copie las configuraciones firebase
de src/environments/environment.ts
a src/environments/environment.prod.ts
y ejecute:
firebase deploy
Debería ver algo como esto:
✔ Browser application bundle generation complete.
✔ Copying assets complete.
✔ Index html generation complete.
=== Deploying to 'friendly-travels-b6a4b'...
i deploying storage, firestore, hosting
i firebase.storage: checking storage.rules for compilation errors...
✔ firebase.storage: rules file storage.rules compiled successfully
i firestore: reading indexes from firestore.indexes.json...
i cloud.firestore: checking firestore.rules for compilation errors...
✔ cloud.firestore: rules file firestore.rules compiled successfully
i storage: latest version of storage.rules already up to date, skipping upload...
i firestore: deploying indexes...
i firestore: latest version of firestore.rules already up to date, skipping upload...
✔ firestore: deployed indexes in firestore.indexes.json successfully for (default) database
i hosting[friendly-travels-b6a4b]: beginning deploy...
i hosting[friendly-travels-b6a4b]: found 6 files in .firebase/friendly-travels-b6a4b/hosting
✔ hosting[friendly-travels-b6a4b]: file upload complete
✔ storage: released rules storage.rules to firebase.storage
✔ firestore: released rules firestore.rules to cloud.firestore
i hosting[friendly-travels-b6a4b]: finalizing version...
✔ hosting[friendly-travels-b6a4b]: version finalized
i hosting[friendly-travels-b6a4b]: releasing new version...
✔ hosting[friendly-travels-b6a4b]: release complete
✔ Deploy complete!
Project Console: https://console.firebase.google.com/project/friendly-travels-b6a4b/overview
Hosting URL: https://friendly-travels-b6a4b.web.app
10. ¡Felicitaciones!
¡Ahora su aplicación debería estar completa e implementada en Firebase Hosting! Todos los datos y análisis ahora estarán accesibles en su Firebase Console.
Para obtener más funciones relacionadas con AngularFire, funciones y reglas de seguridad, no olvide consultar los pasos opcionales a continuación, así como otros Firebase Codelabs .
11. Opcional: guardias de autenticación de AngularFire
Junto con Firebase Authentication, AngularFire también ofrece protecciones basadas en autenticación en rutas, para que los usuarios con acceso insuficiente puedan ser redirigidos. Esto ayuda a proteger la aplicación contra el acceso de los usuarios a datos protegidos.
En src/app/app-routing.module.ts
, importe
import {AuthGuard, redirectLoggedInTo, redirectUnauthorizedTo} from '@angular/fire/auth-guard'
Luego puede definir funciones sobre cuándo y dónde se debe redirigir a los usuarios en determinadas páginas:
const redirectUnauthorizedToLogin = () => redirectUnauthorizedTo(['signin']);
const redirectLoggedInToTravels = () => redirectLoggedInTo(['my-travels']);
Luego simplemente agrégalos a tus rutas:
const routes: Routes = [
{path: '', component: LoginPageComponent, canActivate: [AuthGuard], data: {authGuardPipe: redirectLoggedInToTravels}},
{path: 'signin', component: LoginPageComponent, canActivate: [AuthGuard], data: {authGuardPipe: redirectLoggedInToTravels}},
{path: 'my-travels', component: MyTravelsComponent, canActivate: [AuthGuard], data: {authGuardPipe: redirectUnauthorizedToLogin}},
{path: 'edit/:travelId', component: EditTravelsComponent, canActivate: [AuthGuard], data: {authGuardPipe: redirectUnauthorizedToLogin}},
];
12. Opcional: reglas de seguridad
Tanto Firestore como Cloud Storage utilizan reglas de seguridad ( firestore.rules
y security.rules
respectivamente) para hacer cumplir la seguridad y validar los datos.
Por el momento, los datos de Firestore y Storage tienen acceso abierto para lecturas y escrituras, ¡pero no quieres que la gente cambie las publicaciones de otros! Puede utilizar reglas de seguridad para restringir el acceso a sus colecciones y documentos.
Reglas del almacén de bomberos
Para permitir que solo los usuarios autenticados vean publicaciones de viajes, vaya al archivo firestore.rules
y agregue:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/travels {
allow read: if request.auth.uid != null;
allow write:
if request.auth.uid == request.resource.data.userId;
}
}
También se pueden utilizar reglas de seguridad para validar datos:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/posts {
allow read: if request.auth.uid != null;
allow write:
if request.auth.uid == request.resource.data.userId;
&& "author" in request.resource.data
&& "text" in request.resource.data
&& "timestamp" in request.resource.data;
}
}
Reglas de almacenamiento
De manera similar, podemos usar reglas de seguridad para imponer el acceso a las bases de datos de almacenamiento en storage.rules
. Tenga en cuenta que también podemos usar funciones para comprobaciones más complejas:
rules_version = '2';
function isImageBelowMaxSize(maxSizeMB) {
return request.resource.size < maxSizeMB * 1024 * 1024
&& request.resource.contentType.matches('image/.*');
}
service firebase.storage {
match /b/{bucket}/o {
match /{userId}/{postId}/{filename} {
allow write: if request.auth != null
&& request.auth.uid == userId && isImageBelowMaxSize(5);
allow read;
}
}
}