1. O que você vai criar
Neste codelab, você criará um blog de viagens com um mapa colaborativo em tempo real com as novidades da biblioteca do Angular: o AngularFire. O app da Web final será um blog de viagens onde você pode fazer upload de imagens para cada local que visitou.
O AngularFire será usado para criar o app da Web, o Pacote de emuladores para testes locais, o Authentication para rastrear dados de usuários, o Firestore e o Storage para manter dados e mídia, com tecnologia do Cloud Functions e, por fim, o Firebase Hosting para implantar o app.
Conteúdo
- Como desenvolver localmente com produtos do Firebase usando o Pacote de emuladores
- Como aprimorar seu app da Web com o AngularFire
- Como manter seus dados no Firestore
- Como persistir mídia no Storage
- Como implantar o app no Firebase Hosting
- Como usar o Cloud Functions para interagir com bancos de dados e APIs
Pré-requisitos
- Node.js versão 10 ou mais recente
- Uma Conta do Google para a criação e o gerenciamento do seu projeto do Firebase
- A CLI do Firebase versão 11.14.2 ou mais recente
- em um navegador da sua escolha, como o Chrome;
- Noções básicas de Angular e JavaScript
2. Acessar o exemplo de código
Clone o repositório do GitHub do codelab na linha de comando:
git clone https://github.com/firebase/codelab-friendlychat-web
Se você não tiver o git instalado, faça o download do repositório como um arquivo ZIP (link em inglês).
O repositório do GitHub contém projetos de amostra para várias plataformas.
Este codelab usa apenas o repositório webframework:
- 📁 webframework: o código inicial que será usado como base durante este codelab.
Instalar dependências
Após a clonagem, instale as dependências nas pastas raiz e functions
antes de criar o app da Web.
cd webframework && npm install
cd functions && npm install
Instalar a CLI do Firebase
Instale a CLI do Firebase usando este comando em um terminal:
npm install -g firebase-tools
Verifique se a versão da CLI do Firebase é mais recente que 11.14.2 usando:
firebase --version
Se sua versão for anterior à 11.14.2, atualize-a usando:
npm update firebase-tools
3. Criar e configurar um projeto do Firebase
Criar um projeto do Firebase
- Faça login no Firebase.
- No console do Firebase, clique em Adicionar projeto e nomeie seu projeto do Firebase como <your-project>. Lembre-se do ID do seu projeto do Firebase.
- Clique em Criar projeto.
Importante: seu projeto do Firebase será chamado <your-project>, mas o Firebase atribuirá automaticamente a ele um ID de projeto exclusivo no formato <your-project>-1234. Esse identificador exclusivo é como seu projeto é realmente identificado (inclusive na CLI), enquanto <your-project> é simplesmente um nome de exibição.
O aplicativo que criaremos usa produtos do Firebase disponíveis para apps da Web:
- Firebase Authentication: usado para permitir que os usuários façam login no seu app com facilidade.
- Cloud Firestore: é usado para salvar dados estruturados na nuvem e receber notificações instantâneas quando os dados são alterados.
- Cloud Storage para Firebase: é usado para salvar arquivos na nuvem.
- Firebase Hosting: é usado para hospedar e exibir seus recursos.
- Funções para interagir com APIs internas e externas.
Alguns desses produtos precisam de configurações especiais ou ser ativados usando o Console do Firebase.
Adicionar um app da Web do Firebase ao projeto
- Clique no ícone da Web para criar um app da Web do Firebase.
- Na próxima etapa, você verá um objeto de configuração. Copie o conteúdo desse objeto no arquivo
environments/environment.ts
.
Ative o Login do Google para o Firebase Authentication.
Para permitir que os usuários façam login no app da Web com as próprias Contas do Google, usaremos o método de login do Google.
Para ativar o Login do Google:
- No Console do Firebase, localize a seção Build no painel esquerdo.
- Clique em Autenticação e na guia Método de login (ou clique aqui para acessá-la).
- Ative o provedor de login do Google e clique em Salvar.
- Defina o nome público do app como <your-project-name> e escolha um E-mail de suporte do projeto no menu suspenso.
Ativar o Cloud Firestore
- Na seção Build do Console do Firebase, clique em Firestore Database.
- Clique em Criar banco de dados no painel do Cloud Firestore.
- Defina o local em que os dados do Cloud Firestore são armazenados. Mantenha essa configuração como padrão ou escolha uma região próxima a você.
Ativar o Cloud Storage
O app da Web usa o Cloud Storage para Firebase para armazenar, fazer upload e compartilhar imagens.
- Na seção Build do Console do Firebase, clique em Armazenamento.
- Se não houver um botão Get Started, isso significa que o Cloud Storage já está
ativado, e você não precisa seguir as etapas abaixo.
- Clique em Começar.
- Leia a exoneração de responsabilidade sobre as regras de segurança do seu projeto do Firebase e clique em Próxima.
- O local do Cloud Storage é pré-selecionado com a mesma região que você escolheu para seu banco de dados do Cloud Firestore. Clique em Concluído para finalizar a configuração.
Com as regras de segurança padrão, qualquer usuário autenticado pode gravar o que quiser no Cloud Storage. Vamos tornar nosso armazenamento mais seguro mais adiante neste codelab.
4. Conectar ao seu projeto do Firebase
A interface de linha de comando (CLI) do Firebase permite usar o Firebase Hosting para disponibilizar seu app da Web localmente e implantá-lo no projeto do Firebase.
Confira se a linha de comando está acessando o diretório local webframework
do app.
Conecte o código do app da Web ao seu projeto do Firebase. Primeiro, faça login na CLI do Firebase na linha de comando:
firebase login
Em seguida, execute o seguinte comando para criar um alias de projeto. Substitua $YOUR_PROJECT_ID
pelo ID do seu projeto do Firebase.
firebase use $YOUR_PROJECT_ID
Adicionar o AngularFire
Para adicionar o AngularFire ao app, execute o comando:
ng add @angular/fire
Em seguida, siga as instruções da linha de comando e selecione os recursos que existem no seu projeto do Firebase.
Inicializar o Firebase
Para inicializar o projeto do Firebase, execute:
firebase init
Em seguida, seguindo os prompts da linha de comando, selecione os recursos e emuladores que foram usados no seu projeto do Firebase.
Iniciar os emuladores
No diretório webframework
, execute o seguinte comando para iniciar os emuladores:
firebase emulators:start
Futuramente, você vai encontrar algo assim:
$ 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.
Depois que a mensagem ✔All emulators ready!
aparecer, os emuladores estarão prontos para uso.
Você verá a interface do seu app de viagens, que (ainda) não está funcionando:
Agora vamos criar!
5. Conectar o app da Web aos emuladores
Com base na tabela dos registros do emulador, o emulador do Cloud Firestore está escutando na porta 8080 e o emulador do Authentication está escutando na porta 9099.
Abrir a EmulatorUI
No navegador da Web, acesse http://127.0.0.1:4000/. A IU do Pacote de emuladores é exibida.
Encaminhar o app para usar os emuladores
Em src/app/app.module.ts
, adicione o seguinte código à lista de importações 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;
}),
...
]
Agora, o app está configurado para usar emuladores locais, permitindo que o teste e o desenvolvimento sejam feitos localmente.
6. Adicionar autenticação
Agora que os emuladores estão configurados para o app, podemos adicionar recursos do Authentication para garantir que cada usuário esteja conectado antes de postar mensagens.
Para fazer isso, podemos importar as funções signin
diretamente do AngularFire e acompanhar o estado de autenticação do usuário com a função authState
. Modifique as funções da página de login para que ela verifique o estado de autenticação do usuário no carregamento.
Como injetar autenticação do AngularFire
No src/app/pages/login-page/login-page.component.ts
, importe Auth
do @angular/fire/auth
e injete-o no LoginPageComponent
. Provedores de autenticação, como o Google, e funções como signin
e signout
também podem ser importados diretamente do mesmo pacote e usados no app.
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);
})
}
}
A página de login já está funcionando. Tente fazer login e confira os resultados no emulador do Authentication.
7. Como configurar o Firestore
Nesta etapa, você vai adicionar a funcionalidade de publicar e atualizar postagens de blogs de viagens armazenadas no Firestore.
Assim como o Authentication, as funções do Firestore vêm pré-empacotadas do AngularFire. Cada documento pertence a uma coleção, e cada documento também pode ter coleções aninhadas. É necessário saber o path
do documento no Firestore para criar e atualizar uma postagem do blog de viagens.
Implementação do TravelService
Como muitas páginas diferentes precisarão ler e atualizar documentos do Firestore no app da Web, podemos implementar as funções em src/app/services/travel.service.ts
para não injetar repetidamente as mesmas funções do AngularFire em todas as páginas.
Comece injetando Auth
, semelhante à etapa anterior, e Firestore
no nosso serviço. Definir um objeto user$
observável que detecta o status atual da autenticação também é útil.
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);
Adicionar uma postagem de viagem
As postagens de viagem existem como documentos armazenados no Firestore. Como os documentos precisam estar dentro de coleções, a coleção que contém todas as postagens de viagens será chamada de travels
. Assim, o caminho de qualquer postagem de viagem será travels/
.
Usando a função addDoc
do AngularFire, um objeto pode ser inserido em uma coleção:
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;
})
}
Atualizar e excluir dados
Com o UID de qualquer postagem de viagem, é possível deduzir o caminho do documento armazenado no Firestore, que pode ser lido, atualizado ou excluído usando as funções updateFoc
e deleteDoc
do 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)
}
Ler dados como um elemento observável
Como as postagens de viagem e as paradas ao longo do caminho podem ser modificadas após a criação, seria mais útil usar objetos de documentos como observáveis para receber as mudanças feitas. Essa funcionalidade é oferecida pelas funções docData
e collectionData
do @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[]>
}
Adicionar paradas a uma postagem de viagem
Agora que as operações de postagem de viagem estão configuradas, é hora de considerar as paradas, que estarão em uma subcoleção de uma postagem de viagem como esta: travels/
Esse recurso é quase idêntico à criação de uma postagem de viagem. Então, tente fazer a implementação por conta própria ou confira a implementação abaixo:
async addStop(travelId: string) {
...
const ref = await addDoc(collection(this.firestore, `travels/${travelId}/stops`), stopData)
setDoc(ref, {...stopData, id: ref.id})
}
Muito bem! As funções do Firestore foram implementadas no serviço Travel, então você pode vê-las em ação.
Como usar as funções do Firestore no app
Navegue até src/app/pages/my-travels/my-travels.component.ts
e injete TravelService
para usar as funções dele.
travelService = inject(TravelService);
travelsData$: Observable<Travel[]>;
stopsList$!: Observable<Stop[]>;
constructor() {
this.travelsData$ = this.travelService.getCollectionData(`travels`) as Observable<Travel[]>
}
TravelService
é chamado no construtor para receber uma matriz observável de todas as viagens.
No caso em que apenas as viagens do usuário atual são necessárias, use a função query
.
Outros métodos para garantir a segurança incluem a implementação de regras de segurança ou o uso do Cloud Functions com o Firestore, conforme explorado nas etapas opcionais abaixo.
Em seguida, basta chamar as funções implementadas em TravelService
.
async createTravel(userId: String) {
this.travelService.addEmptyTravel(userId);
}
deleteTravel(travelId: String) {
this.travelService.deleteData(`travels/${travelId}`)
}
Agora a página "Minhas viagens" deve funcionar. Confira o que acontece no emulador do Firestore quando você cria uma nova postagem de viagem.
Depois, repita essas etapas para as funções de atualização em /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. Como configurar o armazenamento
Agora você vai implementar o Storage para armazenar imagens e outros tipos de mídia.
O Cloud Firestore é melhor usado para armazenar dados estruturados, como objetos JSON. O Cloud Storage foi projetado para armazenar arquivos ou blobs. Neste app, você vai usar o app para permitir que os usuários compartilhem fotos de viagens.
Da mesma forma que acontece com o Firestore, armazenar e atualizar arquivos com o Storage requer um identificador exclusivo para cada arquivo.
Vamos implementar as funções em TraveService
:
Como enviar um arquivo
Navegue até src/app/services/travel.service.ts
e injete o armazenamento do AngularFire:
export class TravelService {
firestore: Firestore = inject(Firestore);
auth: Auth = inject(Auth);
storage: Storage = inject(Storage);
E implemente a função de upload:
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;
}
A principal diferença entre acessar documentos do Firestore e arquivos do Cloud Storage é que, embora ambos sigam caminhos estruturados de pasta, a combinação de URL de base e caminho é recebida pelo getDownloadURL
, que pode ser armazenado e usado em um arquivo .
Como usar a função no app
Navegue até src/app/components/edit-stop/edit-stop.component.ts
e chame a função de upload 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);
}
Quando a imagem é enviada, o próprio arquivo de mídia é enviado para o armazenamento, e o URL é armazenado no documento no Firestore.
9. Como implantar o aplicativo
Agora estamos prontos para implantar o aplicativo.
Copie as configurações firebase
de src/environments/environment.ts
para src/environments/environment.prod.ts
e execute:
firebase deploy
Você vai conferir algo parecido com:
✔ 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. Parabéns!
Agora seu aplicativo deve estar completo e implantado no Firebase Hosting. Agora todos os dados e análises podem ser acessados no console do Firebase.
Para mais recursos relacionados ao AngularFire, Functions, regras de segurança, confira as etapas opcionais abaixo e outros codelabs do Firebase.
11. Opcional: proteções de autenticação do AngularFire.
Além do Firebase Authentication, o AngularFire também oferece proteções baseadas em autenticação nas rotas para que usuários com acesso insuficiente possam ser redirecionados. Isso ajuda a proteger o app contra usuários que acessam dados protegidos.
No src/app/app-routing.module.ts
, importe
import {AuthGuard, redirectLoggedInTo, redirectUnauthorizedTo} from '@angular/fire/auth-guard'
Você pode definir funções de quando e para onde os usuários devem ser redirecionados em determinadas páginas:
const redirectUnauthorizedToLogin = () => redirectUnauthorizedTo(['signin']);
const redirectLoggedInToTravels = () => redirectLoggedInTo(['my-travels']);
Em seguida, basta adicioná-los aos seus trajetos:
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: regras de segurança
O Firestore e o Cloud Storage usam regras de segurança (firestore.rules
e security.rules
, respectivamente) para aplicar a segurança e validar os dados.
No momento, os dados do Firestore e do Storage têm acesso aberto para leituras e gravações, mas você não quer que as pessoas mudem as postagens de outras pessoas. Você pode usar regras de segurança para restringir o acesso a coleções e documentos.
Regras do Firestore
Para permitir que apenas usuários autenticados vejam as postagens de viagem, acesse o arquivo firestore.rules
e adicione:
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;
}
}
As regras de segurança também podem ser usadas para validar dados:
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;
}
}
Regras de armazenamento
Da mesma forma, podemos usar regras de segurança para aplicar o acesso a bancos de dados de armazenamento no storage.rules
. Também podemos usar funções para verificações mais complexas:
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;
}
}
}