1. O que você criará
Neste codelab, você criará um blog de viagem com um mapa colaborativo em tempo real com as novidades da nossa biblioteca Angular: AngularFire . O aplicativo web final consistirá em um blog de viagens onde você poderá fazer upload de imagens de cada local para onde viajou.
AngularFire será usado para construir o aplicativo da web, Emulator Suite para testes locais, Autenticação para rastrear os dados do usuário, Firestore e Storage para persistir dados e mídia, alimentado por Cloud Functions e, finalmente, Firebase Hosting para implantar o aplicativo.
O que você aprenderá
- Como desenvolver produtos Firebase localmente com o Emulator Suite
- Como aprimorar seu aplicativo web com AngularFire
- Como persistir seus dados no Firestore
- Como persistir mídia no armazenamento
- Como implantar seu aplicativo no Firebase Hosting
- Como usar o Cloud Functions para interagir com seus bancos de dados e APIs
O que você precisará
- Node.js versão 10 ou superior
- Uma Conta Google para a criação e gerenciamento do seu projeto Firebase
- A CLI do Firebase versão 11.14.2 ou posterior
- Um navegador de sua preferência, como o Chrome
- Compreensão básica de Angular e Javascript
2. Obtenha o código de exemplo
Clone o repositório GitHub do codelab na linha de comando:
git clone https://github.com/firebase/codelab-friendlychat-web
Alternativamente, se você não tiver o git instalado, poderá baixar o repositório como um arquivo ZIP .
O repositório Github contém exemplos de projetos para múltiplas plataformas.
Este codelab usa apenas o repositório do webframework:
- 📁 webframework : o código inicial que você usará durante este codelab.
Instalar dependências
Após a clonagem, instale dependências na pasta raiz e functions
antes de construir o aplicativo web.
cd webframework && npm install
cd functions && npm install
Instale a CLI do Firebase
Instale o Firebase CLI usando este comando em um terminal:
npm install -g firebase-tools
Verifique novamente se sua versão do Firebase CLI é superior a 11.14.2 usando:
firebase --version
Se sua versão for inferior a 11.14.2, atualize usando:
npm update firebase-tools
3. Crie e configure um projeto Firebase
Crie um projeto do Firebase
- Faça login no Firebase .
- No console do Firebase, clique em Adicionar projeto e nomeie seu projeto do Firebase como <seu-projeto> . Lembre-se do ID do seu projeto do Firebase.
- Clique em Criar projeto .
Importante : seu projeto do Firebase será nomeado <seu-projeto> , mas o Firebase atribuirá a ele automaticamente um ID de projeto exclusivo no formato <seu-projeto>-1234 . Este 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 Firebase disponíveis para aplicativos da web:
- Firebase Authentication para permitir que seus usuários façam login facilmente em seu aplicativo.
- Cloud Firestore para salvar dados estruturados na nuvem e receber notificações instantâneas quando os dados forem alterados.
- Cloud Storage para Firebase para salvar arquivos na nuvem.
- Firebase Hosting para hospedar e servir seus ativos.
- Funções para interagir com APIs internas e externas.
Alguns desses produtos precisam de configurações especiais ou precisam ser habilitados usando o console do Firebase.
Adicione um aplicativo da Web do Firebase ao projeto
- Clique no ícone da web para criar um novo aplicativo da web do Firebase.
- Na próxima etapa, você verá um objeto de configuração. Copie o conteúdo deste objeto no arquivo
environments/environment.ts
.
Ative o login do Google para Firebase Authentication
Para permitir que os usuários façam login no aplicativo da web com suas 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, em seguida, clique na guia Método de login (ou clique aqui para ir diretamente para lá).
- Ative o provedor de login do Google e clique em Salvar .
- Defina o nome público do seu aplicativo como <nome do seu projeto> 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 Cloud Firestore.
- Defina o local onde seus dados do Cloud Firestore são armazenados. Você pode deixar isso como padrão ou escolher uma região mais próxima de você.
Habilitar armazenamento em nuvem
O aplicativo da web usa Cloud Storage for Firebase para armazenar, fazer upload e compartilhar imagens.
- Na seção Build do console do Firebase, clique em Storage .
- Se não houver um botão Começar , significa que o armazenamento em nuvem já está
ativado e você não precisa seguir as etapas abaixo.
- Clique em Começar .
- Leia a isenção de responsabilidade sobre as regras de segurança do seu projeto do Firebase e clique em Avançar .
- O local do Cloud Storage é pré-selecionado com a mesma região escolhida para seu banco de dados do Cloud Firestore. Clique em Concluído para concluir a configuração.
Com as regras de segurança padrão, qualquer usuário autenticado pode gravar qualquer coisa no Cloud Storage. Tornaremos nosso armazenamento mais seguro posteriormente neste codelab.
4. Conecte-se ao seu projeto Firebase
A interface de linha de comando (CLI) do Firebase permite que você use o Firebase Hosting para servir seu aplicativo da Web localmente, bem como para implantar seu aplicativo da Web em seu projeto do Firebase.
Certifique-se de que sua linha de comando esteja acessando o diretório webframework
local do seu aplicativo.
Conecte o código do aplicativo 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 Angular Fire
Para adicionar AngularFire ao aplicativo, execute o comando:
ng add @angular/fire
Em seguida, siga as instruções da linha de comando e selecione os recursos existentes em seu projeto Firebase.
Inicializar Firebase
Para inicializar o projeto 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.
Inicie os emuladores
No diretório webframework
, execute o seguinte comando para iniciar os emuladores:
firebase emulators:start
Eventualmente você deverá ver 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 de ver os ✔All emulators ready!
mensagem, os emuladores estão prontos para uso.
Você deverá ver a IU do seu aplicativo de viagens, que (ainda!) não está funcionando:
Agora vamos construir!
5. Conecte o aplicativo web aos emuladores
Com base na tabela nos registros do emulador, o emulador do Cloud Firestore está escutando na porta 8080 e o emulador do Authentication está escutando na porta 9099.
Abra o emuladorUI
No seu navegador da web, navegue até http://127.0.0.1:4000/ . Você deverá ver a IU do Emulator Suite.
Roteie o aplicativo para usar os emuladores
Em src/app/app.module.ts
, adicione o seguinte código à lista de importações do 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;
}),
...
]
O aplicativo agora está configurado para usar emuladores locais, permitindo que testes e desenvolvimento sejam feitos localmente.
6. Adicionando autenticação
Agora que os emuladores estão configurados para o aplicativo, podemos adicionar recursos de autenticação para garantir que cada usuário esteja conectado antes de postar mensagens.
Para fazer isso, podemos importar funções signin
diretamente do AngularFire e rastrear o estado de autenticação do seu usuário com a função authState
. Modifique as funções da página de login para que a página verifique o estado de autenticação do usuário durante o carregamento.
Injetando autenticação AngularFire
Em src/app/pages/login-page/login-page.component.ts
, importe Auth
de @angular/fire/auth
e injete-o no LoginPageComponent
. Provedores de autenticação, como Google, e funções como signin
e signout
também podem ser importados diretamente do mesmo pacote e usados no aplicativo.
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);
})
}
}
Agora a página de login está funcional! Tente fazer login e confira os resultados no emulador de autenticação.
7. Configurando o Firestore
Nesta etapa, você adicionará funcionalidade para postar e atualizar postagens de blogs de viagens armazenadas no Firestore.
Semelhante ao 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 conhecer o path
do documento no Firestore para criar e atualizar uma postagem no blog de viagens.
Implementando TravelService
Como muitas páginas diferentes precisarão ler e atualizar documentos do Firestore no aplicativo da web, podemos implementar as funções em src/app/services/travel.service.ts
, para evitar injetar repetidamente as mesmas funções do AngularFire em todas as páginas.
Comece injetando Auth
, semelhante à etapa anterior, bem como Firestore
em nosso serviço. Definir um objeto user$
observável que ouça o status de autenticação atual 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);
Adicionando uma postagem de viagem
As postagens de viagem existirão como documentos armazenados no Firestore e, como os documentos devem existir nas coleções, a coleção que contém todas as postagens de viagem será chamada de travels
. Assim, o caminho de qualquer posto 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;
})
}
Atualizando e excluindo dados
Dado o uid de qualquer postagem de viagem, pode-se deduzir o caminho do documento armazenado no Firestore, que pode então 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)
}
Lendo dados como um 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 obter objetos de documento como observáveis, para assinar quaisquer alterações feitas. Esta funcionalidade é oferecida pelas funções docData
e 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[]>
}
Adicionando paradas a uma postagem de viagem
Agora que as operações do posto de viagem estão configuradas, é hora de considerar as paradas, que existirão em uma subcoleção de um posto de viagem como: travels/ /stops/
travels/ /stops/
Isso é quase idêntico à criação de um post de viagem, então desafie-se a implementá-lo sozinho 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})
}
Legal! As funções do Firestore foram implementadas no serviço Travel, agora você pode vê-las em ação.
Usando funções do Firestore no aplicativo
Navegue para src/app/pages/my-travels/my-travels.component.ts
e injete TravelService
para usar suas funções.
travelService = inject(TravelService);
travelsData$: Observable<Travel[]>;
stopsList$!: Observable<Stop[]>;
constructor() {
this.travelsData$ = this.travelService.getCollectionData(`travels`) as Observable<Travel[]>
}
TravelService
é chamado no construtor para obter uma matriz observável de todas as viagens.
No caso em que são necessárias apenas as viagens do usuário atual, utilize 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 Firestore, conforme explorado nas etapas opcionais abaixo.
Depois, basta chamar as funções implementadas no TravelService
.
async createTravel(userId: String) {
this.travelService.addEmptyTravel(userId);
}
deleteTravel(travelId: String) {
this.travelService.deleteData(`travels/${travelId}`)
}
Agora a página Minhas Viagens deve estar funcional! Confira o que acontece no emulador do Firestore quando você cria uma nova postagem de viagem.
Em seguida, repita 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. Configurando armazenamento
Agora você 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 aplicativo, você o usará para permitir que os usuários compartilhem suas fotos de viagens.
Da mesma forma que no Firestore, armazenar e atualizar arquivos com o Storage requer um identificador exclusivo para cada arquivo.
Vamos implementar as funções em TraveService
:
Fazendo upload de um arquivo
Navegue para src/app/services/travel.service.ts
e injete 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 pastas, o URL base e a combinação do caminho são obtidos por meio do getDownloadURL
, que pode então ser armazenado e usado em um arquivo.
Usando a função no aplicativo
Navegue para 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 for carregada, o próprio arquivo de mídia será carregado para armazenamento e o URL será armazenado de acordo no documento no Firestore.
9. Implantando 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ê deverá ver algo assim:
✔ 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! Todos os dados e análises agora estarão acessíveis em seu Firebase Console.
Para mais recursos sobre AngularFire, Funções, regras de segurança, não se esqueça de conferir as etapas opcionais abaixo, bem como outros Codelabs do Firebase !
11. Opcional: protetores de autenticação AngularFire
Junto com o 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 aplicativo contra usuários que acessam dados protegidos.
Em src/app/app-routing.module.ts
, importe
import {AuthGuard, redirectLoggedInTo, redirectUnauthorizedTo} from '@angular/fire/auth-guard'
Você pode então definir funções sobre 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 às suas rotas:
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
Tanto o Firestore quanto o Cloud Storage usam regras de segurança ( firestore.rules
e security.rules
respectivamente) para reforçar a segurança e validar 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 dos outros! Você pode usar regras de segurança para restringir o acesso às suas coleções e documentos.
Regras do Firestore
Para permitir que apenas usuários autenticados visualizem postagens de viagens, 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 impor acesso a bancos de dados de armazenamento em storage.rules
. Observe que 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;
}
}
}