Firebase Angular Web Frameworks Codelab

1. What you'll create

In this codelab, you'll be building a traveling blog with a real-time collaborative map with the latest from our Angular library: AngularFire . The final web app will consist of a travel blog where you can upload images to each location that you've traveled to.

AngularFire will be used to build the web app, Emulator Suite for local testing, Authentication to keep track of user data, Firestore and Storage to persist data and media, powered by Cloud Functions, and finally, Firebase Hosting to deploy the app.

What you'll learn

  • How to develop with Firebase products locally with Emulator Suite
  • How to enhance your web app with AngularFire
  • How to persist your data in Firestore
  • How to persist media in Storage
  • How to deploy your app to Firebase Hosting
  • How to use Cloud Functions to interact with your databases and APIs

What you'll need

  • Node.js version 10 or higher
  • A Google Account for the creation and management of your Firebase Project
  • The Firebase CLI version 11.14.2 or later
  • A browser of your choice, such as Chrome
  • Basic understanding of Angular and Javascript

2. Get the sample code

Clone the codelab's GitHub repository from the command line:

 git clone https://github.com/firebase/codelab-friendlychat-web 

Alternatively, if you do not have git installed, you can download the repository as a ZIP file .

The Github repository contains sample projects for multiple platforms.

This codelab only uses the webframework repository:

  • 📁 webframework: The starting code that you'll build upon during this codelab.

Install dependencies

After cloning, install dependencies in the root and functions folder before building the web app.

 cd webframework && npm install
cd functions && npm install 

Install Firebase CLI

Install Firebase CLI using this command in a terminal:

  npm 
  
 install 
  
 - 
 g 
  
 firebase 
 - 
 tools 
 

Double check that your Firebase CLI version is greater than 11.14.2 using:

 firebase  --version 

If your version is lower than 11.14.2, please update using:

  npm 
  
 update 
  
 firebase 
 - 
 tools 
 

3. Create and set up a Firebase project

Create a Firebase project

  1. Sign in to Firebase .
  2. In the Firebase console, click Add Project, and then name your Firebase project <your-project>. Remember the project ID for your Firebase project.
  3. Click Create Project.

Important: Your Firebase project will be named <your-project>, but Firebase will automatically assign it a unique Project ID in the form <your-project>-1234. This unique identifier is how your project is actually identified (including in the CLI), whereas <your-project> is simply a display name.

The application that we're going to build uses Firebase products that are available for web apps:

  • Firebase Authenticationto easily allow your users to sign into your app.
  • Cloud Firestoreto save structured data on the cloud and get instant notification when data changes.
  • Cloud Storage for Firebaseto save files in the cloud.
  • Firebase Hostingto host and serve your assets.
  • Functionsto interact with internal and external APIs.

Some of these products need special configurations or need to be enabled using the Firebase console.

Add a Firebase web app to the project

  1. Click the web icon to create a new Firebase web app.
  2. On the next step, you'll see a configuration object. Copy the contents of this object into the environments/environment.ts file.

To allow users to sign in to the web app with their Google accounts, we'll use the Googlesign-in method.

To enable Googlesign-in:

  1. In the Firebase console, locate the Buildsection in the left panel.
  2. Click Authentication, then click the Sign-in methodtab (or click here to go directly there).
  3. Enable the Googlesign-in provider, then click Save.
  4. Set the public-facing name of your app to <your-project-name>and choose a Project support emailfrom the dropdown menu.

Enable Cloud Firestore

  1. In the Firebase console's Buildsection, click Firestore Database.
  2. Click Create databasein the Cloud Firestore pane.
  3. Set the location where your Cloud Firestore data is stored. You can leave this as the default or choose a region close to you.

Enable Cloud Storage

The web app uses Cloud Storage for Firebase to store, upload, and share pictures.

  1. In the Firebase console's Buildsection, click Storage.
  2. If there's no Get Startedbutton, it means that Cloud storage is already

enabled, and you don't need to follow the steps below.

  1. Click Get Started.
  2. Read the disclaimer about security rules for your Firebase project, then click Next.
  3. The Cloud Storage location is preselected with the same region you chose for your Cloud Firestore database. Click Doneto complete the setup.

With the default security rules, any authenticated user can write anything to Cloud Storage. We'll make our storage more secure later in this codelab.

4. Connect to your Firebase project

The Firebase command-line interface (CLI) allows you to use Firebase Hosting to serve your web app locally, as well as to deploy your web app to your Firebase project.

Make sure that your command line is accessing your app's local webframework directory.

Connect the web app code to your Firebase project. First, log in to the Firebase CLI in command line:

 firebase login 

Next run the following command to create a project alias. Replace $YOUR_PROJECT_ID with the ID of your Firebase project.

 firebase  use  $YOUR_PROJECT_ID 

Add AngularFire

To add AngularFire to the app, run the command:

  ng 
  
 add 
  
 @angular 
 / 
 fire 
 

Then, follow the command line instructions, and select the features that exists in your Firebase project.

Initialize Firebase

To initialize the Firebase project, run:

 firebase init 

Then, following the command line prompts, select the features and emulators that were used in your Firebase project.

Start the emulators

From the webframework directory, run the following command to start the emulators:

 firebase  emulators:start 

Eventually you should see something like this:

  $ 
  
 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 
 . 
 

Once you see the ✔All emulators ready! message, the emulators are ready to use.

You should see your travel app's UI, which is not (yet!) functioning:

Now let's get building!

5. Connect the web app to the emulators

Based on the table in the emulator logs, Cloud Firestore emulator is listening on port 8080 and the Authentication emulator is listening on port 9099.

Open the EmulatorUI

In your web browser, navigate to http://127.0.0.1:4000/ . You should see the Emulator Suite UI.

Route the app to use the emulators

In src/app/app.module.ts , add the following code to AppModule 's list of imports:

  @ 
 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 
 ; 
  
 }), 
  
 ... 
  
 ] 
 

The app is now configured to use local emulators, allowing testing and development to be done locally.

6. Adding Authentication

Now that emulators are set up for the app, we can add Authentication features to ensure that each user is signed in before they post messages.

To do so, we can import signin functions directly from AngularFire, and track your user's auth state with the authState function. Modify the login page functions so that the page checks for user auth state on load.

Injecting AngularFire Auth

In src/app/pages/login-page/login-page.component.ts , import Auth from @angular/fire/auth , and inject it into the LoginPageComponent . Authentication providers, such as Google, and functions such as signin , signout can also be directly imported from the same package, and used in the 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 
 ); 
 }) 
 } 
 } 
 

Now the login page is functional! Try logging in, and check out the results in the Authentication Emulator.

7. Configuring Firestore

In this step, you'll add functionality to post and update travel blog posts stored in Firestore.

Similar to Authentication, Firestore functions come prepackaged from AngularFire. Each document belongs to a collection, and each document can also have nested collections. Knowing the path of the document in Firestore is required to create and update a travel blog post.

Implementing TravelService

Since many different pages will need to read and update Firestore documents in the web app, we can implement the functions in src/app/services/travel.service.ts , to refrain from repeatedly injecting the same AngularFire functions every page.

Begin with injecting Auth , similar to the previous step, as well as Firestore into our service. Defining an observable user$ object that listens to the current authentication status is also useful.

  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 
 ); 
 

Adding a travel post

Travel posts will exist as documents that are stored in Firestore, and since documents must exist within collections, the collection that contains all travel posts will be named travels . Thus, the path of any travel post will be travels/

Using the addDoc function from AngularFire, an object can be inserted into a collection:

 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;  
})
} 

Updating and deleting data

Given the uid of any travel post, one can deduce the path of the document stored in Firestore, which can then be read, updated or deleted using AngularFire's updateFoc and deleteDoc functions:

  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 
 ) 
 } 
 

Reading data as an observable

Since travel posts and stops along the way can be modified after creation, it would be more useful to get document objects as observables, to subscribe to any changes that are made. This functionality is offered by the docData and collectionData functions from @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[]>
} 

Adding stops to a travel post

Now that travel post operations are set up, it's time to consider stops, which will exist under a subcollection of a travel post like so: travels/ /stops/

This is almost identical to creating a travel post, so challenge yourself to implement it on your own, or check out the implementation below:

  async 
  
 addStop 
 ( 
 travelId 
 : 
  
 string 
 ) 
  
 { 
  
 ... 
  
 const 
  
 ref 
  
 = 
  
 await 
  
 addDoc 
 ( 
 collection 
 ( 
 this 
 . 
 firestore 
 , 
  
 ` 
 travels 
 /$ 
 { 
 travelId 
 } 
 / 
 stops 
 ` 
 ), 
  
 stopData 
 ) 
  
 setDoc 
 ( 
 ref 
 , 
  
 { 
 ... 
 stopData 
 , 
  
 id 
 : 
  
 ref 
 . 
 id 
 }) 
 } 
 

Nice! The Firestore functions have been implemented in the Travel service, so now you can see them in action.

Using Firestore functions in the app

Navigate to src/app/pages/my-travels/my-travels.component.ts and inject TravelService to use its functions.

  travelService 
  
 = 
  
 inject 
 ( 
 TravelService 
 ); 
 travelsData$ 
 : 
  
 Observable<Travel 
 [] 
> ; 
 stopsList$ 
 !: 
  
 Observable<Stop 
 [] 
> ; 
 constructor 
 () 
  
 { 
  
 this 
 . 
 travelsData$ 
  
 = 
  
 this 
 . 
 travelService 
 . 
 getCollectionData 
 ( 
 `travels` 
 ) 
  
 as 
  
 Observable<Travel 
 [] 
> } 
 

TravelService is called in the constructor to get an Observable array of all travels.

In the case where only the travels of the current user is needed, use the query function .

Other methods to ensure security include implementing security rules, or using Cloud Functions with Firestore as explored in optional steps below

Then, simply call the functions implemented in TravelService .

 async  
createTravel(userId:  
String)  
{  
this.travelService.addEmptyTravel(userId);
}

deleteTravel(travelId:  
String)  
{  
this.travelService.deleteData(`travels/ ${ 
 travelId 
 } 
`)
} 

Now the My Travels page should be functional! Check out what happens in your Firestore emulator when you create a new travel post.

Then, repeat for the update functions in /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}/s 
 tops 
 ` 
 ) 
  
 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. Configuring Storage

You'll now implement Storage to store images and other types of media.

Cloud Firestore is best used to store structured data, such as JSON objects. Cloud Storage is designed to store files or blobs. In this app, you will use it to allow users to share their travel pictures.

Likewise with Firestore, storing and updating files with Storage requires a unique identifier for each file.

Let's implement the functions in TraveService :

Uploading a file

Navigate to src/app/services/travel.service.ts and inject Storage from AngularFire:

  export 
  
 class 
  
 TravelService 
  
 { 
 firestore 
 : 
  
 Firestore 
  
 = 
  
 inject 
 ( 
 Firestore 
 ); 
 auth 
 : 
  
 Auth 
  
 = 
  
 inject 
 ( 
 Auth 
 ); 
 storage 
 : 
  
 Storage 
  
 = 
  
 inject 
 ( 
 Storage 
 ); 
 

And implement the upload function:

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

The primary difference between accessing documents from Firestore and files from Cloud Storage is that, although they both follow folder structured paths, the base url and path combination is obtained through the getDownloadURL , which can then be stored, and used in a file.

Using the function in app

Navigate to src/app/components/edit-stop/edit-stop.component.ts and call the upload function using:

   
 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 
 ); 
 } 
 

When the image is uploaded, the media file itself will be uploaded to storage, and the url stored accordingly in the document in Firestore.

9. Deploying the application

Now we're ready to deploy the application!

Copy the firebase configs from src/environments/environment.ts to src/environments/environment.prod.ts and run:

 firebase deploy 

You should see something like this:

   
  
 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. Congratulations!

Now your application should be complete and deployed to Firebase Hosting! All data and analytics will now be accessible in your Firebase Console.

For more features regarding AngularFire, Functions, security rules, don't forget to check out optional steps below, as well as other Firebase Codelabs !

11. Optional: AngularFire auth guards

Along with Firebase Authentication, AngularFire also offers authentication based guards on routes, so that users with insufficient access can be redirected. This helps protect the app from users accessing protected data.

In src/app/app-routing.module.ts , import

  import 
  
 { 
 AuthGuard 
 , 
 redirectLoggedInTo 
 , 
 redirectUnauthorizedTo 
 } 
 from 
  
 '@angular/fire/auth-guard' 
 

You can then define functions as to when, and where users should be redirected to on certain pages:

  const 
  
 redirectUnauthorizedToLogin 
  
 = 
  
 () 
  
 = 
>  
 redirectUnauthorizedTo 
 ([ 
 'signin' 
 ]); 
 const 
  
 redirectLoggedInToTravels 
  
 = 
  
 () 
  
 = 
>  
 redirectLoggedInTo 
 ([ 
 'my-travels' 
 ]); 
 

Then simply add them to your routes:

  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. Optional: security rules

Both Firestore and Cloud Storage use security rules ( firestore.rules and security.rules respectively) to enforce security and to validate data.

At the moment, the Firestore and Storage data has open access for reads and writes, but you don't want people to go about changing others' posts! You can use security rules to restrict access to your collections and documents.

Firestore rules

To only allow authenticated users to view travel posts, go to firestore.rules file and add:

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

Security rules can also be used to validate data:

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

Storage rules

Similarly, we can use security rules to enforce access to storage databases in storage.rules . Note that we can also use functions for more complex checks:

  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 
 ; 
  
 } 
  
 } 
 } 
 
Design a Mobile Site
View Site in Mobile | Classic
Share by: