CINXE.COM

AngularFire web codelab  |  Firebase

<!doctype html> <html lang="en" dir="ltr"> <head> <meta name="google-signin-client-id" content="721724668570-nbkv1cfusk7kk4eni4pjvepaus73b13t.apps.googleusercontent.com"> <meta name="google-signin-scope" content="profile email https://www.googleapis.com/auth/developerprofiles https://www.googleapis.com/auth/developerprofiles.award"> <meta property="og:site_name" content="Firebase"> <meta property="og:type" content="website"><meta name="theme-color" content="#a8c7fa"><meta charset="utf-8"> <meta content="IE=Edge" http-equiv="X-UA-Compatible"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="manifest" href="/_pwa/firebase/manifest.json" crossorigin="use-credentials"> <link rel="preconnect" href="//www.gstatic.com" crossorigin> <link rel="preconnect" href="//fonts.gstatic.com" crossorigin> <link rel="preconnect" href="//fonts.googleapis.com" crossorigin> <link rel="preconnect" href="//apis.google.com" crossorigin> <link rel="preconnect" href="//www.google-analytics.com" crossorigin><link rel="stylesheet" href="//fonts.googleapis.com/css?family=Google+Sans:400,500|Roboto:400,400italic,500,500italic,700,700italic|Roboto+Mono:400,500,700&display=swap"> <link rel="stylesheet" href="//fonts.googleapis.com/css2?family=Material+Icons&family=Material+Symbols+Outlined&display=block"><link rel="stylesheet" href="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/firebase/css/app.css"> <link rel="stylesheet" href="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/firebase/css/dark-theme.css" disabled> <link rel="shortcut icon" href="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/firebase/images/favicon.png"> <link rel="apple-touch-icon" href="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/firebase/images/touchicon-180.png"><link rel="canonical" href="https://firebase.google.com/codelabs/firebase-web"><link rel="search" type="application/opensearchdescription+xml" title="Firebase" href="https://firebase.google.com/s/opensearch.xml"> <link rel="alternate" hreflang="en" href="https://firebase.google.com/codelabs/firebase-web" /><link rel="alternate" hreflang="x-default" href="https://firebase.google.com/codelabs/firebase-web" /><link rel="alternate" hreflang="ar" href="https://firebase.google.com/codelabs/firebase-web?hl=ar" /><link rel="alternate" hreflang="bn" href="https://firebase.google.com/codelabs/firebase-web?hl=bn" /><link rel="alternate" hreflang="zh-Hans" href="https://firebase.google.com/codelabs/firebase-web?hl=zh-cn" /><link rel="alternate" hreflang="zh-Hant" href="https://firebase.google.com/codelabs/firebase-web?hl=zh-tw" /><link rel="alternate" hreflang="fa" href="https://firebase.google.com/codelabs/firebase-web?hl=fa" /><link rel="alternate" hreflang="fr" href="https://firebase.google.com/codelabs/firebase-web?hl=fr" /><link rel="alternate" hreflang="de" href="https://firebase.google.com/codelabs/firebase-web?hl=de" /><link rel="alternate" hreflang="he" href="https://firebase.google.com/codelabs/firebase-web?hl=he" /><link rel="alternate" hreflang="hi" href="https://firebase.google.com/codelabs/firebase-web?hl=hi" /><link rel="alternate" hreflang="id" href="https://firebase.google.com/codelabs/firebase-web?hl=id" /><link rel="alternate" hreflang="it" href="https://firebase.google.com/codelabs/firebase-web?hl=it" /><link rel="alternate" hreflang="ja" href="https://firebase.google.com/codelabs/firebase-web?hl=ja" /><link rel="alternate" hreflang="ko" href="https://firebase.google.com/codelabs/firebase-web?hl=ko" /><link rel="alternate" hreflang="pl" href="https://firebase.google.com/codelabs/firebase-web?hl=pl" /><link rel="alternate" hreflang="pt-BR" href="https://firebase.google.com/codelabs/firebase-web?hl=pt-br" /><link rel="alternate" hreflang="pt" href="https://firebase.google.com/codelabs/firebase-web?hl=pt" /><link rel="alternate" hreflang="ru" href="https://firebase.google.com/codelabs/firebase-web?hl=ru" /><link rel="alternate" hreflang="es" href="https://firebase.google.com/codelabs/firebase-web?hl=es" /><link rel="alternate" hreflang="es-419" href="https://firebase.google.com/codelabs/firebase-web?hl=es-419" /><link rel="alternate" hreflang="th" href="https://firebase.google.com/codelabs/firebase-web?hl=th" /><link rel="alternate" hreflang="tr" href="https://firebase.google.com/codelabs/firebase-web?hl=tr" /><link rel="alternate" hreflang="vi" href="https://firebase.google.com/codelabs/firebase-web?hl=vi" /><title>AngularFire web codelab &nbsp;|&nbsp; Firebase</title> <meta property="og:title" content="AngularFire web codelab &nbsp;|&nbsp; Firebase"><meta name="description" content="In this codelab, you’ll learn how to use the Firebase platform on the web by building a chat app."> <meta property="og:description" content="In this codelab, you’ll learn how to use the Firebase platform on the web by building a chat app."><meta property="og:url" content="https://firebase.google.com/codelabs/firebase-web"><meta property="og:locale" content="en"> <link rel="stylesheet" href="/extras.css"></head> <body class="" template="codelab" theme="firebase-icy-theme" type="codelab" appearance layout="docs" concierge='closed' display-toc pending> <devsite-progress type="indeterminate" id="app-progress"></devsite-progress> <section class="devsite-wrapper"> <devsite-cookie-notification-bar></devsite-cookie-notification-bar><devsite-header role="banner" keep-tabs-visible> <div class="devsite-header--inner nocontent"> <div class="devsite-top-logo-row-wrapper-wrapper"> <div class="devsite-top-logo-row-wrapper"> <div class="devsite-top-logo-row"> <button type="button" id="devsite-hamburger-menu" class="devsite-header-icon-button button-flat material-icons gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Navigation menu button" visually-hidden aria-label="Open menu"> </button> <div class="devsite-product-name-wrapper"> <a href="/" class="devsite-site-logo-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Site logo" track-type="globalNav" track-name="firebase" track-metadata-position="nav" track-metadata-eventDetail="nav"> <picture> <source srcset="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/firebase/images/lockup.svg" media="(prefers-color-scheme: dark)" class="devsite-dark-theme" alt="Firebase"> <img src="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/firebase/images/lockup.svg" class="devsite-site-logo" alt="Firebase"> </picture> </a> </div> <div class="devsite-top-logo-row-middle"> <div class="devsite-header-upper-tabs"> </div> <devsite-search enable-signin enable-search enable-suggestions enable-query-completion tenant-name="Firebase" > <form class="devsite-search-form" action="https://firebase.google.com/s/results" method="GET"> <div class="devsite-search-container"> <button type="button" search-open class="devsite-search-button devsite-header-icon-button button-flat material-icons" aria-label="Open search"></button> <div class="devsite-searchbox"> <input aria-activedescendant="" aria-autocomplete="list" aria-label="Search" aria-expanded="false" aria-haspopup="listbox" autocomplete="off" class="devsite-search-field devsite-search-query" name="q" placeholder="Search" role="combobox" type="text" value="" > <div class="devsite-search-image material-icons" aria-hidden="true"> </div> <div class="devsite-search-shortcut-icon-container" aria-hidden="true"> <kbd class="devsite-search-shortcut-icon">/</kbd> </div> </div> </div> </form> <button type="button" search-close class="devsite-search-button devsite-header-icon-button button-flat material-icons" aria-label="Close search"></button> </devsite-search> </div> <devsite-appearance-selector></devsite-appearance-selector> <devsite-language-selector> <ul role="presentation"> <li role="presentation"> <a role="menuitem" lang="en" >English</a> </li> <li role="presentation"> <a role="menuitem" lang="de" >Deutsch</a> </li> <li role="presentation"> <a role="menuitem" lang="es" >Español</a> </li> <li role="presentation"> <a role="menuitem" lang="es_419" >Español – América Latina</a> </li> <li role="presentation"> <a role="menuitem" lang="fr" >Français</a> </li> <li role="presentation"> <a role="menuitem" lang="id" >Indonesia</a> </li> <li role="presentation"> <a role="menuitem" lang="it" >Italiano</a> </li> <li role="presentation"> <a role="menuitem" lang="pl" >Polski</a> </li> <li role="presentation"> <a role="menuitem" lang="pt" >Português</a> </li> <li role="presentation"> <a role="menuitem" lang="pt_br" >Português – Brasil</a> </li> <li role="presentation"> <a role="menuitem" lang="vi" >Tiếng Việt</a> </li> <li role="presentation"> <a role="menuitem" lang="tr" >Türkçe</a> </li> <li role="presentation"> <a role="menuitem" lang="ru" >Русский</a> </li> <li role="presentation"> <a role="menuitem" lang="he" >עברית</a> </li> <li role="presentation"> <a role="menuitem" lang="ar" >العربيّة</a> </li> <li role="presentation"> <a role="menuitem" lang="fa" >فارسی</a> </li> <li role="presentation"> <a role="menuitem" lang="hi" >हिंदी</a> </li> <li role="presentation"> <a role="menuitem" lang="bn" >বাংলা</a> </li> <li role="presentation"> <a role="menuitem" lang="th" >ภาษาไทย</a> </li> <li role="presentation"> <a role="menuitem" lang="zh_cn" >中文 – 简体</a> </li> <li role="presentation"> <a role="menuitem" lang="zh_tw" >中文 – 繁體</a> </li> <li role="presentation"> <a role="menuitem" lang="ja" >日本語</a> </li> <li role="presentation"> <a role="menuitem" lang="ko" >한국어</a> </li> </ul> </devsite-language-selector> <a class="devsite-header-link devsite-top-button button gc-analytics-event" href="//console.firebase.google.com" data-category="Site-Wide Custom Events" data-label="Site header link" > Go to console </a> <devsite-user enable-profiles fp-auth id="devsite-user"> <span class="button devsite-top-button" aria-hidden="true" visually-hidden>Sign in</span> </devsite-user> </div> </div> </div> </div> </devsite-header> <devsite-book-nav scrollbars hidden> <div class="devsite-book-nav-filter" hidden> <span class="filter-list-icon material-icons" aria-hidden="true"></span> <input type="text" placeholder="Filter" aria-label="Type to filter" role="searchbox"> <span class="filter-clear-button hidden" data-title="Clear filter" aria-label="Clear filter" role="button" tabindex="0"></span> </div> <nav class="devsite-book-nav devsite-nav nocontent" aria-label="Side menu"> <div class="devsite-mobile-header"> <button type="button" id="devsite-close-nav" class="devsite-header-icon-button button-flat material-icons gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Close navigation" aria-label="Close navigation"> </button> <div class="devsite-product-name-wrapper"> <a href="/" class="devsite-site-logo-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Site logo" track-type="globalNav" track-name="firebase" track-metadata-position="nav" track-metadata-eventDetail="nav"> <picture> <source srcset="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/firebase/images/lockup.svg" media="(prefers-color-scheme: dark)" class="devsite-dark-theme" alt="Firebase"> <img src="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/firebase/images/lockup.svg" class="devsite-site-logo" alt="Firebase"> </picture> </a> </div> </div> <div class="devsite-book-nav-wrapper"> <div class="devsite-mobile-nav-top"> <ul class="devsite-nav-list"> <li class="devsite-nav-item"> <a href="//console.firebase.google.com" class="devsite-nav-title gc-analytics-event " data-category="Site-Wide Custom Events" data-label="Responsive Tab: Go to console" track-type="navMenu" track-metadata-eventDetail="globalMenu" track-metadata-position="nav"> <span class="devsite-nav-text" tooltip > Go to console </span> </a> </li> </ul> </div> </div> </nav> </devsite-book-nav> <section id="gc-wrapper"> <main role="main" class="devsite-main-content" > <devsite-content> <article class="devsite-article"><style> body { transition: opacity ease-in 0.2s; } body[unresolved] { opacity: 0; display: block; overflow: hidden; position: relative; } </style> <div class="devsite-article-meta nocontent" role="navigation"> <ul class="devsite-breadcrumb-list" > </ul> </div> <h1 class="devsite-page-title" tabindex="-1"> AngularFire web codelab </h1> <devsite-feature-tooltip ack-key="AckCollectionsBookmarkTooltipDismiss" analytics-category="Site-Wide Custom Events" analytics-action-show="Callout Profile displayed" analytics-action-close="Callout Profile dismissed" analytics-label="Create Collection Callout" class="devsite-page-bookmark-tooltip nocontent" dismiss-button="true" id="devsite-collections-dropdown" dismiss-button-text="Dismiss" close-button-text="Got it"> <devsite-bookmark></devsite-bookmark> <span slot="popout-heading"> Stay organized with collections </span> <span slot="popout-contents"> Save and categorize content based on your preferences. </span> </devsite-feature-tooltip> <devsite-toc class="devsite-nav" depth="1" devsite-toc-embedded > </devsite-toc> <div class="devsite-article-body clearfix "> <google-codelab-analytics gaid="UA-49880327-14" ga4id="G-JTFZSJVVVZ"></google-codelab-analytics> <google-codelab codelab-gaid="" codelab-ga4id="" doc-id="" id="firebase-web" title="AngularFire web codelab" no-tooltip="" environment="web" category="cloud,web,firebase" feedback-link="" layout="paginated" > <google-codelab-step label="Overview" duration="1" step="0"> <google-codelab-about codelab-title="AngularFire web codelab" authors="Nicolas Garnier, Jeff Huleatt, Cynthia Wang" last-updated="2024-11-01T13:19:31Z" duration="85"> </google-codelab-about> <h2 class="step-title" id="0" data-text="Overview" tabindex="-1"> 1. Overview </h2> <p>In this codelab, you&#39;ll learn how to use <a href="https://firebaseopensource.com/projects/firebase/angularfire/" target="_blank">AngularFire</a> to create web applications by implementing and deploying a chat client using Firebase products and services.</p> <p class="image-container"><img alt="A chat app where users are discussing Firebase" title="Final App" style="width: 537.95px" src="/static/codelabs/firebase-web/img/friendlychat_with_ai.webp"></p> <h3 class="checklist" is-upgraded id="what-youll-learn" data-text="What you'll learn" tabindex="-1">What you'll learn</h3> <ul class="checklist"> <li>Build a web app using Angular and Firebase.</li> <li>Sync data using Cloud Firestore and Cloud Storage for Firebase.</li> <li>Authenticate your users using Firebase Authentication.</li> <li>Deploy your web app on Firebase App Hosting.</li> <li>Send notifications with Firebase Cloud Messaging.</li> <li>Collect your web app&#39;s performance data.</li> </ul> <h3 is-upgraded id="what-youll-need" data-text="What you'll need" tabindex="-1">What you'll need</h3> <ul> <li>A GitHub account</li> <li>The ability to upgrade your Firebase project to the <a href="/docs/projects/billing/firebase-pricing-plans#blaze-pricing-plan" target="_blank">Blaze pricing plan</a></li> <li>The IDE/text editor of your choice, such as <a href="https://www.jetbrains.com/webstorm" target="_blank">WebStorm</a>, <a href="https://www.sublimetext.com/" target="_blank">Sublime</a>, or <a href="https://code.visualstudio.com/" target="_blank">VS Code</a></li> <li>The package manager <a href="https://www.npmjs.com/" target="_blank">npm</a>, which typically comes with <a href="https://nodejs.org/en/" target="_blank">Node.js</a></li> <li>A terminal/console</li> <li>A browser of your choice, such as Chrome</li> <li>The codelab&#39;s sample code (See the next step of the codelab for how to get the code.)</li> </ul> </google-codelab-step> <google-codelab-step label="Get the sample code" duration="2" step="1"> <h2 class="step-title" id="1" data-text="Get the sample code" tabindex="-1"> 2. Get the sample code </h2> <h2 is-upgraded id="create-a-github-repository" data-text="Create a GitHub repository" tabindex="-1">Create a GitHub repository</h2> <p>The codelab source can be found at <a href="https://github.com/firebase/codelab-friendlychat-web" target="_blank">https://github.com/firebase/codelab-friendlychat-web</a>. The repository contains sample projects for multiple platforms. However, this codelab uses only the <code translate="no" dir="ltr">angularfire-start</code> directory.</p> <p>Copy the <code translate="no" dir="ltr">angularfire-start</code> folder into your own repository:</p> <ol type="1"> <li>Using a terminal, create a new folder on your computer and change into the new directory:<pre class="prettyprint" translate="no" dir="ltr"><code language="language-shell" class="language-shell" translate="no" dir="ltr">mkdir codelab-friendlyeats-web cd codelab-friendlyeats-web </code></pre> </li> <li>Use the <a href="https://github.com/unjs/giget" target="_blank">giget</a> npm package to fetch only the <code translate="no" dir="ltr">angularfire-start</code> folder:<pre class="prettyprint" translate="no" dir="ltr"><code language="language-shell" class="language-shell" translate="no" dir="ltr">npx giget@latest gh:firebase/codelab-friendlychat-web/angularfire-start#master . --install </code></pre> </li> <li>Track changes locally with git:<pre class="prettyprint" translate="no" dir="ltr"><code language="language-shell" class="language-shell" translate="no" dir="ltr">git init git add . git commit -m &#34;codelab starting point&#34; git branch -M main </code></pre> </li> <li>Create a new GitHub repository: <a href="https://github.com/new" target="_blank">https://github.com/new</a>. Name it anything you&#39;d like.<ol type="1"> <li>GitHub will give you a new repository URL that looks like either <code translate="no" dir="ltr">https://github.com/[user-name]/[repository-name].git</code> or <code translate="no" dir="ltr">git@github.com:[user-name]/[repository-name].git</code>. Copy this URL.</li> </ol> </li> <li>Push local changes to your new GitHub repository. Run the following command, substituting your repository URL for the <code translate="no" dir="ltr">your-repository-url</code> placeholder.<pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">git remote add origin your-repository-url git push -u origin main </code></pre> </li> <li>You should now see the starter code in your GitHub repository.</li> </ol> </google-codelab-step> <google-codelab-step label="Create and set up a Firebase project" duration="5" step="2"> <h2 class="step-title" id="2" data-text="Create and set up a Firebase project" tabindex="-1"> 3. Create and set up a Firebase project </h2> <h3 is-upgraded id="create-a-firebase-project" data-text="Create a Firebase project" tabindex="-1"><strong>Create a Firebase project</strong></h3> <ol type="1"> <li>Sign in to the <a href="https://console.firebase.google.com/" target="_blank">Firebase console</a>.</li> <li>In the Firebase console, click <strong>Add Project</strong>, and then name your Firebase project <strong>FriendlyChat</strong>. Remember the project ID for your Firebase project.</li> <li>Uncheck <strong>Enable Google Analytics for this project</strong></li> <li>Click <strong>Create Project</strong>.</li> </ol> <aside class="special"><p><strong>Important</strong>: Your Firebase project will be named <strong>FriendlyChat</strong>, but Firebase will automatically assign it a unique Project ID in the form <strong>friendlychat-1234</strong>. This unique identifier is how your project is identified (including in the CLI), whereas <em>FriendlyChat</em> is simply a display name.</p> </aside> <p>The application that you&#39;re going to build uses Firebase products that are available for web apps:</p> <ul> <li><strong>Firebase Authentication</strong> to easily allow your users to sign into your app.</li> <li><strong>Cloud Firestore</strong> to save structured data on the cloud and get instant notification when data changes.</li> <li><strong>Cloud Storage for Firebase</strong> to save files in the cloud.</li> <li><strong>Firebase App Hosting</strong> to build, host, and serve the app.</li> <li><strong>Firebase Cloud Messaging</strong> to send push notifications and display browser popup notifications.</li> <li><strong>Firebase Performance Monitoring</strong> to collect user performance data for your app.</li> </ul> <p>Some of these products need special configuration or need to be enabled using the Firebase console.</p> <h2 is-upgraded id="upgrade-your-firebase-pricing-plan" data-text="Upgrade your Firebase pricing plan" tabindex="-1">Upgrade your Firebase pricing plan</h2> <p>To use Firebase App Hosting and Cloud Storage for Firebase, your Firebase project needs to be on the <a href="/docs/projects/billing/firebase-pricing-plans#blaze-pricing-plan" target="_blank">pay-as-you go (Blaze) pricing plan</a>, which means it&#39;s linked to a <a href="/docs/projects/billing/firebase-pricing-plans#cloud-billing-accounts" target="_blank">Cloud Billing account</a>.</p> <ul> <li>A Cloud Billing account requires a payment method, like a credit card.</li> <li>If you&#39;re new to Firebase and Google Cloud, check if you&#39;re eligible for a <a href="/support/faq#pricing-free-trial" target="_blank">$300 credit and a Free Trial Cloud Billing account</a>.</li> <li>If you&#39;re doing this codelab as part of an event, ask your organizer if there are any Cloud credits available.</li> </ul> <p>To upgrade your project to the Blaze plan, follow these steps:</p> <ol type="1"> <li>In the Firebase console, select to <a href="https://console.firebase.google.com/project/_/overview?purchaseBillingPlan=metered" target="_blank">upgrade your plan</a>.</li> <li>Select the Blaze plan. Follow the on-screen instructions to link a Cloud Billing account to your project.<br>If you needed to create a Cloud Billing account as part of this upgrade, you might need to navigate back to the upgrade flow in the Firebase console to complete the upgrade.</li> </ol> <aside class="special"><p><strong>Note</strong>: You can <a href="https://console.firebase.google.com/project/_/overview?purchaseBillingPlan=metered" target="_blank">downgrade your project to the no-cost (Spark) pricing plan</a> after completing this codelab.</p> </aside> <h3 is-upgraded id="add-a-firebase-web-app-to-the-project" data-text="Add a Firebase web app to the project" tabindex="-1">Add a Firebase web app to the project</h3> <ol type="1"> <li>Click the web icon <img alt="58d6543a156e56f9.png" style="width: 41.00px" src="/static/codelabs/firebase-web/img/58d6543a156e56f9.png" srcset="https://firebase.google.com/static/codelabs/firebase-web/img/58d6543a156e56f9_36.png 36w,https://firebase.google.com/static/codelabs/firebase-web/img/58d6543a156e56f9_48.png 48w,https://firebase.google.com/static/codelabs/firebase-web/img/58d6543a156e56f9_72.png 72w,https://firebase.google.com/static/codelabs/firebase-web/img/58d6543a156e56f9_96.png 96w,https://firebase.google.com/static/codelabs/firebase-web/img/58d6543a156e56f9_480.png 480w,https://firebase.google.com/static/codelabs/firebase-web/img/58d6543a156e56f9_720.png 720w,https://firebase.google.com/static/codelabs/firebase-web/img/58d6543a156e56f9_856.png 856w,https://firebase.google.com/static/codelabs/firebase-web/img/58d6543a156e56f9_960.png 960w,https://firebase.google.com/static/codelabs/firebase-web/img/58d6543a156e56f9_1440.png 1440w,https://firebase.google.com/static/codelabs/firebase-web/img/58d6543a156e56f9_1920.png 1920w,https://firebase.google.com/static/codelabs/firebase-web/img/58d6543a156e56f9_2880.png 2880w" sizes="(max-width: 840px) 100vw, 856px">to create a new Firebase web app.</li> <li>Register the app with the nickname <strong>Friendly Chat</strong>. Don&#39;t check the box next to <strong>Also set up Firebase Hosting for this app</strong>. Click <strong>Register app</strong>.</li> <li>On the next step, you&#39;ll see a configuration object. You don&#39;t need it right now. Click <strong>Continue to console</strong>.</li> </ol> <p class="image-container"><img alt="Register web app screenshot" style="width: 624.00px" src="/static/codelabs/firebase-web/img/register-web-app.png" srcset="https://firebase.google.com/static/codelabs/firebase-web/img/register-web-app_36.png 36w,https://firebase.google.com/static/codelabs/firebase-web/img/register-web-app_48.png 48w,https://firebase.google.com/static/codelabs/firebase-web/img/register-web-app_72.png 72w,https://firebase.google.com/static/codelabs/firebase-web/img/register-web-app_96.png 96w,https://firebase.google.com/static/codelabs/firebase-web/img/register-web-app_480.png 480w,https://firebase.google.com/static/codelabs/firebase-web/img/register-web-app_720.png 720w,https://firebase.google.com/static/codelabs/firebase-web/img/register-web-app_856.png 856w,https://firebase.google.com/static/codelabs/firebase-web/img/register-web-app_960.png 960w,https://firebase.google.com/static/codelabs/firebase-web/img/register-web-app_1440.png 1440w,https://firebase.google.com/static/codelabs/firebase-web/img/register-web-app_1920.png 1920w,https://firebase.google.com/static/codelabs/firebase-web/img/register-web-app_2880.png 2880w" sizes="(max-width: 840px) 100vw, 856px"></p> <h3 is-upgraded id="set-up-authentication" data-text="Set up Authentication" tabindex="-1">Set up Authentication</h3> <p>To allow users to sign in to the web app with their Google accounts, you&#39;ll use the <strong>Google</strong> sign-in method.</p> <ol type="1"> <li>In the Firebase console, navigate to <a href="https://console.firebase.google.com/project/_/authentication" target="_blank">Authentication</a>.</li> <li>Click <strong>Get started</strong>.</li> <li>In the <strong>Additional providers</strong> column, click <strong>Google &gt; Enable</strong>.</li> <li>In the <strong>Public-facing name for project</strong> text box, enter a memorable name, such as <code translate="no" dir="ltr">My Next.js app</code>.</li> <li>From the <strong>Support email for project</strong> drop-down, select your email address.</li> <li>Click <strong>Save</strong>.</li> </ol> <h3 is-upgraded id="set-up-cloud-firestore" data-text="Set up Cloud Firestore" tabindex="-1"><strong>Set up Cloud Firestore</strong></h3> <p>The web app uses <a href="https://firebase.google.com/docs/firestore/" target="_blank">Cloud Firestore</a> to save chat messages and receive new chat messages.</p> <p>Here&#39;s how to set up Cloud Firestore in your Firebase project:</p> <ol type="1"> <li>In the left-panel of the Firebase console, expand <strong>Build</strong> and then select <a href="https://console.firebase.google.com/project/_/firestore" target="_blank"><strong>Firestore database</strong></a>.</li> <li>Click <strong>Create database</strong>.</li> <li>Leave the <em>Database ID</em> set to <code translate="no" dir="ltr">(default)</code>.</li> <li>Select a location for your database, then click <em>Next</em>.<br>For a real app, you want to choose a location that&#39;s close to your users.</li> <li>Click <strong>Start in test mode</strong>. Read the disclaimer about the security rules.<br>Later in this codelab, you&#39;ll add Security Rules to secure your data. <strong>Do </strong><strong><em>not</em></strong><strong> distribute or expose an app publicly without adding Security Rules for your database.</strong></li> <li>Click <strong>Create</strong>.</li> </ol> <h3 is-upgraded id="set-up-cloud-storage-for-firebase" data-text="Set up Cloud Storage for Firebase" tabindex="-1"><strong>Set up Cloud Storage for Firebase</strong></h3> <p>The web app uses Cloud Storage for Firebase to store, upload, and share pictures.</p> <p>Here&#39;s how to set up Cloud Storage for Firebase in your Firebase project:</p> <ol type="1"> <li>In the left-panel of the Firebase console, expand <strong>Build</strong> and then select <a href="https://console.firebase.google.com/project/_/storage" target="_blank"><strong>Storage</strong></a>.</li> <li>Click <strong>Get started</strong>.</li> <li>Select a location for your default Storage bucket.<br>Buckets in <code translate="no" dir="ltr">US-WEST1</code>, <code translate="no" dir="ltr">US-CENTRAL1</code>, and <code translate="no" dir="ltr">US-EAST1</code> can take advantage of the <a href="https://cloud.google.com/storage/pricing#cloud-storage-always-free" target="_blank">&#34;Always Free&#34; tier</a> for Google Cloud Storage. Buckets in all other locations follow <a href="https://cloud.google.com/storage/pricing" target="_blank">Google Cloud Storage pricing and usage</a>.</li> <li>Click <strong>Start in test mode</strong>. Read the disclaimer about the security rules.<br>Later in this codelab, you&#39;ll add security rules to secure your data. <strong>Do </strong><strong><em>not</em></strong><strong> distribute or expose an app publicly without adding Security Rules for your Storage bucket.</strong></li> <li>Click <strong>Create</strong>.</li> </ol> </google-codelab-step> <google-codelab-step label="Install the Firebase command-line interface" duration="2" step="3"> <h2 class="step-title" id="3" data-text="Install the Firebase command-line interface" tabindex="-1"> 4. Install the Firebase command-line interface </h2> <p>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.</p> <aside class="special"><p><strong>Note</strong>: To install the CLI, you need to install <a href="https://www.npmjs.com/" target="_blank">npm</a> which typically comes with <a href="https://nodejs.org/en/" target="_blank">Node.js</a>.</p> </aside> <ol type="1"> <li>Install the CLI by running the following npm command:</li> </ol> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">npm -g install firebase-tools@latest </code></pre> <aside class="warning"><p>Doesn&#39;t work? You may need to <a href="https://docs.npmjs.com/getting-started/fixing-npm-permissions" target="_blank">change npm permissions.</a></p> </aside> <ol type="1" start="2"> <li>Verify that the CLI has been installed correctly by running the following command:</li> </ol> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">firebase --version </code></pre> <p>Make sure that the version of the Firebase CLI is vv13.9.0 or later.</p> <ol type="1" start="3"> <li>Authorize the Firebase CLI by running the following command:</li> </ol> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">firebase login </code></pre> <p>You&#39;ve set up the web app template to pull your app&#39;s configuration for Firebase Hosting from your app&#39;s local directory (the repository that you cloned earlier in the codelab). But to pull the configuration, you need to associate your app with your Firebase project.</p> <ol type="1" start="4"> <li>Make sure that your command line is accessing your app&#39;s local <code translate="no" dir="ltr">angularfire-start</code> directory.</li> <li>Associate your app with your Firebase project by running the following command:</li> </ol> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">firebase use --add </code></pre> <ol type="1" start="6"> <li>When prompted, select your <strong>Project ID</strong>, then give your Firebase project an alias.</li> </ol> <p>An alias is useful if you have multiple environments (production, staging, etc). However, for this codelab, let&#39;s just use the alias of <code translate="no" dir="ltr">default</code>.</p> <ol type="1" start="7"> <li>Follow the remaining instructions on your command line.</li> </ol> </google-codelab-step> <google-codelab-step label="Install AngularFire" duration="5" step="4"> <h2 class="step-title" id="4" data-text="Install AngularFire" tabindex="-1"> 5. Install AngularFire </h2> <p>Before running the project, make sure you have the Angular CLI and AngularFire set up.</p> <ol type="1"> <li>In a console, run the following command:</li> </ol> <pre translate="no" dir="ltr">npm install -g @angular/cli </pre> <ol type="1" start="2"> <li>Then, in a console from the <code translate="no" dir="ltr">angularfire-start</code> directory, run the following Angular CLI command:</li> </ol> <pre translate="no" dir="ltr">ng add @angular/fire </pre> <p>This will install all the necessary dependencies for your project.</p> <ol type="1" start="3"> <li>When prompted, uncheck <code translate="no" dir="ltr">ng deploy -- hosting</code> with the space bar. Select the following features with the arrow keys and space bar:<ul> <li><code translate="no" dir="ltr">Authentication</code></li> <li><code translate="no" dir="ltr">Firestore</code></li> <li><code translate="no" dir="ltr">Cloud Messaging</code></li> <li><code translate="no" dir="ltr">Cloud Storage</code></li> </ul> </li> <li>Press <code translate="no" dir="ltr">enter</code> and follow the remaining prompts.</li> <li>Create a commit with commit message &#34;Install AngularFire&#34; and push it to your GitHub repository.</li> </ol> </google-codelab-step> <google-codelab-step label="Create an App Hosting backend" duration="10" step="5"> <h2 class="step-title" id="5" data-text="Create an App Hosting backend" tabindex="-1"> 6. Create an App Hosting backend </h2> <p>In this section, you&#39;ll set up an App Hosting backend to watch a branch on your git repository.</p> <aside class="special"><p><strong>Note</strong>: <a href="/docs/app-hosting" target="_blank">Firebase App Hosting is in public preview</a>.</p> </aside> <p>By the end of this section, you&#39;ll have an App Hosting backend connected to your repository in GitHub that will automatically re-build and rollout a new version of your app whenever you push a new commit to your <code translate="no" dir="ltr">main</code> branch.</p> <ol type="1"> <li>Navigate to the <a href="https://console.firebase.google.com/project/_/apphosting" target="_blank">App Hosting page</a> in the Firebase console:</li> </ol> <p class="image-container"><img alt="The zero state of the App Hosting console, with a 'Get Started' button" style="width: 541.50px" src="/static/codelabs/firebase-web/img/app_hosting_console_zero_state.webp"></p> <ol type="1" start="2"> <li>Click &#34;Get started&#34; to start the backend creation flow. Configure your backend as follows:</li> <li>Follow the prompts in the first step to connect the GitHub repository you created earlier.</li> <li>Set deployment settings:<ol type="1"> <li>Keep the root directory as <code translate="no" dir="ltr">/</code></li> <li>Set the live branch to <code translate="no" dir="ltr">main</code></li> <li>Enable automatic rollouts</li> </ol> </li> <li>Name your backend <code translate="no" dir="ltr">friendlychat-codelab</code>.</li> <li>In &#34;Create or associate a Firebase web app&#34;, pick the web app you configured earlier from the &#34;Select an existing Firebase web app&#34; drop down.</li> <li>Click &#34;Finish and deploy&#34;. After a moment, you&#39;ll be taken to a new page where you can see the status of your new App Hosting backend!<aside class="special"><p><strong>Note</strong>: You can monitor the underlying Google Cloud services for your App Hosting backend by following links from the console. Learn more in <a href="https://firebase.google.com/docs/app-hosting/logging" target="_blank">View logs and metrics</a>.</p> </aside> </li> <li>Once your rollout completes, click your free domain under &#34;domains&#34;. This may take a few minutes to begin working due to DNS propagation.</li> </ol> <p>You&#39;ve deployed the initial web app! Every time you push a new commit to the <code translate="no" dir="ltr">main</code> branch of your GitHub repository, you&#39;ll see a new build and rollout begin in the Firebase console, and your site will automatically update once the rollout completes.</p> <p class="image-container"><img alt="The zero state of the App Hosting console, with a 'Get Started' button" style="width: 541.50px" src="/static/codelabs/firebase-web/img/app_hosting_with_rollouts.webp"></p> <p>You should see your FriendlyChat app&#39;s sign-in screen, which is not (yet!) functioning.</p> <p>The app cannot do anything right now, but with your help, it will soon!</p> <p>Let&#39;s now build a real-time chat app.</p> </google-codelab-step> <google-codelab-step label="Import and configure Firebase" duration="5" step="6"> <h2 class="step-title" id="6" data-text="Import and configure Firebase" tabindex="-1"> 7. Import and configure Firebase </h2> <h3 is-upgraded id="configure-firebase" data-text="Configure Firebase" tabindex="-1"><strong>Configure Firebase</strong></h3> <p>You&#39;ll need to configure the Firebase SDK to tell it which Firebase project you&#39;re using.</p> <ol type="1"> <li>Go to your <a href="https://console.firebase.google.com/project/_/settings/general/" target="_blank">Project settings in the Firebase console</a></li> <li>In the &#34;Your apps&#34; card, select the nickname of the app for which you need a config object.</li> <li>Select &#34;Config&#34; from the Firebase SDK snippet pane.</li> </ol> <p>You will find that an environment file <code translate="no" dir="ltr">/angularfire-start/src/environments/environment.ts</code> was generated for you.</p> <ol type="1" start="4"> <li>Copy the config object snippet, then add it to <code translate="no" dir="ltr">angularfire-start/src/firebase-config.js</code>.</li> </ol> <h3 is-upgraded id="environment.ts" data-text="environment.ts" tabindex="-1"><a href="https://github.com/firebase/codelab-friendlychat-web/blob/main/angularfire-start/src/environments/environment.ts" target="_blank"><code translate="no" dir="ltr">environment.ts</code></a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">export const environment = { firebase: { apiKey: &#34;API_KEY&#34;, authDomain: &#34;PROJECT_ID.firebaseapp.com&#34;, projectId: &#34;PROJECT_ID&#34;, storageBucket: &#34;PROJECT_ID.firebasestorage.app&#34;, messagingSenderId: &#34;SENDER_ID&#34;, appId: &#34;APP_ID&#34;, }, }; </code></pre> <aside class="special"><p>The above code should contain your app-specific Firebase config object, not our placeholder values!</p> </aside> <h3 is-upgraded id="view-angularfire-setup" data-text="View AngularFire setup" tabindex="-1"><strong>View AngularFire setup</strong></h3> <p>You will find that the features you&#39;ve selected in the console were automatically added in the <code translate="no" dir="ltr">/angularfire-start/src/app/app.config.ts</code> file. This allows your app to use Firebase features and functionalities.</p> </google-codelab-step> <google-codelab-step label="Set up user sign-in" duration="10" step="7"> <h2 class="step-title" id="7" data-text="Set up user sign-in" tabindex="-1"> 8. Set up user sign-in </h2> <p>AngularFire should now be ready to use since it&#39;s imported and initialized in <code translate="no" dir="ltr">app.config.ts</code>. You&#39;re now going to implement user sign-in using <a href="https://firebase.google.com/docs/auth/web/start" target="_blank">Firebase Authentication</a>.</p> <h2 is-upgraded id="add-an-authorized-domain" data-text="Add an authorized domain" tabindex="-1">Add an authorized domain</h2> <p>Firebase Authentication only allows sign-ins from a set list of domains that you control. Add your free App Hosting domain to the list of domains:</p> <ol type="1"> <li>Navigate to <a href="https://console.firebase.google.com/project/_/apphosting" target="_blank">App Hosting</a>.</li> <li>Copy your backend&#39;s domain.</li> <li>Navigate to <a href="https://console.firebase.google.com/project/_/authentication/settings" target="_blank">Authentication settings</a>.</li> <li>Choose the <strong>Authorized domains</strong> tab.</li> <li>Click <strong>Add domain</strong> and paste your App Hosting backend&#39;s domain.</li> </ol> <h3 is-upgraded id="authenticate-your-users-with-google-sign-in" data-text="Authenticate your users with Google Sign-In" tabindex="-1"><strong>Authenticate your users with Google Sign-In</strong></h3> <p>In the app, when a user clicks the <strong>Sign in with Google</strong> button, the <code translate="no" dir="ltr">login</code> function is triggered. For this codelab, you want to authorize Firebase to use Google as the identity provider. You&#39;ll use a popup, but <a href="https://firebase.google.com/docs/auth/web/google-signin" target="_blank">several other methods</a> are available from Firebase.</p> <ol type="1"> <li>In the subdirectory <code translate="no" dir="ltr">/src/app/services/</code>, open <code translate="no" dir="ltr">chat.service.ts</code>.</li> <li>Find the function <code translate="no" dir="ltr">login</code>.</li> <li>Replace the entire function with the following code.</li> </ol> <h3 is-upgraded id="chat.service.ts" data-text="chat.service.ts" tabindex="-1"><a href="https://github.com/firebase/codelab-friendlychat-web/blob/main/angularfire-start/src/app/services/chat.service.ts" target="_blank"><code translate="no" dir="ltr">chat.service.ts</code></a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">// Signs-in Friendly Chat. login() { signInWithPopup(this.auth, this.provider).then((result) =&gt; { const credential = GoogleAuthProvider.credentialFromResult(result); this.router.navigate([&#39;/&#39;, &#39;chat&#39;]); return credential; }) } </code></pre> <p>The <code translate="no" dir="ltr">logout</code> function is triggered when the user clicks the <strong>Log out</strong> button.</p> <ol type="1"> <li>Go back to the file <code translate="no" dir="ltr">src/app/services/chat.service.ts</code>.</li> <li>Find the function <code translate="no" dir="ltr">logout</code>.</li> <li>Replace the entire function with the following code.</li> </ol> <h3 is-upgraded id="chat.service.ts_1" data-text="chat.service.ts" tabindex="-1"><a href="https://github.com/firebase/codelab-friendlychat-web/blob/main/angularfire-start/src/app/services/chat.service.ts" target="_blank"><code translate="no" dir="ltr">chat.service.ts</code></a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">// Logout of Friendly Chat. logout() { signOut(this.auth).then(() =&gt; { this.router.navigate([&#39;/&#39;, &#39;login&#39;]) console.log(&#39;signed out&#39;); }).catch((error) =&gt; { console.log(&#39;sign out error: &#39; + error); }) } </code></pre> <h3 is-upgraded id="track-the-authentication-state" data-text="Track the authentication state" tabindex="-1"><strong>Track the authentication state</strong></h3> <p>To update our UI accordingly, you need a way to check if the user is logged in or logged out. AngularFire provides a function to get an observable that updates each time the authentication state changes. This has already been implemented, but is worth taking a look at.</p> <ol type="1"> <li>Go back to the file <code translate="no" dir="ltr">src/app/services/chat.service.ts</code>.</li> <li>Find the variable assignment <code translate="no" dir="ltr">user$</code>.</li> </ol> <h3 is-upgraded id="chat.service.ts_2" data-text="chat.service.ts" tabindex="-1"><a href="https://github.com/firebase/codelab-friendlychat-web/blob/main/angularfire-start/src/app/services/chat.service.ts" target="_blank"><code translate="no" dir="ltr">chat.service.ts</code></a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">// observable that is updated when the auth state changes user$ = user(this.auth); </code></pre> <p>The code above calls the AngularFire function <code translate="no" dir="ltr">user</code> which returns an observable user. It will trigger each time the authentication state changes (when the user signs in or signs out). The Angular templates components in FriendlyChat use this observable to update the UI to redirect, display the user in the header nav, and so on.</p> <h3 is-upgraded id="test-logging-into-the-app" data-text="Test logging into the app" tabindex="-1">Test logging into the app</h3> <ol type="1"> <li>Create a commit with commit message &#34;Adding Google Authentication&#34; and push it to your GitHub repository.</li> <li>Open the <a href="https://console.firebase.google.com/project/_/apphosting" target="_blank">App Hosting page</a> in the Firebase console and wait for your new rollout to complete.</li> <li>In the web app, refresh the page and log in to the app using the sign-in button and your Google account. If you see an error message stating <code translate="no" dir="ltr">auth/operation-not-allowed</code>, check to make sure that you enabled Google Sign-in as an authentication provider in the Firebase console.</li> <li>After logging in, your profile picture and user name should be displayed: <img alt="angularfire-3.png" style="width: 312.00px" src="/static/codelabs/firebase-web/img/angularfire-3.png" srcset="https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-3_36.png 36w,https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-3_48.png 48w,https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-3_72.png 72w,https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-3_96.png 96w,https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-3_480.png 480w,https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-3_720.png 720w,https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-3_856.png 856w,https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-3_960.png 960w,https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-3_1440.png 1440w,https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-3_1920.png 1920w,https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-3_2880.png 2880w" sizes="(max-width: 840px) 100vw, 856px"></li> </ol> </google-codelab-step> <google-codelab-step label="Write messages to Cloud Firestore" duration="5" step="8"> <h2 class="step-title" id="8" data-text="Write messages to Cloud Firestore" tabindex="-1"> 9. Write messages to Cloud Firestore </h2> <p>In this section, you&#39;ll write some data to Cloud Firestore so that you can populate the app&#39;s UI. This can be done manually with the <a href="https://console.firebase.google.com" target="_blank">Firebase console</a>, but you&#39;ll do it in the app itself to demonstrate a basic Cloud Firestore write.</p> <h3 is-upgraded id="data-model" data-text="Data model" tabindex="-1"><strong>Data model</strong></h3> <p>Cloud Firestore data is split into collections, documents, fields, and subcollections. You will store each message of the chat as a document in a top-level collection called <code translate="no" dir="ltr">messages</code>.</p> <p class="image-container"><img alt="688d7bc5fb662b57.png" style="width: 193.10px" src="/static/codelabs/firebase-web/img/688d7bc5fb662b57.png" srcset="https://firebase.google.com/static/codelabs/firebase-web/img/688d7bc5fb662b57_36.png 36w,https://firebase.google.com/static/codelabs/firebase-web/img/688d7bc5fb662b57_48.png 48w,https://firebase.google.com/static/codelabs/firebase-web/img/688d7bc5fb662b57_72.png 72w,https://firebase.google.com/static/codelabs/firebase-web/img/688d7bc5fb662b57_96.png 96w,https://firebase.google.com/static/codelabs/firebase-web/img/688d7bc5fb662b57_480.png 480w,https://firebase.google.com/static/codelabs/firebase-web/img/688d7bc5fb662b57_720.png 720w,https://firebase.google.com/static/codelabs/firebase-web/img/688d7bc5fb662b57_856.png 856w,https://firebase.google.com/static/codelabs/firebase-web/img/688d7bc5fb662b57_960.png 960w,https://firebase.google.com/static/codelabs/firebase-web/img/688d7bc5fb662b57_1440.png 1440w,https://firebase.google.com/static/codelabs/firebase-web/img/688d7bc5fb662b57_1920.png 1920w,https://firebase.google.com/static/codelabs/firebase-web/img/688d7bc5fb662b57_2880.png 2880w" sizes="(max-width: 840px) 100vw, 856px"></p> <aside class="special"><p><strong>Tip</strong>: To learn more about the Cloud Firestore data model, read about documents and collections in <a href="https://firebase.google.com/docs/firestore/data-model" target="_blank">the documentation</a>.</p> </aside> <h3 is-upgraded id="add-messages-to-cloud-firestore" data-text="Add messages to Cloud Firestore" tabindex="-1"><strong>Add messages to Cloud Firestore</strong></h3> <p>To store the chat messages that are written by users, you&#39;ll use <a href="https://firebase.google.com/docs/firestore/" target="_blank">Cloud Firestore</a>.</p> <p>In this section, you&#39;ll add the functionality for users to write new messages to your database. A user clicking the <strong>SEND</strong> button will trigger the code snippet below. It adds a message object with the contents of the message fields to your Cloud Firestore instance in the <code translate="no" dir="ltr">messages</code> collection. The <code translate="no" dir="ltr">add()</code> method adds a new document with an automatically generated ID to the collection.</p> <ol type="1"> <li>Go back to the file <code translate="no" dir="ltr">src/app/services/chat.service.ts</code>.</li> <li>Find the function <code translate="no" dir="ltr">addMessage</code>.</li> <li>Replace the entire function with the following code.</li> </ol> <h3 is-upgraded id="chat.service.ts_3" data-text="chat.service.ts" tabindex="-1"><a href="https://github.com/firebase/codelab-friendlychat-web/blob/main/angularfire-start/src/app/services/chat.service.ts" target="_blank"><code translate="no" dir="ltr">chat.service.ts</code></a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">// Adds a text or image message to Cloud Firestore. addMessage = async ( textMessage: string | null, imageUrl: string | null, ): Promise&lt;void | DocumentReference&lt;DocumentData&gt;&gt; =&gt; { // ignore empty messages if (!textMessage &amp;&amp; !imageUrl) { console.log( &#34;addMessage was called without a message&#34;, textMessage, imageUrl, ); return; } if (this.currentUser === null) { console.log(&#34;addMessage requires a signed-in user&#34;); return; } const message: ChatMessage = { name: this.currentUser.displayName, profilePicUrl: this.currentUser.photoURL, timestamp: serverTimestamp(), uid: this.currentUser?.uid, }; textMessage &amp;&amp; (message.text = textMessage); imageUrl &amp;&amp; (message.imageUrl = imageUrl); try { const newMessageRef = await addDoc( collection(this.firestore, &#34;messages&#34;), message, ); return newMessageRef; } catch (error) { console.error(&#34;Error writing new message to Firebase Database&#34;, error); return; } }; </code></pre> <h3 is-upgraded id="test-sending-messages" data-text="Test sending messages" tabindex="-1"><strong>Test sending messages</strong></h3> <ol type="1"> <li>Create a commit with commit message &#34;Post new chats to Firestore&#34; and push it to your GitHub repository.</li> <li>Open the <a href="https://console.firebase.google.com/project/_/apphosting" target="_blank">App Hosting page</a> in the Firebase console and wait for your new rollout to complete.</li> <li>Refresh FriendlyChat. After logging in, enter a message such as &#34;Hey there!&#34;, and then click <strong>SEND</strong>. This will write the message into Cloud Firestore. However, <em>you won&#39;t yet see the data in your actual web app</em> because you still need to implement <em>retrieving</em> the data (the next section of the codelab).</li> <li>You can see the newly added message in your Firebase Console. Open your Emulator suite UI. Under the <strong>Build</strong> section click <strong>Firestore Database</strong> (or click <a href="http://localhost:4000/firestore/data" target="_blank">here</a> and you should see the <strong>messages</strong> collection with your newly added message:</li> </ol> <p class="image-container"><img alt="6812efe7da395692.png" style="width: 624.00px" src="/static/codelabs/firebase-web/img/6812efe7da395692.png" srcset="https://firebase.google.com/static/codelabs/firebase-web/img/6812efe7da395692_36.png 36w,https://firebase.google.com/static/codelabs/firebase-web/img/6812efe7da395692_48.png 48w,https://firebase.google.com/static/codelabs/firebase-web/img/6812efe7da395692_72.png 72w,https://firebase.google.com/static/codelabs/firebase-web/img/6812efe7da395692_96.png 96w,https://firebase.google.com/static/codelabs/firebase-web/img/6812efe7da395692_480.png 480w,https://firebase.google.com/static/codelabs/firebase-web/img/6812efe7da395692_720.png 720w,https://firebase.google.com/static/codelabs/firebase-web/img/6812efe7da395692_856.png 856w,https://firebase.google.com/static/codelabs/firebase-web/img/6812efe7da395692_960.png 960w,https://firebase.google.com/static/codelabs/firebase-web/img/6812efe7da395692_1440.png 1440w,https://firebase.google.com/static/codelabs/firebase-web/img/6812efe7da395692_1920.png 1920w,https://firebase.google.com/static/codelabs/firebase-web/img/6812efe7da395692_2880.png 2880w" sizes="(max-width: 840px) 100vw, 856px"></p> </google-codelab-step> <google-codelab-step label="Read messages" duration="10" step="9"> <h2 class="step-title" id="9" data-text="Read messages" tabindex="-1"> 10. Read messages </h2> <h3 is-upgraded id="synchronize-messages" data-text="Synchronize messages" tabindex="-1">Synchronize <strong>messages</strong></h3> <p>To read messages in the app, you&#39;ll need to add an observable that will trigger when data changes and then create a UI element that shows new messages.</p> <p>You&#39;ll add code that listens for newly added messages from the app. In this code, you&#39;ll retrieve the snapshot of the <code translate="no" dir="ltr">messages</code> collection. You&#39;ll only display the last 12 messages of the chat to avoid displaying a very long history upon loading.</p> <ol type="1"> <li>Go back to the file <code translate="no" dir="ltr">src/app/services/chat.service.ts</code>.</li> <li>Find the function <code translate="no" dir="ltr">loadMessages</code>.</li> <li>Replace the entire function with the following code.</li> </ol> <h3 is-upgraded id="chat.service.ts_4" data-text="chat.service.ts" tabindex="-1"><a href="https://github.com/firebase/codelab-friendlychat-web/blob/main/angularfire-start/src/app/services/chat.service.ts" target="_blank"><code translate="no" dir="ltr">chat.service.ts</code></a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">// Loads chat message history and listens for upcoming ones. loadMessages = () =&gt; { // Create the query to load the last 12 messages and listen for new ones. const recentMessagesQuery = query(collection(this.firestore, &#39;messages&#39;), orderBy(&#39;timestamp&#39;, &#39;desc&#39;), limit(12)); // Start listening to the query. return collectionData(recentMessagesQuery); } </code></pre> <p>To listen to messages in the database, you create a query on a collection by using the <code translate="no" dir="ltr">collection</code> function to specify which collection the data that you want to listen to is in. In the code above, you&#39;re listening to the changes within the <code translate="no" dir="ltr">messages</code> collection, which is where the chat messages are stored. You&#39;re also applying a limit by only listening to the last 12 messages using <code translate="no" dir="ltr">limit(12)</code> and ordering the messages by date using <code translate="no" dir="ltr">orderBy('timestamp', 'desc')</code> to get the 12 newest messages.</p> <p>The <code translate="no" dir="ltr">collectionData</code> function uses snapshots under the hood. The callback function will be triggered when there are any changes to documents that match the query. This could be if a message gets deleted, modified, or added. You can read more about this in the <a href="https://firebase.google.com/docs/firestore/query-data/listen" target="_blank">Cloud Firestore documentation</a>.</p> <h3 is-upgraded id="test-synchronizing-messages" data-text="Test synchronizing messages" tabindex="-1">Test synchronizing messages</h3> <ol type="1"> <li>Create a commit with commit message &#34;Show new chats in the UI&#34; and push it to your GitHub repository.</li> <li>Open the <a href="https://console.firebase.google.com/project/_/apphosting" target="_blank">App Hosting page</a> in the Firebase console and wait for your new rollout to complete.</li> <li>Refresh FriendlyChat. The messages that you created earlier in the database should be displayed in the FriendlyChat UI (see below). Feel free to write new messages; they should appear instantly.</li> <li><em>(Optional)</em> You can try manually deleting, modifying, or adding new messages directly in the <strong>Firestore</strong> section of the Emulator suite; any changes should be reflected in the UI.</li> </ol> <p>Congratulations! You are reading Cloud Firestore documents in your app!</p> <p class="image-container"><img alt="angularfire-2.png" style="width: 464.18px" src="/static/codelabs/firebase-web/img/angularfire-2.png" srcset="https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-2_36.png 36w,https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-2_48.png 48w,https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-2_72.png 72w,https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-2_96.png 96w,https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-2_480.png 480w,https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-2_720.png 720w,https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-2_856.png 856w,https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-2_960.png 960w,https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-2_1440.png 1440w,https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-2_1920.png 1920w,https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-2_2880.png 2880w" sizes="(max-width: 840px) 100vw, 856px"></p> </google-codelab-step> <google-codelab-step label="Add AI features" duration="0" step="10"> <h2 class="step-title" id="10" data-text="Add AI features" tabindex="-1"> 11. Add AI features </h2> <p>You&#39;ll use Google AI to add useful assistive features to the chat app.</p> <h2 is-upgraded id="get-a-google-ai-api-key" data-text="Get a Google AI API key" tabindex="-1">Get a Google AI API key</h2> <ol type="1"> <li>Navigate to <a href="https://aistudio.google.com/app/apikey" target="_blank">Google AI Studio</a> and click <strong>Create API key</strong></li> <li>Select the Firebase project you created for this codelab. The prompt is for a Google Cloud project, but every Firebase project is a Google Cloud project.</li> <li>Click <strong>Create API key in existing project</strong></li> <li>Copy the resulting API key</li> </ol> <h2 is-upgraded id="install-an-extension" data-text="Install an extension" tabindex="-1">Install an extension</h2> <p>This extension will deploy a cloud function that gets triggered every time a new document is added to the <code translate="no" dir="ltr">messages</code> collection in Firestore. The function will call Gemini, and write its response back to the <code translate="no" dir="ltr">response</code> field in the document.</p> <ol type="1"> <li>Click <strong>Install in Firebase console</strong> on the <a href="https://extensions.dev/extensions/googlecloud/firestore-genai-chatbot" target="_blank">Build Chatbot with the Gemini API</a> extension page.</li> <li>Follow the prompts. Once you get to the <strong>Configure extension</strong> step, set the following parameter values:<ul> <li>Gemini API Provider: <code translate="no" dir="ltr">Google AI</code></li> <li>Google AI API Key: Paste the key you created earlier and click <strong>Create secret</strong>.</li> <li>Firestore collection path: <code translate="no" dir="ltr">messages</code></li> <li>Prompt field: <code translate="no" dir="ltr">text</code></li> <li>Response field: <code translate="no" dir="ltr">response</code></li> <li>Order field: <code translate="no" dir="ltr">timestamp</code></li> <li>Context: <code translate="no" dir="ltr">Keep your answers short, informal, and helpful. Use emojis when possible.</code></li> </ul> </li> <li>Click <strong>install extension</strong></li> <li>Wait for the extension to finish installing</li> </ol> <h2 is-upgraded id="test-ai-feature" data-text="Test AI feature" tabindex="-1">Test AI feature</h2> <p>FriendlyChat already has code to read responses from the AI extension. All you need to do is send a new chat message to test it out!</p> <ol type="1"> <li>Open FriendlyChat and send a message.</li> <li>After a moment, you should see a response pop up alongside your message. It has an <code translate="no" dir="ltr">✨ ai generated</code> note at the end to make it clear that it was created with generative AI, not a real user.</li> </ol> </google-codelab-step> <google-codelab-step label="Send images" duration="10" step="11"> <h2 class="step-title" id="11" data-text="Send images" tabindex="-1"> 12. Send images </h2> <p>You&#39;ll now add a feature that shares images.</p> <p>While Cloud Firestore is good for storing structured data, Cloud Storage is better suited for storing files. <a href="https://firebase.google.com/docs/storage/" target="_blank">Cloud Storage for Firebase</a> is a file/blob storage service, and you&#39;ll use it to store any images that a user shares using our app.</p> <h3 is-upgraded id="save-images-to-cloud-storage" data-text="Save images to Cloud Storage" tabindex="-1">Save images to Cloud Storage</h3> <p>For this codelab, you&#39;ve already added for you a button that triggers a file picker dialog. After selecting a file, the <code translate="no" dir="ltr">saveImageMessage</code> function is called, and you can get a reference to the selected file. The <code translate="no" dir="ltr">saveImageMessage</code> function accomplishes the following:</p> <ol type="1"> <li>Creates a &#34;placeholder&#34; chat message in the chat feed, so that users see a &#34;Loading&#34; animation while you upload the image.</li> <li>Uploads the image file to Cloud Storage to this path: <code translate="no" dir="ltr">/&lt;uid&gt;/&lt;file_name&gt;</code></li> <li>Generates a publicly readable URL for the image file.</li> <li>Updates the chat message with the newly uploaded image file&#39;s URL in lieu of the temporary loading image.</li> </ol> <p>Now you&#39;ll add the functionality to send an image:</p> <ol type="1"> <li>Go back to the file <code translate="no" dir="ltr">src/chat.service.ts</code>.</li> <li>Find the function <code translate="no" dir="ltr">saveImageMessage</code>.</li> <li>Replace the entire function with the following code.</li> </ol> <h3 is-upgraded id="chat.service.ts_5" data-text="chat.service.ts" tabindex="-1"><a href="https://github.com/firebase/codelab-friendlychat-web/blob/main/angularfire-start/src/app/services/chat.service.ts#L81" target="_blank">chat.service.ts</a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">// Saves a new message containing an image in Firestore. // This first saves the image in Firebase storage. saveImageMessage = async(file: any) =&gt; { try { // 1 - Add a message with a loading icon that will get updated with the shared image. const messageRef = await this.addMessage(null, this.LOADING_IMAGE_URL); // 2 - Upload the image to Cloud Storage. const filePath = `${this.auth.currentUser?.uid}/${file.name}`; const newImageRef = ref(this.storage, filePath); const fileSnapshot = await uploadBytesResumable(newImageRef, file); // 3 - Generate a public URL for the file. const publicImageUrl = await getDownloadURL(newImageRef); // 4 - Update the chat message placeholder with the image&#39;s URL. messageRef ? await updateDoc(messageRef, { imageUrl: publicImageUrl, storageUri: fileSnapshot.metadata.fullPath }): null; } catch (error) { console.error(&#39;There was an error uploading a file to Cloud Storage:&#39;, error); } } </code></pre> <h3 is-upgraded id="test-sending-images" data-text="Test sending images" tabindex="-1"><strong>Test sending images</strong></h3> <ol type="1"> <li>Create a commit with commit message &#34;Add the ability to post images&#34; and push it to your GitHub repository.</li> <li>Open the <a href="https://console.firebase.google.com/project/_/apphosting" target="_blank">App Hosting page</a> in the Firebase console and wait for your new rollout to complete.</li> <li>Refresh FriendlyChat. After logging in, click the image upload button on the bottom left <img alt="angularfire-4.png" style="width: 26.61px" src="/static/codelabs/firebase-web/img/angularfire-4.png" srcset="https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-4_36.png 36w,https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-4_48.png 48w,https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-4_72.png 72w,https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-4_96.png 96w,https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-4_480.png 480w,https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-4_720.png 720w,https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-4_856.png 856w,https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-4_960.png 960w,https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-4_1440.png 1440w,https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-4_1920.png 1920w,https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-4_2880.png 2880w" sizes="(max-width: 840px) 100vw, 856px"> and select an image file using the file picker. If you&#39;re looking for an image, feel free to use this <a href="https://www.pexels.com/photo/aroma-aromatic-art-artistic-434213/" target="_blank">nice picture of a coffee cup</a>.</li> <li>A new message should appear in the app&#39;s UI with your selected image: <img alt="angularfire-2.png" style="width: 555.06px" src="/static/codelabs/firebase-web/img/angularfire-2.png" srcset="https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-2_36.png 36w,https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-2_48.png 48w,https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-2_72.png 72w,https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-2_96.png 96w,https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-2_480.png 480w,https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-2_720.png 720w,https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-2_856.png 856w,https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-2_960.png 960w,https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-2_1440.png 1440w,https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-2_1920.png 1920w,https://firebase.google.com/static/codelabs/firebase-web/img/angularfire-2_2880.png 2880w" sizes="(max-width: 840px) 100vw, 856px"></li> </ol> <p>If you try adding an image while not signed in, you should see an error telling you that you must sign in to add images.</p> </google-codelab-step> <google-codelab-step label="Show notifications" duration="10" step="12"> <h2 class="step-title" id="12" data-text="Show notifications" tabindex="-1"> 13. Show notifications </h2> <p>You&#39;ll now add support for browser notifications. The app will notify users when new messages are posted in the chat. <a href="https://firebase.google.com/docs/cloud-messaging/" target="_blank">Firebase Cloud Messaging</a> (FCM) is a cross-platform messaging solution that lets you reliably deliver messages and notifications at no cost.</p> <h3 is-upgraded id="add-the-fcm-service-worker" data-text="Add the FCM service worker" tabindex="-1"><strong>Add the FCM service worker</strong></h3> <p>The web app needs a <a href="https://developer.mozilla.org/en/docs/Web/API/Service_Worker_API" target="_blank">service worker</a> that will receive and display web notifications.</p> <p>The messaging provider should have already been set up when AngularFire was added, make sure that the following code exists in the imports section of <code translate="no" dir="ltr">/angularfire-start/src/app/app.config.ts</code></p> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">provideMessaging(() =&gt; { return getMessaging(); }), </code></pre> <h3 is-upgraded id="appapp.config.ts" data-text="app/app.config.ts" tabindex="-1"><a href="https://github.com/firebase/codelab-friendlychat-web/blob/main/angularfire-start/src/app/app.config.ts" target="_blank"><code translate="no" dir="ltr">app/app.config.ts</code></a></h3> <p>The service worker simply needs to load and initialize the Firebase Cloud Messaging SDK, which will take care of displaying notifications.</p> <h3 is-upgraded id="get-fcm-device-tokens" data-text="Get FCM device tokens" tabindex="-1"><strong>Get FCM device tokens</strong></h3> <p>When notifications have been enabled on a device or browser, you&#39;ll be given a <strong>device token</strong>. This device token is what you use to send a notification to a particular device or particular browser.</p> <p>When the user signs in, you call the <code translate="no" dir="ltr">saveMessagingDeviceToken</code> function. That&#39;s where you&#39;ll get the <strong>FCM device token</strong> from the browser and save it to Cloud Firestore.</p> <h3 is-upgraded id="chat.service.ts_6" data-text="chat.service.ts" tabindex="-1"><a href="https://github.com/firebase/codelab-friendlychat-web/blob/main/angularfire-start/src/app/services/chat.service.ts" target="_blank"><code translate="no" dir="ltr">chat.service.ts</code></a></h3> <ol type="1" start="2"> <li>Find the function <code translate="no" dir="ltr">saveMessagingDeviceToken</code>.</li> <li>Replace the entire function with the following code.</li> </ol> <h3 is-upgraded id="chat.service.ts_7" data-text="chat.service.ts" tabindex="-1"><a href="https://github.com/firebase/codelab-friendlychat-web/blob/main/angularfire-start/src/app/services/chat.service.ts" target="_blank"><code translate="no" dir="ltr">chat.service.ts</code></a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">// Saves the messaging device token to Cloud Firestore. saveMessagingDeviceToken= async () =&gt; { try { const currentToken = await getToken(this.messaging); if (currentToken) { console.log(&#39;Got FCM device token:&#39;, currentToken); // Saving the Device Token to Cloud Firestore. const tokenRef = doc(this.firestore, &#39;fcmTokens&#39;, currentToken); await setDoc(tokenRef, { uid: this.auth.currentUser?.uid }); // This will fire when a message is received while the app is in the foreground. // When the app is in the background, firebase-messaging-sw.js will receive the message instead. onMessage(this.messaging, (message) =&gt; { console.log( &#39;New foreground notification from Firebase Messaging!&#39;, message.notification ); }); } else { // Need to request permissions to show notifications. this.requestNotificationsPermissions(); } } catch(error) { console.error(&#39;Unable to get messaging token.&#39;, error); }; } </code></pre> <p>However, this code won&#39;t work initially. For your app to be able to retrieve the device token, the user needs to grant your app permission to show notifications (the next step of the codelab).</p> <h3 is-upgraded id="request-permissions-to-show-notifications" data-text="Request permissions to show notifications" tabindex="-1"><strong>Request permissions to show notifications</strong></h3> <p>When the user has not yet granted your app permission to show notifications, you won&#39;t be given a device token. In this case, you call the <code translate="no" dir="ltr">requestPermission()</code> method, which will display a browser dialog asking for this permission ( <a href="https://caniuse.com/#feat=push-api" target="_blank">in supported browsers</a>).</p> <p class="image-container"><img alt="8b9d0c66dc36153d.png" style="width: 567.50px" src="/static/codelabs/firebase-web/img/8b9d0c66dc36153d.png" srcset="https://firebase.google.com/static/codelabs/firebase-web/img/8b9d0c66dc36153d_36.png 36w,https://firebase.google.com/static/codelabs/firebase-web/img/8b9d0c66dc36153d_48.png 48w,https://firebase.google.com/static/codelabs/firebase-web/img/8b9d0c66dc36153d_72.png 72w,https://firebase.google.com/static/codelabs/firebase-web/img/8b9d0c66dc36153d_96.png 96w,https://firebase.google.com/static/codelabs/firebase-web/img/8b9d0c66dc36153d_480.png 480w,https://firebase.google.com/static/codelabs/firebase-web/img/8b9d0c66dc36153d_720.png 720w,https://firebase.google.com/static/codelabs/firebase-web/img/8b9d0c66dc36153d_856.png 856w,https://firebase.google.com/static/codelabs/firebase-web/img/8b9d0c66dc36153d_960.png 960w,https://firebase.google.com/static/codelabs/firebase-web/img/8b9d0c66dc36153d_1440.png 1440w,https://firebase.google.com/static/codelabs/firebase-web/img/8b9d0c66dc36153d_1920.png 1920w,https://firebase.google.com/static/codelabs/firebase-web/img/8b9d0c66dc36153d_2880.png 2880w" sizes="(max-width: 840px) 100vw, 856px"></p> <ol type="1"> <li>Go back to the file <code translate="no" dir="ltr">src/app/services/chat.service.ts</code>.</li> <li>Find the function <code translate="no" dir="ltr">requestNotificationsPermissions</code>.</li> <li>Replace the entire function with the following code.</li> </ol> <h3 is-upgraded id="chat.service.ts_8" data-text="chat.service.ts" tabindex="-1"><a href="https://github.com/firebase/codelab-friendlychat-web/blob/main/angularfire-start/src/app/services/chat.service.ts" target="_blank"><code translate="no" dir="ltr">chat.service.ts</code></a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">// Requests permissions to show notifications. requestNotificationsPermissions = async () =&gt; { console.log(&#39;Requesting notifications permission...&#39;); const permission = await Notification.requestPermission(); if (permission === &#39;granted&#39;) { console.log(&#39;Notification permission granted.&#39;); // Notification permission granted. await this.saveMessagingDeviceToken(); } else { console.log(&#39;Unable to get permission to notify.&#39;); } } </code></pre> <h3 is-upgraded id="get-your-device-token" data-text="Get your device token" tabindex="-1"><strong>Get your device token</strong></h3> <ol type="1"> <li>Create a commit with commit message &#34;Add the ability to post images&#34; and push it to your GitHub repository.</li> <li>Open the <a href="https://console.firebase.google.com/project/_/apphosting" target="_blank">App Hosting page</a> in the Firebase console and wait for your new rollout to complete.</li> <li>Refresh FriendlyChat. After logging in, the notifications permission dialog should appear: <img alt="bd3454e6dbfb6723.png" style="width: 480.00px" src="/static/codelabs/firebase-web/img/bd3454e6dbfb6723.png" srcset="https://firebase.google.com/static/codelabs/firebase-web/img/bd3454e6dbfb6723_36.png 36w,https://firebase.google.com/static/codelabs/firebase-web/img/bd3454e6dbfb6723_48.png 48w,https://firebase.google.com/static/codelabs/firebase-web/img/bd3454e6dbfb6723_72.png 72w,https://firebase.google.com/static/codelabs/firebase-web/img/bd3454e6dbfb6723_96.png 96w,https://firebase.google.com/static/codelabs/firebase-web/img/bd3454e6dbfb6723_480.png 480w,https://firebase.google.com/static/codelabs/firebase-web/img/bd3454e6dbfb6723_720.png 720w,https://firebase.google.com/static/codelabs/firebase-web/img/bd3454e6dbfb6723_856.png 856w,https://firebase.google.com/static/codelabs/firebase-web/img/bd3454e6dbfb6723_960.png 960w,https://firebase.google.com/static/codelabs/firebase-web/img/bd3454e6dbfb6723_1440.png 1440w,https://firebase.google.com/static/codelabs/firebase-web/img/bd3454e6dbfb6723_1920.png 1920w,https://firebase.google.com/static/codelabs/firebase-web/img/bd3454e6dbfb6723_2880.png 2880w" sizes="(max-width: 840px) 100vw, 856px"></li> <li>Click <strong>Allow</strong>.</li> <li>Open the JavaScript console of your browser. You should see the following message: <code translate="no" dir="ltr">Got FCM device token: cWL6w:APA91bHP...4jDPL_A-wPP06GJp1OuekTaTZI5K2Tu</code></li> <li>Copy your device token. You&#39;ll need it for the next stage of the codelab.</li> </ol> <h3 is-upgraded id="send-a-notification-to-your-device" data-text="Send a notification to your device" tabindex="-1"><strong>Send a notification to your device</strong></h3> <p>Now that you have your device token, you can send a notification.</p> <ol type="1"> <li>Open the <a href="https://console.firebase.google.com/project/_/notification" target="_blank">Cloud Messaging tab of the Firebase console</a>.</li> <li>Click &#34;New Notification&#34;</li> <li>Enter a notification title and notification text.</li> <li>On the right side of the screen, click &#34;send a test message&#34;</li> <li>Enter the device token you copied from the JavaScript console of your browser, then click the plus (&#34;+&#34;) sign</li> <li>Click &#34;test&#34;</li> </ol> <p>If your app is in the foreground, you&#39;ll see the notification in the JavaScript console.</p> <p>If your app is in the background, a notification should appear in your browser, as in this example:</p> <p class="image-container"><img alt="de79e8638a45864c.png" style="width: 377.78px" src="/static/codelabs/firebase-web/img/de79e8638a45864c.png" srcset="https://firebase.google.com/static/codelabs/firebase-web/img/de79e8638a45864c_36.png 36w,https://firebase.google.com/static/codelabs/firebase-web/img/de79e8638a45864c_48.png 48w,https://firebase.google.com/static/codelabs/firebase-web/img/de79e8638a45864c_72.png 72w,https://firebase.google.com/static/codelabs/firebase-web/img/de79e8638a45864c_96.png 96w,https://firebase.google.com/static/codelabs/firebase-web/img/de79e8638a45864c_480.png 480w,https://firebase.google.com/static/codelabs/firebase-web/img/de79e8638a45864c_720.png 720w,https://firebase.google.com/static/codelabs/firebase-web/img/de79e8638a45864c_856.png 856w,https://firebase.google.com/static/codelabs/firebase-web/img/de79e8638a45864c_960.png 960w,https://firebase.google.com/static/codelabs/firebase-web/img/de79e8638a45864c_1440.png 1440w,https://firebase.google.com/static/codelabs/firebase-web/img/de79e8638a45864c_1920.png 1920w,https://firebase.google.com/static/codelabs/firebase-web/img/de79e8638a45864c_2880.png 2880w" sizes="(max-width: 840px) 100vw, 856px"></p> <aside class="special"><p>In a follow-up codelab, <a href="https://codelabs.developers.google.com/codelabs/firebase-cloud-functions" target="_blank"><strong>Firebase SDK for Cloud Functions</strong></a>, you&#39;ll see how to automate sending notifications from the backend for each new message posted in the chat app.</p> </aside> </google-codelab-step> <google-codelab-step label="Cloud Firestore security rules" duration="5" step="13"> <h2 class="step-title" id="13" data-text="Cloud Firestore security rules" tabindex="-1"> 14. Cloud Firestore security rules </h2> <h3 is-upgraded id="view-database-security-rules" data-text="View database security rules" tabindex="-1">View <strong>database security rules</strong></h3> <p>Cloud Firestore uses a specific <a href="https://firebase.google.com/docs/firestore/security/overview" target="_blank">rules language</a> to define access rights, security, and data validations.</p> <p>When setting up the Firebase project at the beginning of this codelab, you chose to use &#34;Test mode&#34; default security rules so that you didn&#39;t restrict access to the datastore. In the <a href="https://console.firebase.google.com" target="_blank">Firebase console</a>, in the <strong>Database</strong> section&#39;s <strong>Rules</strong> tab, you can view and modify these rules.</p> <p>Right now, you should see the default rules, which do not restrict access to the datastore. This means that any user can read and write to any collections in your datastore.</p> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">rules_version = &#39;2&#39;; service cloud.firestore { match /databases/{database}/documents { match /{document=**} { allow read, write; } } } </code></pre> <p>You&#39;ll update the rules to restrict things by using the following rules:</p> <h3 is-upgraded id="firestore.rules" data-text="firestore.rules" tabindex="-1"><a href="https://github.com/firebase/friendlychat/blob/master/angularfire-start/firestore.rules" target="_blank">firestore.rules</a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">rules_version = &#39;2&#39;; service cloud.firestore { match /databases/{database}/documents { // Messages: // - Anyone can read. // - Authenticated users can add and edit messages. // - Validation: Check name is same as auth token and text length below 300 char or that imageUrl is a URL. // - Deletes are not allowed. match /messages/{messageId} { allow read; allow create, update: if request.auth != null &amp;&amp; request.resource.data.name == request.auth.token.name &amp;&amp; (request.resource.data.text is string &amp;&amp; request.resource.data.text.size() &lt;= 300 || request.resource.data.imageUrl is string &amp;&amp; request.resource.data.imageUrl.matches(&#39;https?://.*&#39;)); allow delete: if false; } // FCM Tokens: // - Anyone can write their token. // - Reading list of tokens is not allowed. match /fcmTokens/{token} { allow read: if false; allow write; } } } </code></pre> <aside class="special"><p>The <code translate="no" dir="ltr">request.auth</code> rule variable is a special variable containing information about an authenticated user. The <code translate="no" dir="ltr">request.resource</code> rule variable points to the new data being written. More information can be found in <a href="https://firebase.google.com/docs/firestore/security/overview" target="_blank">the documentation</a>.</p> </aside> <p>The security rules should update automatically to your Emulator suite.</p> <h3 is-upgraded id="view-cloud-storage-security-rules" data-text="View Cloud Storage security rules" tabindex="-1"><strong>View Cloud Storage security rules</strong></h3> <p>Cloud Storage for Firebase uses a specific <a href="https://firebase.google.com/docs/storage/security/start" target="_blank">rules language</a> to define access rights, security, and data validations.</p> <p>When setting up the Firebase project at the beginning of this codelab, you chose to use the default Cloud Storage security rule that only allows authenticated users to use Cloud Storage. In the <a href="https://console.firebase.google.com" target="_blank">Firebase console</a>, in the <strong>Storage</strong> section&#39;s <strong>Rules</strong> tab, you can view and modify rules. You should see the default rule which allows any signed-in user to read and write any files in your storage bucket.</p> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">rules_version = &#39;2&#39;; service firebase.storage { match /b/{bucket}/o { match /{allPaths=**} { allow read, write: if request.auth != null; } } } </code></pre> <p>You&#39;ll update the rules to do the following:</p> <ul> <li>Allow each user to write only to their own specific folders</li> <li>Allow anyone to read from Cloud Storage</li> <li>Make sure that the files uploaded are images</li> <li>Restrict the size of the images that can be uploaded to maximum 5 MB</li> </ul> <p>This can be implemented using the following rules:</p> <h3 is-upgraded id="storage.rules" data-text="storage.rules" tabindex="-1"><a href="https://github.com/firebase/friendlychat/blob/master/angularfire-start/storage.rules" target="_blank">storage.rules</a></h3> <pre class="prettyprint" translate="no" dir="ltr"><code translate="no" dir="ltr">rules_version = &#39;2&#39;; // Returns true if the uploaded file is an image and its size is below the given number of MB. function isImageBelowMaxSize(maxSizeMB) { return request.resource.size &lt; maxSizeMB * 1024 * 1024 &amp;&amp; request.resource.contentType.matches(&#39;image/.*&#39;); } service firebase.storage { match /b/{bucket}/o { match /{userId}/{messageId}/{fileName} { allow write: if request.auth != null &amp;&amp; request.auth.uid == userId &amp;&amp; isImageBelowMaxSize(5); allow read; } } } </code></pre> <aside class="special"><p>The <code translate="no" dir="ltr">request.auth</code> rule variable is a special variable containing information about an authenticated user. The <code translate="no" dir="ltr">request.resource</code> rule variable contains information about the uploaded file. More information can be found in <a href="https://firebase.google.com/docs/storage/security/start" target="_blank">the documentation</a>.</p> </aside> </google-codelab-step> <google-codelab-step label="Congratulations!" duration="0" step="14"> <h2 class="step-title" id="14" data-text="Congratulations!" tabindex="-1"> 15. Congratulations! </h2> <p>You&#39;ve used Firebase to build a real-time chat web application!</p> <h3 is-upgraded id="what-youve-covered" data-text="What you've covered" tabindex="-1"><strong>What you've covered</strong></h3> <ul> <li>Firebase App Hosting</li> <li>Firebase Authentication</li> <li>Cloud Firestore</li> <li>Firebase SDK for Cloud Storage</li> <li>Firebase Cloud Messaging</li> <li>Firebase Performance Monitoring</li> </ul> <h3 is-upgraded id="next-steps" data-text="Next steps" tabindex="-1">Next steps</h3> <aside class="special"><p>Continue to the <a href="https://codelabs.developers.google.com/codelabs/firebase-cloud-functions/#4" target="_blank"><strong>Cloud Functions for Firebase</strong></a> codelab to learn how to use the Firebase SDK for Cloud Functions and add some backend tasks to your chat app. You can start directly on Step 5 of that codelab since you&#39;ve already set up your Firebase project.</p> </aside> <aside class="special"><p>Want to learn more about Cloud Firestore? Maybe you want to learn about subcollections and transactions? Head over to the <a href="https://codelabs.developers.google.com/codelabs/firestore-web/#0" target="_blank"><strong>Cloud Firestore web codelab</strong></a> for a codelab that goes into greater depth on Cloud Firestore.</p> </aside> <aside class="special"><p>Want to learn more about Firebase Performance Monitoring for Web? Head over to the <a href="https://codelabs.developers.google.com/codelabs/firebase-perf-mon-web/" target="_blank"><strong>Firebase performance monitoring for web codelab</strong></a> for a codelab that goes into greater depth on Firebase Performance Monitoring.</p> </aside> <h3 is-upgraded id="learn-more" data-text="Learn more" tabindex="-1"><strong>Learn more</strong></h3> <ul> <li><a href="https://firebase.google.com" target="_blank">firebase.google.com</a></li> </ul> </google-codelab-step> <google-codelab-step label="[Optional] Enforce with App Check" duration="5" step="15"> <h2 class="step-title" id="15" data-text="[Optional] Enforce with App Check" tabindex="-1"> 16. [Optional] Enforce with App Check </h2> <p>Firebase <a href="https://firebase.google.com/docs/app-check" target="_blank">App Check</a> helps secure your services from unwanted traffic and helps protect your backend from abuse. In this step, you&#39;ll add credentials validation and block unauthorized clients with App Check and <a href="https://cloud.google.com/recaptcha-enterprise" target="_blank">reCAPTCHA Enterprise</a>.</p> <aside class="special"><p> This step depends on the completion of previous steps, make sure that FriendlyChat is running locally with emulators or deployed with Firebase services.</p> </aside> <p>First, you&#39;ll need to enable App Check and reCaptcha.</p> <h2 is-upgraded id="enabling-recaptcha-enterprise" data-text="Enabling reCaptcha Enterprise" tabindex="-1"><a href="https://firebase.google.com/docs/app-check/web/recaptcha-enterprise-provider" target="_blank">Enabling reCaptcha Enterprise</a></h2> <ol type="1"> <li>In the Cloud console, find and select <a href="https://console.cloud.google.com/security/recaptcha?project=_" target="_blank">reCaptcha Enterprise</a> under Security.</li> <li>Enable the service as prompted, and click <strong>Create Key</strong>.</li> <li>Input a display name as prompted, and select <strong>Website</strong> as your platform type.</li> <li>Add your deployed URLs to the <strong>Domain list</strong>, and make sure that the &#34;Use checkbox challenge&#34; option is <strong>unselected</strong>.</li> <li>Click <strong>Create Key</strong>, and store the generated key somewhere for safekeeping. You will need it later in this step.</li> </ol> <h2 is-upgraded id="enabling-app-check" data-text="Enabling App Check" tabindex="-1"><a href="https://firebase.google.com/docs/app-check/enable-enforcement" target="_blank">Enabling App Check</a></h2> <ol type="1"> <li>In the Firebase console, locate the <strong>Build</strong> section in the left panel.</li> <li>Click <strong>App Check</strong>, then click the <strong>Sign-in method</strong> tab to navigate to <a href="https://firebase.google.com/project/_/appcheck" target="_blank">App Check</a>.</li> <li>Click <strong>Register</strong> and enter your reCaptcha Enterprise key when prompted, then click <strong>Save</strong>.</li> <li>In the APIs View, select <strong>Storage</strong> and click <strong>Enforce</strong>. Do the same for <strong>Cloud Firestore</strong>.</li> </ol> <p>App Check should now be enforced! Refresh your app and try to view or send chat messages. You should get the error message:</p> <pre translate="no" dir="ltr">Uncaught Error in snapshot listener: FirebaseError: [code=permission-denied]: Missing or insufficient permissions. </pre> <p>This means App Check is blocking unvalidated requests by default. Now let&#39;s add validation to your app.</p> <p>Navigate to your <a href="https://github.com/firebase/codelab-friendlychat-web/blob/main/angularfire-start/src/environments/environment.ts" target="_blank"><code translate="no" dir="ltr">environment.ts</code></a> file and add <code translate="no" dir="ltr">reCAPTCHAEnterpriseKey</code> to the <code translate="no" dir="ltr">environment</code> object.</p> <pre class="prettyprint" translate="no" dir="ltr"><code language="language-javascript" class="language-javascript" translate="no" dir="ltr">export const environment = { firebase: { apiKey: &#39;API_KEY&#39;, authDomain: &#39;PROJECT_ID.firebaseapp.com&#39;, databaseURL: &#39;https://PROJECT_ID.firebaseio.com&#39;, projectId: &#39;PROJECT_ID&#39;, storageBucket: &#39;PROJECT_ID.firebasestorage.app&#39;, messagingSenderId: &#39;SENDER_ID&#39;, appId: &#39;APP_ID&#39;, measurementId: &#39;G-MEASUREMENT_ID&#39;, }, reCAPTCHAEnterpriseKey: { key: &#34;Replace with your recaptcha enterprise site key&#34; }, }; </code></pre> <p>Replace the value of <code translate="no" dir="ltr">key</code> with your reCaptcha Enterprise token.</p> <p>Then, navigate to <a href="https://github.com/firebase/codelab-friendlychat-web/blob/main/angularfire-start/src/app/app.config.ts" target="_blank"><code translate="no" dir="ltr">app.config.ts</code></a> file and add the following imports:</p> <pre class="prettyprint" translate="no" dir="ltr"><code language="language-javascript" class="language-javascript" translate="no" dir="ltr">import { getApp } from &#39;@angular/fire/app&#39;; import { ReCaptchaEnterpriseProvider, initializeAppCheck, provideAppCheck, } from &#39;@angular/fire/app-check&#39;; </code></pre> <p>In the same <a href="https://github.com/firebase/codelab-friendlychat-web/blob/main/angularfire-start/src/app/app.config.ts" target="_blank"><code translate="no" dir="ltr">app.config.ts</code></a> file, add the following global variable declaration:</p> <pre class="prettyprint" translate="no" dir="ltr"><code language="language-javascript" class="language-javascript" translate="no" dir="ltr">declare global { var FIREBASE_APPCHECK_DEBUG_TOKEN: boolean; } @NgModule({ ... </code></pre> <p>In imports, add initialization of App Check with <code translate="no" dir="ltr">ReCaptchaEnterpriseProvider</code>, and set <code translate="no" dir="ltr">isTokenAutoRefreshEnabled</code> to <code translate="no" dir="ltr">true</code> to allow tokens to auto-refresh.</p> <pre class="prettyprint" translate="no" dir="ltr"><code language="language-javascript" class="language-javascript" translate="no" dir="ltr">imports: [ BrowserModule, AppRoutingModule, CommonModule, FormsModule, provideFirebaseApp(() =&gt; initializeApp(environment.firebase)), provideAppCheck(() =&gt; { const appCheck = initializeAppCheck(getApp(), { provider: new ReCaptchaEnterpriseProvider( environment.reCAPTCHAEnterpriseKey.key ), isTokenAutoRefreshEnabled: true, }); if (location.hostname === &#39;localhost&#39;) { self.FIREBASE_APPCHECK_DEBUG_TOKEN = true; } return appCheck; }), </code></pre> <p>To allow local testing, set <code translate="no" dir="ltr">self.FIREBASE_APPCHECK_DEBUG_TOKEN</code> to <code translate="no" dir="ltr">true</code>. When you refresh your app in <code translate="no" dir="ltr">localhost</code>, this will log a debug token in the console similar to:</p> <pre translate="no" dir="ltr">App Check debug token: CEFC0C76-7891-494B-B764-349BDFD00D00. You will need to add it to your app&#39;s App Check settings in the Firebase console for it to work. </pre> <p>Now, go to the <a href="https://firebase.google.com/project/_/appcheck/apps" target="_blank">Apps View</a> of App Check in the Firebase console.</p> <p>Click the overflow menu, and select <strong>Manage debug tokens</strong>.</p> <p>Then, click <strong>Add debug token</strong> and paste the debug token from your console as prompted.</p> <p>Navigate to the <a href="https://github.com/firebase/codelab-friendlychat-web/blob/main/angularfire-start/src/app/services/chat.service.ts" target="_blank"><code translate="no" dir="ltr">chat.service.ts</code></a> file, and add the following import:</p> <pre class="prettyprint" translate="no" dir="ltr"><code language="language-javascript" class="language-javascript" translate="no" dir="ltr">import { AppCheck } from &#39;@angular/fire/app-check&#39;; </code></pre> <p>In the same <a href="https://github.com/firebase/codelab-friendlychat-web/blob/main/angularfire-start/src/app/services/chat.service.ts" target="_blank"><code translate="no" dir="ltr">chat.service.ts</code></a> file, inject App Check alongside the other Firebase services.</p> <pre class="prettyprint" translate="no" dir="ltr"><code language="language-javascript" class="language-javascript" translate="no" dir="ltr">export class ChatService { appCheck: AppCheck = inject(AppCheck); ... </code></pre> <ol type="1"> <li>Create a commit with commit message &#34;Block unauthorized clients with App Check&#34; and push it to your GitHub repository.</li> <li>Open the <a href="https://console.firebase.google.com/project/_/apphosting" target="_blank">App Hosting page</a> in the Firebase console and wait for your new rollout to complete.</li> </ol> <p>Congratulations! App Check should now be working in your app.</p> </google-codelab-step> </google-codelab> </div> <div class="devsite-floating-action-buttons"> </div> </article> <devsite-content-footer class="nocontent"> <p>Except as otherwise noted, the content of this page is licensed under the <a href="https://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution 4.0 License</a>, and code samples are licensed under the <a href="https://www.apache.org/licenses/LICENSE-2.0">Apache 2.0 License</a>. For details, see the <a href="https://developers.google.com/site-policies">Google Developers Site Policies</a>. Java is a registered trademark of Oracle and/or its affiliates.</p> </devsite-content-footer> <devsite-notification > </devsite-notification> <div class="devsite-content-data"> <template class="devsite-content-data-template"> [[["Easy to understand","easyToUnderstand","thumb-up"],["Solved my problem","solvedMyProblem","thumb-up"],["Other","otherUp","thumb-up"]],[["Missing the information I need","missingTheInformationINeed","thumb-down"],["Too complicated / too many steps","tooComplicatedTooManySteps","thumb-down"],["Out of date","outOfDate","thumb-down"],["Samples / code issue","samplesCodeIssue","thumb-down"],["Other","otherDown","thumb-down"]],[],[],[]] </template> </div> </devsite-content> </main> <devsite-footer-promos class="devsite-footer"> </devsite-footer-promos> <devsite-footer-linkboxes class="devsite-footer"> <nav class="devsite-footer-linkboxes nocontent" aria-label="Footer links"> <ul class="devsite-footer-linkboxes-list"> <li class="devsite-footer-linkbox "> <h3 class="devsite-footer-linkbox-heading no-link">Learn</h3> <ul class="devsite-footer-linkbox-list"> <li class="devsite-footer-linkbox-item"> <a href="/docs/" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 1)" > Developer guides </a> </li> <li class="devsite-footer-linkbox-item"> <a href="/docs/reference/" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 2)" > SDK & API reference </a> </li> <li class="devsite-footer-linkbox-item"> <a href="/docs/samples/" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 3)" > Samples </a> </li> <li class="devsite-footer-linkbox-item"> <a href="/docs/libraries/" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 4)" > Libraries </a> </li> <li class="devsite-footer-linkbox-item"> <a href="//github.com/firebase/" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 5)" > GitHub </a> </li> </ul> </li> <li class="devsite-footer-linkbox "> <h3 class="devsite-footer-linkbox-heading no-link">Stay connected</h3> <ul class="devsite-footer-linkbox-list"> <li class="devsite-footer-linkbox-item"> <a href="//firebase.blog" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 1)" > Check out the blog </a> </li> <li class="devsite-footer-linkbox-item"> <a href="//www.reddit.com/r/Firebase" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 2)" > Find us on Reddit </a> </li> <li class="devsite-footer-linkbox-item"> <a href="//x.com/Firebase" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 3)" > Follow on X </a> </li> <li class="devsite-footer-linkbox-item"> <a href="//www.youtube.com/user/Firebase" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 4)" > Subscribe on YouTube </a> </li> <li class="devsite-footer-linkbox-item"> <a href="/community/events" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 5)" > Attend an event </a> </li> </ul> </li> <li class="devsite-footer-linkbox "> <h3 class="devsite-footer-linkbox-heading no-link">Support</h3> <ul class="devsite-footer-linkbox-list"> <li class="devsite-footer-linkbox-item"> <a href="/support/" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 1)" > Contact support </a> </li> <li class="devsite-footer-linkbox-item"> <a href="//stackoverflow.com/questions/tagged/firebase" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 2)" > Stack Overflow </a> </li> <li class="devsite-footer-linkbox-item"> <a href="//firebase.community/" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 3)" > Slack community </a> </li> <li class="devsite-footer-linkbox-item"> <a href="//groups.google.com/forum/#!forum/firebase-talk" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 4)" > Google group </a> </li> <li class="devsite-footer-linkbox-item"> <a href="/support/releases" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 5)" > Release notes </a> </li> <li class="devsite-footer-linkbox-item"> <a href="/brand-guidelines/" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 6)" > Brand guidelines </a> </li> <li class="devsite-footer-linkbox-item"> <a href="/support/faq/" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 7)" > FAQs </a> </li> </ul> </li> </ul> </nav> </devsite-footer-linkboxes> <devsite-footer-utility class="devsite-footer"> <div class="devsite-footer-utility nocontent"> <nav class="devsite-footer-sites" aria-label="Other Google Developers websites"> <a href="https://developers.google.com/" class="devsite-footer-sites-logo-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Google Developers Link"> <picture> <source srcset="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/firebase/images/lockup-google-for-developers-dark-theme.svg" media="(prefers-color-scheme: none)" class="devsite-dark-theme" loading="lazy" alt="Google Developers"> <img class="devsite-footer-sites-logo" src="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/firebase/images/lockup-google-for-developers.svg" loading="lazy" alt="Google Developers"> </picture> </a> <ul class="devsite-footer-sites-list"> <li class="devsite-footer-sites-item"> <a href="//developer.android.com" class="devsite-footer-sites-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Android Link" > Android </a> </li> <li class="devsite-footer-sites-item"> <a href="//developer.chrome.com/home" class="devsite-footer-sites-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Chrome Link" > Chrome </a> </li> <li class="devsite-footer-sites-item"> <a href="//firebase.google.com" class="devsite-footer-sites-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Firebase Link" > Firebase </a> </li> <li class="devsite-footer-sites-item"> <a href="//cloud.google.com" class="devsite-footer-sites-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Google Cloud Platform Link" > Google Cloud Platform </a> </li> <li class="devsite-footer-sites-item"> <a href="//developers.google.com/products/" class="devsite-footer-sites-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer All products Link" > All products </a> </li> </ul> </nav> <nav class="devsite-footer-utility-links" aria-label="Utility links"> <ul class="devsite-footer-utility-list"> <li class="devsite-footer-utility-item "> <a class="devsite-footer-utility-link gc-analytics-event" href="/terms/" data-category="Site-Wide Custom Events" data-label="Footer Terms link" > Terms </a> </li> <li class="devsite-footer-utility-item "> <a class="devsite-footer-utility-link gc-analytics-event" href="//policies.google.com/privacy" data-category="Site-Wide Custom Events" data-label="Footer Privacy link" > Privacy </a> </li> <li class="devsite-footer-utility-item glue-cookie-notification-bar-control"> <a class="devsite-footer-utility-link gc-analytics-event" href="#" data-category="Site-Wide Custom Events" data-label="Footer Manage cookies link" aria-hidden="true" > Manage cookies </a> </li> </ul> <devsite-language-selector> <ul role="presentation"> <li role="presentation"> <a role="menuitem" lang="en" >English</a> </li> <li role="presentation"> <a role="menuitem" lang="de" >Deutsch</a> </li> <li role="presentation"> <a role="menuitem" lang="es" >Español</a> </li> <li role="presentation"> <a role="menuitem" lang="es_419" >Español – América Latina</a> </li> <li role="presentation"> <a role="menuitem" lang="fr" >Français</a> </li> <li role="presentation"> <a role="menuitem" lang="id" >Indonesia</a> </li> <li role="presentation"> <a role="menuitem" lang="it" >Italiano</a> </li> <li role="presentation"> <a role="menuitem" lang="pl" >Polski</a> </li> <li role="presentation"> <a role="menuitem" lang="pt" >Português</a> </li> <li role="presentation"> <a role="menuitem" lang="pt_br" >Português – Brasil</a> </li> <li role="presentation"> <a role="menuitem" lang="vi" >Tiếng Việt</a> </li> <li role="presentation"> <a role="menuitem" lang="tr" >Türkçe</a> </li> <li role="presentation"> <a role="menuitem" lang="ru" >Русский</a> </li> <li role="presentation"> <a role="menuitem" lang="he" >עברית</a> </li> <li role="presentation"> <a role="menuitem" lang="ar" >العربيّة</a> </li> <li role="presentation"> <a role="menuitem" lang="fa" >فارسی</a> </li> <li role="presentation"> <a role="menuitem" lang="hi" >हिंदी</a> </li> <li role="presentation"> <a role="menuitem" lang="bn" >বাংলা</a> </li> <li role="presentation"> <a role="menuitem" lang="th" >ภาษาไทย</a> </li> <li role="presentation"> <a role="menuitem" lang="zh_cn" >中文 – 简体</a> </li> <li role="presentation"> <a role="menuitem" lang="zh_tw" >中文 – 繁體</a> </li> <li role="presentation"> <a role="menuitem" lang="ja" >日本語</a> </li> <li role="presentation"> <a role="menuitem" lang="ko" >한국어</a> </li> </ul> </devsite-language-selector> </nav> </div> </devsite-footer-utility> <devsite-panel></devsite-panel> <devsite-concierge data-info-panel data-ai-panel > </devsite-concierge> </section></section> <devsite-sitemask></devsite-sitemask> <devsite-snackbar></devsite-snackbar> <devsite-tooltip ></devsite-tooltip> <devsite-heading-link></devsite-heading-link> <devsite-analytics> <script type="application/json" analytics>[]</script> <script type="application/json" tag-management>{&#34;at&#34;: &#34;True&#34;, &#34;ga4&#34;: [], &#34;ga4p&#34;: [], &#34;gtm&#34;: [{&#34;id&#34;: &#34;GTM-N84485&#34;, &#34;purpose&#34;: 0}], &#34;parameters&#34;: {&#34;internalUser&#34;: &#34;False&#34;, &#34;language&#34;: {&#34;machineTranslated&#34;: &#34;False&#34;, &#34;requested&#34;: &#34;en&#34;, &#34;served&#34;: &#34;en&#34;}, &#34;pageType&#34;: &#34;codelab&#34;, &#34;projectName&#34;: null, &#34;signedIn&#34;: &#34;False&#34;, &#34;tenant&#34;: &#34;firebase&#34;, &#34;recommendations&#34;: {&#34;sourcePage&#34;: &#34;&#34;, &#34;sourceType&#34;: 0, &#34;sourceRank&#34;: 0, &#34;sourceIdenticalDescriptions&#34;: 0, &#34;sourceTitleWords&#34;: 0, &#34;sourceDescriptionWords&#34;: 0, &#34;experiment&#34;: &#34;&#34;}, &#34;experiment&#34;: {&#34;ids&#34;: &#34;&#34;}}}</script> </devsite-analytics> <devsite-badger></devsite-badger> <firebase-gtm></firebase-gtm> <firebase-utm></firebase-utm> <script nonce="J4T7Nv9NpleqrPNiYTRhxPhXfKB6gI"> (function(d,e,v,s,i,t,E){d['GoogleDevelopersObject']=i; t=e.createElement(v);t.async=1;t.src=s;E=e.getElementsByTagName(v)[0]; E.parentNode.insertBefore(t,E);})(window, document, 'script', 'https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/firebase/js/app_loader.js', '[4,"en",null,"/js/devsite_app_module.js","https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625","https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/firebase","https://firebase-dot-devsite-v2-prod.appspot.com",1,null,["/_pwa/firebase/manifest.json","https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/images/video-placeholder.svg","https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/firebase/images/favicon.png","https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/firebase/images/lockup.svg","https://fonts.googleapis.com/css?family=Google+Sans:400,500|Roboto:400,400italic,500,500italic,700,700italic|Roboto+Mono:400,500,700&display=swap"],1,null,[1,6,8,12,14,17,21,25,50,52,63,70,75,76,80,87,91,92,93,97,98,100,101,102,103,104,105,107,108,109,110,112,113,117,118,120,122,124,125,126,127,129,130,131,132,133,134,135,136,138,140,141,147,148,149,151,152,156,157,158,159,161,163,164,168,169,170,179,180,182,183,186,191,193,196],"AIzaSyAP-jjEJBzmIyKR4F-3XITp8yM9T1gEEI8","AIzaSyB6xiKGDR5O3Ak2okS4rLkauxGUG7XP0hg","firebase.google.com","AIzaSyAQk0fBONSGUqCNznf6Krs82Ap1-NV6J4o","AIzaSyCCxcqdrZ_7QMeLCRY20bh_SXdAYqy70KY",null,null,null,["Profiles__enable_page_saving","Profiles__enable_profile_collections","Cloud__enable_cloud_dlp_service","Search__enable_ai_eligibility_checks","Cloud__enable_cloudx_experiment_ids","Experiments__reqs_query_experiments","Cloud__enable_llm_concierge_chat","Profiles__enable_recognition_badges","DevPro__enable_cloud_innovators_plus","EngEduTelemetry__enable_engedu_telemetry","Profiles__enable_public_developer_profiles","Cloud__enable_free_trial_server_call","Concierge__enable_pushui","MiscFeatureFlags__emergency_css","Profiles__require_profile_eligibility_for_signin","Cloud__enable_cloud_shell","MiscFeatureFlags__enable_view_transitions","CloudShell__cloud_shell_button","Profiles__enable_developer_profiles_callout","Cloud__enable_cloud_facet_chat","MiscFeatureFlags__enable_explain_this_code","Search__enable_dynamic_content_confidential_banner","CloudShell__cloud_code_overflow_menu","MiscFeatureFlags__enable_dark_theme","MiscFeatureFlags__developers_footer_dark_image","MiscFeatureFlags__enable_firebase_utm","Profiles__enable_completecodelab_endpoint","Profiles__enable_awarding_url","TpcFeatures__enable_required_headers","Profiles__enable_complete_playlist_endpoint","Analytics__enable_clearcut_logging","Profiles__enable_release_notes_notifications","MiscFeatureFlags__enable_variable_operator","Significatio__enable_by_tenant","Search__enable_ai_search_summaries","MiscFeatureFlags__enable_project_variables","Search__enable_page_map","Profiles__enable_dashboard_curated_recommendations","TpcFeatures__enable_mirror_tenant_redirects","Concierge__enable_concierge","Search__enable_suggestions_from_borg","Cloud__enable_legacy_calculator_redirect","BookNav__enable_tenant_cache_key","Cloud__enable_cloudx_ping","MiscFeatureFlags__developers_footer_image","Cloud__enable_cloud_shell_fte_user_flow","DevPro__enable_developer_subscriptions"],null,null,"AIzaSyBLEMok-5suZ67qRPzx0qUtbnLmyT_kCVE","https://developerscontentserving-pa.clients6.google.com","AIzaSyCM4QpTRSqP5qI4Dvjt4OAScIN8sOUlO-k","https://developerscontentsearch-pa.clients6.google.com",1,4,null,"https://developerprofiles-pa.clients6.google.com",[4,"firebase","Firebase","firebase.google.com",null,"firebase-dot-devsite-v2-prod.appspot.com",null,null,[1,1,null,null,null,null,null,null,null,null,null,[1],null,null,null,null,null,null,[1],[1,null,null,[1]],null,null,null,[1,null,1],[1,1,null,null,1]],null,[68,null,null,null,null,null,"/images/lockup.svg","/images/touchicon-180.png",null,null,null,1,1,1,null,null,null,null,null,null,null,2,null,null,null,"/images/lockup.svg",[]],[],null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,[6,1,20,22,23,29,37],null,[[],[1,1]],[[null,null,null,null,["UA-24532603-9"],["GTM-N84485"],null,null,null,null,[["UA-24532603-9",1]],[["GTM-N84485",1]],1],[[46,8],[2,5],[16,2],[17,1],[36,4]],null,1],null,4],null,"pk_live_5170syrHvgGVmSx9sBrnWtA5luvk9BwnVcvIi7HizpwauFG96WedXsuXh790rtij9AmGllqPtMLfhe2RSwD6Pn38V00uBCydV4m"]') </script> <devsite-a11y-announce></devsite-a11y-announce> </body> </html>

Pages: 1 2 3 4 5 6 7 8 9 10