From 4801f26f88c1aaa792e469a8d513da6262c41a83 Mon Sep 17 00:00:00 2001 From: Kumar Saptam Date: Fri, 11 Mar 2022 11:12:23 +0530 Subject: [PATCH 1/2] Added docs for firebase authentication and firestore Modular V9 # Added docs for modular firebase authentication, modular firestore documents, and collections. Added a simple and full-fledged authentication documentation with examples and best practices. ## Added configuration methods and properties for firebase auth. 1. Tenant Id 2. Language Code 3. Auth Persistence 4. Set Local Language ## Added complete docs for accessing and using collections in firebase firestore with full examples 1. docs single time 2. Real-time collection changes 3. Only changes observable 4. Perform simple and compound queries 6. Paginate data 7. Order and limit data ## Features list of document querying 1. Create a document 2. Update a document 3. Delete a document 4. Observer a document for changes 5. Inline querying like * Incrementing field value * Adding data in an array * Removing data from an array 6. Offline data persistence ### Description This pull request includes documentation for authentication and firestore collections. --- docs/auth.md | 420 +++++++++++++++++++++++++++++++++++--- docs/firestore.md | 503 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 888 insertions(+), 35 deletions(-) diff --git a/docs/auth.md b/docs/auth.md index 71f42fb90..240c73cc9 100644 --- a/docs/auth.md +++ b/docs/auth.md @@ -13,56 +13,424 @@ Firebase Authentication integrates tightly with other Firebase services, and it [Learn more about Firebase Authentication](https://firebase.google.com/docs/auth) -## Dependency Injection +# 5. Getting started with Firebase Authentication -AngularFire allows you to work with Firebase Auth via Angular's Dependency Injection. +#### This documentation works for Modular Firebase V9+ -First provide an auth instance to AngularFire: +First initialize firebase authentication in `app.module.ts` + +```typescript +@NgModule({ + declarations: [AppComponent], + imports: [provideAuth(() => getAuth())], + providers: [ScreenTrackingService, UserTrackingService], + bootstrap: [AppComponent], +}) +export class AppModule {} +``` + +#### A basic authentication demo code. + +```typescript +import { Component } from "@angular/core"; +import { + Auth, + authState, + GoogleAuthProvider, + signInWithPopup, + signOut, + UserCredential, +} from "@angular/fire/auth"; +import { map } from "rxjs"; +@Component({ + selector: "app-root", + template: ` +
+

Hello {{ user.displayName }}!

+ +
+ +
+

Please login.

+ +
+ `, +}) +export class AppComponent { + title = "AngularFireDevelopment"; + user: any; + public loggedIn: boolean = false; + constructor(private auth: Auth) { + authState(this.auth) + .pipe(map((u) => u)) + .subscribe((userData) => { + this.user = userData; + this.loggedIn = !!userData; + }); + } + login() { + signInWithPopup(this.auth, new GoogleAuthProvider()).then( + async (credentials: UserCredential) => { + this.user = credentials.user; + } + ); + } + logout() { + return signOut(this.auth); + } +} +``` + +It is a good practice to keep your local code away from component code. So let's create a service file inside our + +`ng generate service services/authentication/authentication` + +It should create a file like this. This file is known as service. +[More on services here.](https://angular.io/tutorial/toh-pt4) + +![Directory Strucutre](AuthServiceDireStructure.png) + +Now add these functions in your services + +This is for logging in with google. + +```typescript +googleLogin(){ + signInWithPopup(this.auth, new GoogleAuthProvider()).then( + async (credentials: UserCredential) => { + this.user = credentials.user; + } + ); + } +``` + +This is for logging in with email and password it takes two arguments email and password. + +```typescript +emailLogin(email:string,password:string){ + return signInWithEmailAndPassword(this.auth,email,password) + } +``` + +This is for creating account with an email and password + +#### Please verify and check for all password requirements using form validation firebase does not qualifications for secure password and valid emails. + +```typescript +emailSignup(email:string,password:string){ + return createUserWithEmailAndPassword(this.auth,email,password) + } +``` + +This method logs you out of any or all account for current user instance + +```typescript +logout() { + return signOut(this.auth); + } +``` + +This methods signs you in anonymously. + +```typescript +anonymousSignIn(){ + return signInAnonymously(this.auth); + } +``` + +#### The final service file + +`authentication.service.ts` + +```typescript +import { Injectable } from "@angular/core"; +import { + Auth, + GoogleAuthProvider, + signInWithPopup, + signOut, + UserCredential, +} from "@angular/fire/auth"; +import { + createUserWithEmailAndPassword, + signInAnonymously, + signInWithEmailAndPassword, +} from "firebase/auth"; + +@Injectable({ + providedIn: "root", +}) +export class AuthenticationService { + user: any; + constructor(private auth: Auth) {} + googleLogin() { + signInWithPopup(this.auth, new GoogleAuthProvider()).then( + async (credentials: UserCredential) => { + this.user = credentials.user; + } + ); + } + emailLogin(email: string, password: string) { + return signInWithEmailAndPassword(this.auth, email, password); + } + emailSignup(email: string, password: string) { + return createUserWithEmailAndPassword(this.auth, email, password); + } + logout() { + return signOut(this.auth); + } + anonymousSignIn() { + return signInAnonymously(this.auth); + } +} +``` + +Now in your `app.component.ts` add this code to immport our `AuthenticationService` file we just created. + +`app.component.ts` + +```typescript +import { Component } from "@angular/core"; +import { + FormControl, + FormControlName, + FormGroup, + Validators, +} from "@angular/forms"; +import { AuthenticationService } from "./services/authentication/authentication.service"; +@Component({ + selector: "app-root", + templateUrl: "./app.component.html", + styleUrls: ["./app.component.css"], +}) +export class AppComponent { + title = "AngularFireDevelopment"; + constructor(public authService: AuthenticationService) {} + signIn: FormGroup = new FormGroup({ + email: new FormControl("", [Validators.required]), + password: new FormControl("", [Validators.required]), + }); + signUp: FormGroup = new FormGroup({ + email: new FormControl("", [Validators.required]), + password: new FormControl("", [Validators.required]), + }); + login() { + if (this.signIn.valid) { + this.authService + .emailLogin(this.signIn.value.email, this.signIn.value.password) + .then(() => alert("Signed In")) + .catch((error) => alert("Invalid Sign In" + error.toString())); + } else { + alert("Invalid Login Form"); + } + } + signup() { + if (this.signUp.valid) { + this.authService + .emailSignup(this.signUp.value.email, this.signUp.value.password) + .then(() => alert("Signed up")) + .catch((error: any) => alert("Invalid Sign Up" + error.toString())); + } else { + alert("Invalid Signup Form"); + } + } +} +``` + +Now add `FormsModule` and `ReactiveFormsModule` to your app module imports. + +```typescript +imports: [FormsModule, ReactiveFormsModule]; +``` + +And your `app.component.html` file which will have only UI code but will be able to access the authentication service using Injection + +`app.component.html` + +```html +
+

Hello {{ authService.user.displayName }}!

+ +
+ +
+

Please login.

+ +
+
+ + + +
+
+
+
+ + + +
+
+
+``` + +### Now you just learned how to do authentication with firebase. + +--- + +[Google Docs Reference](https://firebase.google.com/docs/auth/web/auth-state-persistence) +# Authentication state persistance +Authentication State Persistence + +You can specify how the Authentication state persists when using the Angular Fire SDK. This includes the ability to specify whether a signed in user should be indefinitely persisted until explicit sign out, cleared when the window is closed or cleared on page reload. + +For a web application, the default behavior is to persist a user's session even after the user closes the browser. This is convenient as the user is not required to continuously sign-in every time the web page is visited on the same device. This could require the user having to re-enter their password, send an SMS verification, etc, which could add a lot of friction to the user experience. + +However, there are cases where this behavior may not be ideal: + +* Applications with sensitive data may want to clear the state when the window or tab is closed. This is important in case the user forgets to sign out. +* Applications that are used on a device shared by multiple users. A common example here is an app running in a library computer. + +* An application on a shared device that might be accessed by multiple users. The developer is unable to tell how that application is accessed and may want to provide a user with the ability to choose whether to persist their session or not. This could be done by adding a "Remember me" option during sign-in. + +* In some situations, a developer may want to not persist an anonymous user until that user is upgraded to a non-anonymous account (federated, password, phone, etc.). + +* A developer may want to allow different users to sign in to an application on different tabs. The default behavior is to persist the state across tabs for the same origin. + +As stated above, there are multiple situations where the default permanent persistence may need to be overridden. + +>Note: Do not confuse Auth state persistence with Firestore offline data persistence. Auth state persistence specifies how a user session is persisted on a device. Whereas Firestore enablePersistence enables Cloud Firestore data caching when the device is offline. + +# Overview of persistence behavior +You can specify or modify the existing type of persistence by calling the `setPersistence()` method. +The two parameters of this functions are: +1. authInstance: Instance of the initliazed auth by using `getAuth()` method. +2. persistance: A valid persistance type imported from auth module. + +##### Types of Persistence. +* 'browserSessionPersistence' is used for persistence such as `sessionStorage`. +* 'indexedDBLocalPersistence' or 'browserLocalPersistence' used for long term persistence such as `localStorage` `IndexedDB`. +* 'inMemoryPersistence' is used for in-memory, or persistence. + + +### The following criteria will be applied when determining the current state of persistence. +Initially, the SDK will check if an authenticated user exists. Unless setPersistence is called, that user's current persistence type will be applied for future sign-in attempts. So if that user was persisted in session on a previous web page and a new page was visited, signing in again with a different user will result in that user's state being also saved with session persistence. + +If no user is signed in and no persistence is specified, the default setting will be applied (local in a browser app). +If no user is signed in and a new type of persistence is set, any future sign-in attempt will use that type of persistence. +If the user is signed in and persistence type is modified, that existing signed in user will change persistence to the new one. All future sign-in attempts will use that new persistence. + +When signInWithRedirect is called, the current persistence type is retained and applied at the end of the OAuth flow to the newly signed in user, even if the persistence was none. If the persistence is explicitly specified on that page, it will override the retained auth state persistence from the previous page that started the redirect flow. + +Add this in `app.module.ts` imports array. + +```typescript +imports: [ + provideAuth(() => { + const auth = getAuth(); + setPersistence(auth, inMemoryPersistence); + // setPersistence(auth, inMemoryPersistence); + // setPersistence(auth,browserLocalPersistence) + // setPersistence(auth, indexedDBLocalPersistence); + // setPersistence(auth, browserSessionPersistence); + return auth; + }), +]; +``` + +## Configuration with Dependency Injection and Inbuilt Functions +The AngularFireAuth Module provides several from which you can configure the authentication process. + + +### Use Current Browser Language + +Using the `useDeviceLanguage()` method that allow you to set the current language to the default device/browser +preference. This allows to localize emails but be aware that this only applies +if you use the standard template provided by Firebase. ```ts -import { provideFirebaseApp, initializeApp } from '@angular/fire/app'; -import { getAuth, provideAuth } from '@angular/fire/auth'; +import { useDeviceLanguage } from "@angular/fire/auth"; @NgModule({ - imports: [ - provideFirebaseApp(() => initializeApp(environment.firebase)), - provideAuth(() => getAuth()), - ] + provideAuth(()=>{ + const auth = getAuth(); + useDeviceLanguage(auth); + return auth; + }), }) +export class AppModule {} ``` -Next inject it into your component: +If you want to set a different language, you can use `languageCode` property of Auth instance. + +More info at the [firebase auth docs](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#languagecode). ```ts -import { Auth } from '@angular/fire/auth'; -constructor(database: Auth) { -} +@NgModule({ + provideAuth(()=>{ + const auth = getAuth(); + auth.languageCode = 'fr'; + return auth; + }), +}) +export class AppModule {} ``` +### Tenant + +If you need to use multi-tenancy, you can set the current Auth instance's tenant +ID using `tenantId` property from Auth instance. + +More tutorials regarding this topic are _coming soon_. -## Firebase API +```ts -AngularFire wraps the Firebase JS SDK to ensure proper functionality in Angular, while providing the same API. +@NgModule({ + provideAuth(()=>{ + const auth = getAuth(); + auth.tenantId = "tenant-id-app-one" + return auth; + }), +}) +export class AppModule {} +``` -Just change your imports from `import { ... } from 'firebase/auth'` to `import { ... } from '@angular/fire/auth'` and follow the offical documentation. +- [Multi-Tenancy Authentication](https://cloud.google.com/identity-platform/docs/multi-tenancy-authentication) +- [Firebase Auth Tenant](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#tenantid) -[Getting Started](https://firebase.google.com/docs/auth/web/start) | [API Reference](https://firebase.google.com/docs/reference/js/auth) -## Convenience observables +### Configure -AngularFire provides observables to allow convenient use of the Firebase Authentication with RXJS. +Using the `SETTINGS` DI Token (_default: null_), we can set the current Auth +instance's settings. This is used to edit/read configuration related options +like app verification mode for phone authentication, which is useful for +[testing](https://cloud.google.com/identity-platform/docs/test-phone-numbers). -### user +```ts +import { SETTINGS as AUTH_SETTINGS } from "@angular/fire/compat/auth"; + +@NgModule({ + // ... Existing configuration + providers: [ + // ... Existing Providers + { + provide: AUTH_SETTINGS, + useValue: { appVerificationDisabledForTesting: true }, + }, + ], +}) +export class AppModule {} +``` -TBD +Read more at [Firebase Auth Settings](https://firebase.google.com/docs/reference/js/firebase.auth.AuthSettings). -### authState +## UI Libraries -TBD +- Material Design : [ngx-auth-firebaseui](https://github.com/AnthonyNahas/ngx-auth-firebaseui) +- Bootstrap : [@firebaseui/ng-bootstrap](https://github.com/firebaseui/ng-bootstrap) -### idToken +## Cordova -TBD +Learn how to [setup Firebase Authentication with Cordova](https://firebase.google.com/docs/auth/web/cordova) in the Firebase Guides. ## Connecting the the emulator suite @@ -79,4 +447,4 @@ import { connectAuthEmulator, getAuth, provideAuth } from '@angular/fire/auth'; }), ] }) -``` \ No newline at end of file +``` diff --git a/docs/firestore.md b/docs/firestore.md index a5b16e844..c44444790 100644 --- a/docs/firestore.md +++ b/docs/firestore.md @@ -3,21 +3,506 @@ AngularFireDeveloper Guide ❱ Realtime Cloud Firestore +# 2. Documents in AngularFirestore -# Cloud Firestore +> Cloud Firestore is a NoSQL, document-oriented database. Unlike a SQL database, there are no tables or rows. Instead, you store data in *documents*, which are organized into *collections*. +Each *document* contains a set of key-value pairs. Cloud Firestore is optimized for storing large collections of small documents. -What is Firestore? +### [Go to collections querying](#collections-querying) +# Document querying +####

This documentation is updated for modular version 9 of angular fire package. For previous or missing features refer to older docs.

-## Dependency Injection +## Features list of document querying +1. Create a document +2. Update a document +3. Delete a document +4. Observer a document for changes +5. Inline querying like + * Incrementing field value + * Adding a data in an array + * Removing a data from an array +6. Offline data persistence -YADA YADA YADA +## Create a document +### Create the document in a fixed document id -## Firebase API +```ts +import { Component, OnInit } from '@angular/core'; +import { Firestore } from '@angular/fire/firestore'; +import { doc, getDoc } from 'firebase/firestore'; -Something something look at the offical docs +@Component({ + selector: "app-root", + template: ` + + `, +}) +export class AppComponent implements OnInit { + data: any = {}; + constructor(private firestore: Firestore) {} + ngOnInit() { + getDoc(doc(this.firestore,'data/Pn5oHxAjpnSEAPJABj22')).then((data)=>{ + this.data = data.data(); + }) + } +} +``` +### Add the document inside a collection -## Convenience observables +Here the document with data `{name:'Rick Roll'}` will be added to collection `data` with unique random id generated by firebase firestore +```ts +import { Component, OnInit } from '@angular/core'; +import { Firestore } from '@angular/fire/firestore'; +import { doc, addDoc } from 'firebase/firestore'; -### Foo +@Component({ + selector: "app-root", + template: `

Check console

`, +}) +export class AppComponent implements OnInit { + constructor(private firestore: Firestore) {} + ngOnInit() { + addDoc(collection(this.firestore,'data'),{name:'Rick Roll'}).then((data)=>{ + console.log('Doc added'); + }) + } +} +``` +## Update doc with new data +This method changes the data inside the document `mydoc` which is inside the collection `data` with he new data `{name:'Updated name is rick roll'}` +```ts +import { Component, OnInit } from '@angular/core'; +import { Firestore } from '@angular/fire/firestore'; +import { doc, updateDoc } from 'firebase/firestore'; -bar baz \ No newline at end of file +@Component({ + selector: "app-root", + template: `

Check console

`, +}) +export class AppComponent implements OnInit { + constructor(private firestore: Firestore) {} + ngOnInit() { + updateDoc(doc(this.firestore,'data/mydoc'),{name:'Updated name is rick roll'}).then((data)=>{ + console.log('Doc updated'); + }) + } +} +``` +## Delete a doc +This method deletes the document `mydoc` which is inside the collection. +```ts +import { Component, OnInit } from '@angular/core'; +import { Firestore } from '@angular/fire/firestore'; +import { doc, deleteDoc } from 'firebase/firestore'; + +@Component({ + selector: "app-root", + template: `

Check console

`, +}) +export class AppComponent implements OnInit { + constructor(private firestore: Firestore) {} + ngOnInit() { + deleteDoc(doc(this.firestore,'data/mydoc')).then((data)=>{ + console.log('Doc deleted'); + }) + } +} +``` +## Get realtime updates on document data + +This methods returns you a subscription from which you can subscribe and get the latest data whenever the document changes it's data. + +```ts +import { Component, OnInit } from '@angular/core'; +import { Firestore } from '@angular/fire/firestore'; +import { doc, docData } from 'firebase/firestore'; +import { Subscription } from 'rxjs'; + +@Component({ + selector: "app-root", + template: ` +

This data will change when data inside document changes

+

Data {{this.documentData}}

`, +}) +export class AppComponent implements OnInit, OnDestroy { + constructor(private firestore: Firestore) {} + documentSubscription:Subscription = Subscription.EMPTY; + documentData:any = {} + ngOnInit() { + this.documentSubscription = docData(doc(this.firestore, 'data/Pn5oHxAjpnSEAPJABj22')).subscribe( + (data: any) => { + this.documentData = data.data() + console.log('Doc changed',data.data()) + } + ); + } +} +``` + +## Inline Querying and data manipulation + +### Incrementing field value + +```ts +import { Component, OnInit } from '@angular/core'; +import { Firestore } from '@angular/fire/firestore'; +import { doc, docData } from 'firebase/firestore'; +import { Subscription } from 'rxjs'; + +@Component({ + selector: "app-root", + template: `

Everytime you refresh the page your score increases by 4

`, +}) +export class AppComponent implements OnInit, OnDestroy { + constructor(private firestore: Firestore) {} + ngOnInit() { + updateDoc(doc(this.firestore, 'data/Pn5oHxAjpnSEAPJABj22'), {score:increment(4)}).then(()=>{ + console.log('incremented'); + }) + } +} +``` +Other field value functions are also accepted like +1. `increment(n)` Increments the specified value by n +2. `arrayRemove(n)` Removes the data n from a specified array +3. `arrayUnion(n)` Adds the data n to specified array + + +##Access data offline + +Cloud Firestore supports offline data persistence. This feature caches a copy of the Cloud Firestore data that your app is actively using, so your app can access the data when the device is offline. You can write, read, listen to, and query the cached data. When the device comes back online, Cloud Firestore synchronizes any local changes made by your app to the Cloud Firestore backend. + +To use offline persistence, you don't need to make any changes to the code that you use to access Cloud Firestore data. With offline persistence enabled, the Cloud Firestore client library automatically manages online and offline data access and synchronizes local data when the device is back online. +### Configure offline persistence + +When you initialize Cloud Firestore, you can enable or disable offline persistence: + +* For Android and Apple platforms, offline persistence is enabled by default. To disable persistence, set the `PersistenceEnabled` option to `false`. +* For the web, offline persistence is disabled by default. To enable persistence, call the enablePersistence method. Cloud Firestore's cache isn't automatically cleared between sessions. Consequently, if your web app + +[For more info refer here](https://firebase.google.com/docs/firestore/manage-data/enable-offline) + +This will enable persistence in your firestore database +In your `app.module.ts` file add this when initializing firestore +```ts +import { enableIndexedDbPersistence } from 'firebase/firestore'; +import { provideFirestore,getFirestore } from '@angular/fire/firestore'; + +@NgModule({ + imports:[ + provideFirestore(()=>{ + const firestore = getFirestore(); + enableIndexedDbPersistence(firestore); + return firestore + }), + ] +}) +``` +--- +# Collections querying +####

This documentation is updated for modular version 9 of angular fire package. For previous or missing features refer to older docs.

+ +> Cloud Firestore is a NoSQL, document-oriented database. Unlike a SQL database, there are no tables or rows. Instead, you store data in _documents_, which are organized into _collections_. +> Each _document_ contains a set of key-value pairs. Cloud Firestore is optimized for storing large collections of small documents. + +## Using `AngularFirestoreCollection` + +The `AngularFirestoreCollection` service is a wrapper around the native Firestore SDK's [`CollectionReference`](https://firebase.google.com/docs/reference/js/firebase.firestore.CollectionReference) and [`Query`](https://firebase.google.com/docs/reference/js/firebase.firestore.Query) types. It is a generic service that provides you with a strongly typed set of methods for manipulating and streaming data. This service is designed for use as an `@Injectable()`. + +```ts +import { Component } from "@angular/core"; +import { Subscription } from "rxjs"; +import { collection, getDocs, QuerySnapshot } from "firebase/firestore"; +import { Firestore } from "@angular/fire/firestore"; +export interface Item { + name: string; +} + +@Component({ + selector: "app-root", + template: ` + + `, +}) +export class AppComponent implements OnInit { + items: Item[] = []; + constructor(private firestore: Firestore) {} + ngOnInit() { + getDocs(collection(this.firestore, "data")).then( + (querySnapshot: QuerySnapshot) => { + this.items = []; + querySnapshot.forEach((doc: any) => { + this.items.push(doc.data()); + }); + } + ); + } +} +``` + +## Streaming collection data or get realtime data + +There are multiple ways of streaming collection data from Firestore. + +1. `collectionSnapshots()` It is a method which returns a subscription with latest and complete list of documents in a collection. +2. `collectionChanges()` It is a method which returns a subscription with only the changed data in a collection. + +## `collectionSnapshots()` Implementation example + +```typescript +import { Component } from "@angular/core"; +import { Subscription } from "rxjs"; +import { collection } from "firebase/firestore"; +import { collectionSnapshots, Firestore } from "@angular/fire/firestore"; +export interface Item { + name: string; +} + +@Component({ + selector: "app-root", + template: ` + + `, +}) +export class AppComponent implements OnInit, OnDestroy { + itemListSubscription: Subscription = Subscription.EMPTY; + items: Item[] = []; + constructor(private firestore: Firestore) {} + ngOnInit() { + this.dataListSubscription = collectionSnapshots( + collection(this.firestore, "data") + ).subscribe((data) => { + this.items = []; + data.forEach((doc: any) => { + this.items.push(doc.data()); + }); + }); + } + ngOnDestroy(): void { + this.itemListSubscription.unsubscribe(); + } +} +``` + +## `collectionChanges()` Implementation example + +This method only returns data of a changed document so it can be used for keeping an eye on edits on any dataset. This is good method because you don't have to compare all data on the fly to know which one is new. + +> Cool tip: It has the best use case when showing notifications. But remember + +>

WARNING: It outputs every document last change present in the collection when it is first invoked or executed.

+ +```typescript +import { Component } from "@angular/core"; +import { Subscription } from "rxjs"; +import { collection } from "firebase/firestore"; +import { collectionChanges, Firestore } from "@angular/fire/firestore"; +export interface Item { + name: string; +} + +@Component({ + selector: "app-root", + template: ` + + `, +}) +export class AppComponent implements OnInit, OnDestroy { + itemListSubscription: Subscription = Subscription.EMPTY; + items: Item[] = []; + constructor(private firestore: Firestore) {} + ngOnInit() { + this.dataListSubscription = collectionChanges( + collection(this.firestore, "data") + ).subscribe((dataChange) => { + this.items = []; + dataChange.forEach((doc: any) => { + this.items.push(doc.data()); + }); + }); + } + ngOnDestroy(): void { + this.itemListSubscription.unsubscribe(); + } +} +``` + +## Conditional collection querying + +Cloud Firestore provides powerful query functionality for specifying which documents you want to retrieve from a collection or collection group. These queries can also be used with either `getDocs()` or `collectionSnapshots()`, as described in Get Data and Get Realtime Updates. + +[More on queries refer here](https://firebase.google.com/docs/firestore/query-data/queries) + +#### Simple example demonstrating functioning of queries. + +```typescript +import { Component } from "@angular/core"; +import { collection, getDocs, query, where } from "firebase/firestore"; +import { Firestore } from "@angular/fire/firestore"; +export interface Item { + name: string; + salary:number; +} + +@Component({ + selector: "app-root", + template: ` + + `, +}) +export class AppComponent implements OnInit { + items: Item[] = []; + constructor(private firestore: Firestore) {} + ngOnInit() { + getDocs( + query( + collection(this.firestore,'data'), + where('salary', '>=', 400))).then(collections:any)=>{ + collections.forEach((doc:any)=>{ + this.items.push(doc.data()) + }) + }) + } +} +``` +The where() method takes three parameters: a field to filter on, a comparison operator, and a value. Cloud Firestore supports the following comparison operators: + +* `<` less than +* `<=` less than or equal to +* `==` equal to +* `\>` greater than +* `\>=` greater than or equal to +* `!=` not equal to +* `array-contains` +* `array-contains-any` +* `in` +* `not-in` + + +[Looking for more advanced queries](https://firebase.google.com/docs/firestore/query-data/queries#query_operators) + +--- +## Order and limit data with Cloud Firestore + +Cloud Firestore provides powerful query functionality for specifying which documents you want to retrieve from a collection. These queries can also be used with either `getDocs()` or `collectionChanges()`, as described in Get Data. + +### Order and limit data +By default, a query retrieves all documents that satisfy the query in ascending order by document ID. You can specify the sort order for your data using orderBy(), and you can limit the number of documents retrieved using limit(). + +For example, you could query for the first 3 cities alphabetically with: + +```typescript +import { Component } from "@angular/core"; +import { collection, getDocs, query, limit, where } from "firebase/firestore"; +import { Firestore } from "@angular/fire/firestore"; +export interface City { + name: string; +} + +@Component({ + selector: "app-root", + template: ` + + `, +}) +export class AppComponent implements OnInit { + cities: City[] = []; + constructor(private firestore: Firestore) {} + ngOnInit() { + getDocs( + query( + collection(this.firestore,'cities'), + orderBy('name'), + limit(10))).then((collections:any)=>{ + collections.forEach((doc:any)=>{ + console.log('Conditional',doc.data()) + this.cities.push(doc.data()) + }) + }) + } +} +``` + +[For more references and extra options visit full docs](https://firebase.google.com/docs/firestore/query-data/order-limit-data) + +## Paginate data with query cursors +With query cursors in Cloud Firestore, you can split data returned by a query into batches according to the parameters you define in your query. + +Query cursors define the start and end points for a query, allowing you to: + +* Return a subset of the data. +* Paginate query results. + +However, to define a specific range for a query, you should use the `where()` method described in Simple Queries. + +### Add a simple cursor to a query + +Use the `startAt()` or `startAfter()` methods to define the start point for a query. The `startAt()` method includes the start point, while the `startAfter()` method excludes it. + +For example, if you use `startAt(A)` in a query, it returns the entire alphabet. If you use `startAfter(A)` instead, it returns `B-Z`. + +```typescript +import { Component } from "@angular/core"; +import { collection, getDocs, query, limit, where } from "firebase/firestore"; +import { Firestore } from "@angular/fire/firestore"; +export interface City { + name: string; + population:number; +} + +@Component({ + selector: "app-root", + template: ` + + `, +}) +export class AppComponent implements OnInit { + cities: City[] = []; + constructor(private firestore: Firestore) {} + ngOnInit() { + getDocs( + query( + collection(this.firestore,'population'), + orderBy("population"), + startAt(1000000))).then((collections:any)=>{ + collections.forEach((doc:any)=>{ + this.cities.push(doc.data()) + }) + }) + } +} +``` + +> Now to paginate this query. You just need to get the length of current cities length(-1) and then use it inside `startAfter` function so that all the results will be after the last document + +[Full example on pagination](https://firebase.google.com/docs/firestore/query-data/query-cursors#paginate_a_query) From c6b50eabb1001f662ec3f6153b93e8d05a5b498d Mon Sep 17 00:00:00 2001 From: Kumar Saptam Date: Fri, 11 Mar 2022 22:06:42 +0530 Subject: [PATCH 2/2] Added docs for firebase storage ## Topics covered 1. Inject storage service 2. Import and initialize cloud storage 3. Add any file to the cloud storage 4. Check upload Percentage using observables 5. Get download link of the uploaded file 6. Deleting file from a storage reference 7. Custom metadata 8. Update metadata 9. Set cache policy for files 10. Full list of metadata properties 11. List all files from a storage reference 12. Paginate list results 13. Handle error messages ### More features will be added subsequently --- docs/storage.md | 460 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 452 insertions(+), 8 deletions(-) diff --git a/docs/storage.md b/docs/storage.md index a875670b0..6187e60fe 100644 --- a/docs/storage.md +++ b/docs/storage.md @@ -6,18 +6,462 @@ # Cloud Storage -What is messaging? +## Features of storage -## Dependency Injection +1. [Add any file to the cloud storage](#adding-files) +2. [Delete the file](#deleting-a-file-from-storage) +3. [Get a download link](#get-download-url-of-uploaded-file) +4. [Set file metadata](#setting-custom-metadata-with-files) +5. [List all Files](#list-all-files) +6. [Paginate file list](#paginate-list-results) +### First let's initialize our storage api -YADA YADA YADA +File `app.module.ts` -## Firebase API +```ts +import { provideStorage,getStorage } from '@angular/fire/storage'; -Something something look at the offical docs +@NgModule({ + imports:[ + provideStorage(() => getStorage()) + ] +}) +``` -## Convenience observables +#### Inject it in our component or service (Recommended) file. -### Foo +```ts +import { Storage } from "@angular/fire/storage"; +export class StorageComponent { + constructor(private storage: Storage) {} +} +``` -bar baz \ No newline at end of file +### Uploading files and data + +To upload the data to firebase storage we are going to use the `uploadBytesResumable()` + +So let's create a random text file named `data.txt` in assets folder of angular root directory with any data in it. + +```text +Lorem ipsum dolor sit amet, consectetur +adipiscing elit. Quisque nibh nisi, +tincidunt vel facilisis pretium, varius digni ssim +... +``` + +Now let's write logic and implementation + +```ts +import { Storage, ref, uploadBytes } from "@angular/fire/storage"; +@Component({ + selector: "app-storage", + template: '', +}) +export class StorageComponent { + constructor(private storage: Storage) {} + upload(event: any) { + if (event.files.length > 0) { + const storageRef = ref(this.storage, "images/data.txt"); + uploadBytes(storageRef, event.files[0]).then((data) => { + console.log("File uploaded"); + alert("File uploaded"); + }); + } + } +} +``` + +Here the `uploadBytes` function takes two arguments one is a `storageRef` aka storage reference and another is `File` or `Blob` or `Uint8Array` or `ArrayBuffer` and an optional argument `metadata` to include additional metadata with your file. + +This function returns a `Promise` object with [`UploadResult`](https://firebase.google.com/docs/reference/js/storage.uploadresult) data in it. + +### Get progress of the current upload status + +You may want to get the upload percentage of the file while you are uploading. Firebase storage provides `percentage()` method which will return an `Observable` with two data inside it. One is progress another is `UploadTaskSnapshot`. + +But to do this we need to use `uploadBytesResumable()` function which returns a task. + +```ts +import { Storage, ref, uploadBytesResumable } from "@angular/fire/storage"; +@Component({ + selector: "app-storage", + template: ` +

Uploaded {{ uploadPercentage }}

`, +}) +export class StorageComponent implements OnDestroy { + uploadPercentage: number = 0; + percentageSubscription: Subscription = Subscription.EMPTY; + constructor(private storage: Storage) {} + ngOnDestroy() { + percentageSubscription.unsubscribe(); + } + upload(event: any) { + if (event.files.length > 0) { + const storageRef = ref(this.storage, "images/data.txt"); + const uploadTask = uploadBytesResumable(storageRef, event.files[0]); + uploadTask.then((data) => { + console.log("File uploaded"); + alert("File uploaded"); + }); + // Percentage observer + this.percentageSubscription = percentage(task).subscribe((percentage) => { + this.uploadPercentage = percentage.progress; + }); + } + } +} +``` + +### Get download url of uploaded file + +We can get the download URL of the `File` which has been uploaded. So that you can store it in a database for future reference. + +```ts +import { + Storage, + ref, + uploadBytesResumable, + getDownloadURL, +} from "@angular/fire/storage"; +@Component({ + selector: "app-storage", + template: ` +

Uploaded URL {{ uploadURL }}

`, +}) +export class StorageComponent { + uploadURL: string = ""; + constructor(private storage: Storage) {} + upload(event: any) { + if (event.files.length > 0) { + const storageRef = ref(this.storage, "images/data.txt"); + const uploadTask = uploadBytesResumable(storageRef, event.files[0]); + uploadTask.then((data) => { + console.log("File uploaded"); + alert("File uploaded"); + }); + // Download url + getDownloadURL(storageRef).then((url) => { + this.uploadURL = url; + }); + } + } +} +``` + +### Deleting a file from storage + +You may want to delete some data or a file from the firebase storage. Firestore provides `deleteObject()` function which can delete any file reference. + +```ts +import {deleteObject} from "@angular/fire/storage"; +... +const storageRef = ref(this.storage, "images/data.txt"); +deleteObject(storageRef).then(()=>console.log('Deleted')) +``` + +### Setting custom metadata with files. + +After uploading a file to Cloud Storage reference, you can also get or update the file metadata, for example to update the content type. Files can also store custom key/value pairs with additional file metadata. + +> Note: By default, a Cloud Storage bucket requires Firebase Authentication to perform any action on the bucket's data or files. You can change your Firebase Security Rules for Cloud Storage to allow unauthenticated access. Since Firebase and your project's default App Engine app share this bucket, configuring public access may make newly uploaded App Engine files publicly accessible, as well. Be sure to restrict access to your Cloud Storage bucket again when you set up Authentication. + +#### Get file metadata + +File metadata contains common properties such as name, size, and `contentType` (often referred to as MIME type) in addition to some less common ones like `contentDisposition` and `timeCreated`. This metadata can be retrieved from a Cloud Storage reference using the `getMetadata()` method. `getMetadata()` returns a Promise containing the complete metadata, or an error if the Promise rejects. + +```ts +const newMetaData = { + cacheControl: 'public,max-age=300', + contentType: 'image/jpeg' +}; +// Set metadata while uploading +... +const uploadTask = uploadBytesResumable(storageRef, event.files[0],metadata:newMetaData); + +// Update metadata after the files is uploaded + +import { updateMetadata } from '@angular/fire/storage'; +... +const storageRef = ref(this.storage, "images/data.txt"); +updateMetadata(storageRef,newMetaData) + +// Get metadata of a file + +import { getMetadata } from '@angular/fire/storage'; +... +const storageRef = ref(this.storage, "images/data.txt"); +getMetadata(storageRef).then((metadata)=>{ + // Metadata now contains the metadata for 'images/data.txt' +}).catch((error) => { + // Uh-oh, an error occurred! +}); + +``` + +### File metadata properties + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyTypeWritable
bucketstringNO
generationstringNO
metagenerationstringNO
fullPathstringNO
namestringNO
sizenumberNO
timeCreatedstringNO
updatedstringNO
md5HashstringYES on upload, NO on updateMetadata
cacheControlstringYES
contentDispositionstringYES
contentEncodingstringYES
contentLanguagestringYES
contentTypestringYES
customMetadataObject containing string->string mappingsYES
+ +## List files with Cloud Storage on Web + +Cloud Storage for Firebase allows you to list the contents of your Cloud Storage bucket. The SDKs return both the items and the prefixes of objects under the current Cloud Storage reference. + +Projects that use the List API require Cloud Storage for Firebase Rules Version 2. If you have an existing Firebase project, follow the steps in the Security Rules Guide. + +> Note: The List API is only allowed for Rules version 2. In Rules version 2, allow read is the shorthand for allow get, list. + +### List all files + +You can use `listAll` to fetch all results for a directory. This is best used for small directories as all results are buffered in memory. The operation also may not return a consistent snapshot if objects are added or removed during the process. + +For a large list, use the paginated `list()` method as `listAll()` buffers all results in memory. + +The following example demonstrates `listAll`. + +```ts +import { Storage, ref, listAll } from "@angular/fire/storage"; +@Component({ + selector: "app-storage", + template: `

Check console

`, +}) +export class StorageComponent implements OnInit { + constructor(private storage: Storage) {} + ngOnInit() { + const storageRef = ref(this.storage, "images"); + listAll(storageRef) + .then((data) => { + data.prefixes.forEach((prefix) => { + console.log(prefix); + // All the prefixes under listRef. + // You may call listAll() recursively on them. + }); + data.items.forEach((item) => { + console.log(item); + // All the items under listRef. + }); + }) + .catch((error) => { + console.error(error); + // Uh-oh, an error occurred! + }); + } +} +``` + +## Paginate list results + +The `list()` API places a limit on the number of results it returns. `list()` provides a consistent pageview and exposes a `pageToken` that allows control over when to fetch additional results. + +The `pageToken` encodes the path and version of the last item returned in the previous result. In a subsequent request using the `pageToken`, items that come after the `pageToken` are shown. + +The following example demonstrates paginating a result using async/await. + +This method will only return you first 10 results. + +```ts +list(storageRef, { maxResults: 10 }); +``` + +Full Example Below + +```ts +import { getStorage, ref, list } from "firebase/storage"; +@Component({ + selector: "app-storage", + template: `

Check console

`, +}) +export class StorageComponent implements OnInit { + constructor(private storage: Storage) {} + ngOnInit() { + const storageRef = ref(this.storage, "images"); + list(storageRef, { maxResults: 10 }) + .then((data) => { + data.prefixes.forEach((prefix) => { + console.log(prefix); + // All the prefixes under listRef. + // You may call listAll() recursively on them. + }); + data.items.forEach((item) => { + console.log(item); + // All the items under listRef. + }); + }) + .catch((error) => { + console.error(error); + // Uh-oh, an error occurred! + }); + } +} +``` + +## Handle Error Messages + +There are a number of reasons why errors may occur, including the file not existing, the user not having permission to access the desired file, or the user cancelling the file upload. + +To properly diagnose the issue and handle the error, here is a full list of all the errors our client will raise, and how they occurred. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CodeReason
storage/unknownAn unknown error occurred.
storage/object-not-foundNo object exists at the desired reference.
storage/bucket-not-foundNo bucket is configured for Cloud Storage
storage/project-not-foundNo project is configured for Cloud Storage
storage/quota-exceededQuota on your Cloud Storage bucket has been exceeded. + If you're on the no-cost tier, upgrade to a paid plan. If you're on + a paid plan, reach out to Firebase support.
storage/unauthenticatedUser is unauthenticated, please authenticate and try again.
storage/unauthorizedUser is not authorized to perform the desired action, check your + security rules to ensure they are correct.
storage/retry-limit-exceededThe maximum time limit on an operation (upload, download, delete, etc.) + has been excceded. Try uploading again.
storage/invalid-checksumFile on the client does not match the checksum of the file received + by the server. Try uploading again.
storage/canceledUser canceled the operation.
storage/invalid-event-nameInvalid event name provided. Must be one of + [`running`, `progress`, `pause`]
storage/invalid-urlInvalid URL provided to refFromURL(). Must be of the form: + gs://bucket/object or https://firebasestorage.googleapis.com/v0/b/bucket/o/object?token=<TOKEN>
storage/invalid-argumentThe argument passed to put() must be `File`, `Blob`, or + `UInt8` Array. The argument passed to putString() must be + a raw, `Base64`, or `Base64URL` string.
storage/no-default-bucketNo bucket has been set in your config's + storageBucket property.
storage/cannot-slice-blobCommonly occurs when the local file has changed (deleted, saved again, + etc.). Try uploading again after verifying that the file hasn't + changed.
storage/server-file-wrong-sizeFile on the client does not match the size of the file recieved by the + server. Try uploading again.