Google Firebase is a handy set of cloud-based tools designed to assist developers in creating and deploying their apps. It's especially beneficial for frontend developers because it offers database features that don't require a backend.
If you happen to be using Angular, you might find these Firebase features particularly useful for adding persistence to your project and making it more comprehensive. In this article, we'll explore how to build a CRUD (Create, Read, Update, Delete) application, including file uploads, using Angular and Firebase.
While alternatives like json-server
exist, they don't provide the same capabilities as Firebase.
Choosing between json-server
and Firebase
Before diving into Firebase, consider your project's scope and objectives. Is it merely a practice project, or do you plan to deploy it in the future?
Keep in mind that json-server
is not intended for production use, so if deployment is in your plans, Firebase is the way to go. Additionally, if your app requires file uploads, Firebase offers a suitable solution.
For quick demos, homework assignments, practice apps, and experiments, json-server
might be a suitable initial choice. However, for more serious applications like capstone projects, theses, or portfolio pieces, Firebase is a better fit.
Getting Started with Firebase
If Firebase seems a bit daunting, don't worry; I'm here to help! 😊
First, if you're using Angular, remember not to use the npm package firebase
but rather the Angular wrapper called @angular/fire
.
Before any coding, let's create a Firebase account. Firebase is operated by Google, so using your Google account is perfectly fine. Visit this website or search for 'Firebase' on Google, and log in. Create a new project and give it a name.
The website may ask if you want to enable Google Analytics. If you don't need it, you can disable it. Google will take a few seconds to create your project, so you can take a short break (go drink some water!).
Next, you'll be prompted to create an app. Since we're working with Angular, select 'Web' (the > icon), give it a name, and register it. You'll then be asked to install Firebase and initialize the app, but you can skip that for now. Just click on 'go to the console'.
Great! Your Firebase account is set up, but we're not ready to code just yet. If you click on 'See all Build features', you'll find numerous possibilities for your application.
Now, which ones should we choose? It depends on your project's needs. 😊 But for this tutorial, we'll need 'Storage' and 'Cloud Firestore'.
Storage vs. Firestore
You might be wondering, why choose these? What do they do?
Firestore is a NoSQL database that uses 'documents' instead of tables, similar to JSON format. It provides real-time updates. Firestore is ideal when you plan to have multiple collections, such as posts and users, or users and articles.
On the other hand, Firebase Storage is a file storage system. When you upload a file to Firebase Storage, you receive a corresponding URL. It's similar to Cloudinary but offers different capabilities and supports more formats.
To enable Firestore and Storage:
- Click on the desired service.
- Click 'create database' or 'get started'.
- Choose to start in test mode.
- Select the appropriate location (the one closest to you).
Now that we've determined our library version, let's dive into coding.
Assuming you already have some Angular and TypeScript knowledge, I won't delve too deeply into concepts like Observables, Services, and Components.
Setting Up
Starting from your new, empty Angular application, install @angular/fire
using npm, pnpm, or your preferred package manager. Ensure you don't install the firebase
library, as it can conflict with @angular/fire
.
Begin by initializing Firebase in your app.module.ts
:
import { provideFirebaseApp, initializeApp } from "@angular/fire/app"
import { provideStorage, getStorage } from "@angular/fire/storage"
import { provideFirestore, getFirestore } from "@angular/fire/firestore"
Make sure you import these from the correct folders.
In your imports array, call the provideFirebaseApp
function, passing an arrow function that returns initializeApp
. This function takes your Firebase configuration as a parameter.
You can find your Firebase configuration in the sidebar of your Firebase dashboard, under 'project settings'. It includes details like the project ID, app ID, storage bucket, API key, and more.
Create an environment file if it wasn't generated automatically ( ng generate environments
), and store your Firebase configuration there.
export const environment = {
production: false,
firebase: {
projectId: [REDACTED],
appId: [REDACTED],
storageBucket: [REDACTED],
apiKey: [REDACTED],
authDomain: [REDACTED],
messagingSenderId: [REDACTED],
},
};
Replace [REDACTED]
with your actual Firebase credentials.
You might be wondering if these information should be stored in a secure space, the answer is yes. As for all apiKey and/or any kind of data that gives access to your db, it should be hidden in a .env file. If you're concerned about keeping this data secure, consider reading this article on hiding environment variables in your Angular project.
Once you've set up your environment, use the configuration as a parameter for initializeApp
:
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule,
provideFirebaseApp(() => initializeApp(environment.firebase))
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Now, let's initialize both Storage and Firestore using the functions we imported earlier, following the same pattern we used for the app:
@NgModule({
declarations: [
AppComponent,
],
imports: [
BrowserModule,
AppRoutingModule,
provideFirebaseApp(() => initializeApp(environment.firebase)),
provideStorage(() => getStorage()),
provideFirestore(() => getFirestore()),
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
With everything set up and running, let's move on to creating our CRUD functionality.
Implementing CRUD Operations
We're finally ready to build our CRUD application, which will allow us to show, post, edit, and delete text/image posts.
For this, I've created a service called post.service.ts
, where we'll define all our methods.
First and foremost, we need to inject our Firestore and Storage services into our service. We can achieve this by adding them to the constructor:
import { Firestore } from '@angular/fire/firestore';
import { Storage } from '@angular/fire/storage';
Ensure that you keep these imports correct—everything we use to work with Firestore should come from '@angular/fire/firestore', and the same applies to Storage.
Here's what your constructor should look like:
constructor(private db: Firestore, private fileStorage: Storage) { }
Feel free to choose more meaningful names for db
and fileStorage
based on your project's context.
Now, let's dive into implementing each CRUD operation:
1. GET All Posts
To retrieve data from Firestore, you'll need a collection and at least one document. Head back to the Firebase dashboard and click on 'Firestore Database' in the sidebar. You'll be prompted with an empty database and a 'Start Collection' button.
Upon clicking, you'll be asked to add a collection name. After that, create a new document, which will be your first entry in the collection. You'll need to specify the field name, data type, and value for each property. For example, you can have properties for text and image.
Now, let's write our first GET operation towards Firebase. To fetch data from a collection, we'll use the collectionData
function, which can be imported from @angular/fire/firestore
. This function requires two parameters: the CollectionReference
and options.
Here's how to get the CollectionReference
using the collection
function and retrieve data as an Observable:
getPosts() {
const dbCollection = collection(this.db, "/posts");
collectionData(dbCollection, { idField: 'id' }).subscribe(res => {
console.log(res);
/*
[{
text: "the text we entered earlier",
url: "the url we entered earlier"
}]
*/
});
}
Don't forget to subscribe to the Observable, and you can convert the data to your preferred format if needed. In this example, we're using an IPost
interface to represent the data structure.
interface IPost {
text: string
url: string | File
id: string
}
2. POST a New Resource
Now, let's move on to posting new resources, which involves uploading files to Firebase Storage and generating URLs for them.
If you're using a form to collect data, remember not to use ngModel
on file inputs.
The reason for this change is that ngModel
takes the value
of an input, while for file inputs we want to save files
.
Instead, add an onChange
event listener to handle file selection:
<form (submit)="submitPost()">
<input type="text" name="text" [(ngModel)]="text">
<input type="file" name="img" (change)="handleFile($event)">
</form>
In your TypeScript code, implement the handleFile
function to capture the selected file:
handleFile(ev: Event) {
this.img = (ev.target as HTMLInputElement).files![0];
}
With your form ready, on form submission, you'll send the text and file to your service.
For file uploads to Firebase Storage, use the uploadBytes
function from @angular/fire/storage
. Here's the process:
-
Get the
StorageReference
using theref
function with your Storage instance and a desired filename. -
Use
uploadBytes
to upload the file data, which returns a Promise. -
After successful upload, call
getDownloadURL
to obtain the URL for the uploaded file.
addPost(newPost: Partial<IPost>) {
const fileStorageRef = ref(this.fileStorage, (newPost.url as File).name);
uploadBytes(fileStorageRef, newPost.url as File).then(res => {
return getDownloadURL(fileStorageRef);
}).then(url => {
const dbCollection = collection(this.db, "/posts");
addDoc(dbCollection, { text: newPost.text, url });
});
}
3. PUT (Update) a Post
For updating a post, you'll need to reference the specific document you want to edit. Use the doc
function from @angular/fire/firestore
to get the DocumentReference
. Then, use the updateDoc
function to modify the document.
Here's how to do it:
editPost(id: string, newText: string) {
const docRef = doc(this.db, `/posts/${id}`);
updateDoc(docRef, { text: newText });
}
4. DELETE a Post
To delete a post, follow a similar process as updating. Get the DocumentReference
using the doc
function and use the deleteDoc
function to remove the document.
Here's the removePost
method:
removePost(id: string) {
const docRef = doc(this.db, `/posts/${id}`);
deleteDoc(docRef);
}
That's it! You've now implemented the CRUD operations for your Angular and Firebase application.
Feel free to adapt and extend these methods as needed for your specific project, and remember to handle errors and edge cases to ensure a robust application.
Here you can find the complete GitHub repository.
Happy coding! 😊