1. Что вы создадите
В этой лаборатории кода вы создадите блог о путешествиях с совместной картой в реальном времени с использованием новейшей версии нашей библиотеки Angular: AngularFire . Окончательное веб-приложение будет состоять из блога о путешествиях, куда вы сможете загружать изображения каждого места, куда вы побывали.
AngularFire будет использоваться для создания веб-приложения, Emulator Suite для локального тестирования, аутентификации для отслеживания пользовательских данных, Firestore и Storage для сохранения данных и мультимедиа на базе облачных функций и, наконец, хостинга Firebase для развертывания приложения.
Что вы узнаете
- Как разрабатывать продукты Firebase локально с помощью Emulator Suite
- Как улучшить ваше веб-приложение с помощью AngularFire
- Как сохранить ваши данные в Firestore
- Как сохранить носитель в хранилище
- Как развернуть ваше приложение на хостинге Firebase
- Как использовать облачные функции для взаимодействия с вашими базами данных и API
Что вам понадобится
- Node.js версии 10 или выше
- Аккаунт Google для создания и управления вашим проектом Firebase.
- Firebase CLI версии 11.14.2 или новее.
- Браузер по вашему выбору, например Chrome.
- Базовое понимание Angular и Javascript
2. Получите пример кода
Клонируйте репозиторий GitHub codelab из командной строки:
git clone https://github.com/firebase/codelab-friendlychat-web
Альтернативно, если у вас не установлен git, вы можете загрузить репозиторий в виде ZIP-файла .
Репозиторий Github содержит примеры проектов для нескольких платформ.
Эта лаборатория кода использует только репозиторий веб-фреймворка:
- 📁 webframework : начальный код, который вы будете использовать во время этой лабораторной работы.
Установить зависимости
После клонирования установите зависимости в корневую папку и папку functions
, прежде чем создавать веб-приложение.
cd webframework && npm install
cd functions && npm install
Установите интерфейс командной строки Firebase
Установите Firebase CLI, используя эту команду в терминале:
npm install -g firebase-tools
Дважды проверьте, что ваша версия Firebase CLI выше 11.14.2, используя:
firebase --version
Если ваша версия ниже 11.14.2, обновите, используя:
npm update firebase-tools
3. Создайте и настройте проект Firebase.
Создать проект Firebase
- Войдите в Firebase .
- В консоли Firebase нажмите «Добавить проект» и назовите свой проект Firebase <your-project> . Запомните идентификатор вашего проекта Firebase.
- Нажмите Создать проект .
Важно : ваш проект Firebase будет называться <ваш-проект> , но Firebase автоматически присвоит ему уникальный идентификатор проекта в форме <ваш-проект>-1234 . Этот уникальный идентификатор фактически идентифицирует ваш проект (в том числе в CLI), тогда как <ваш-проект> — это просто отображаемое имя.
Приложение, которое мы собираемся создать, использует продукты Firebase, доступные для веб-приложений:
- Аутентификация Firebase , позволяющая пользователям легко входить в ваше приложение.
- Cloud Firestore для сохранения структурированных данных в облаке и мгновенного получения уведомлений при изменении данных.
- Облачное хранилище для Firebase для сохранения файлов в облаке.
- Хостинг Firebase для размещения и обслуживания ваших ресурсов.
- Функции для взаимодействия с внутренними и внешними API.
Некоторые из этих продуктов требуют специальных конфигураций или их необходимо включить с помощью консоли Firebase.
Добавьте веб-приложение Firebase в проект
- Нажмите значок веб-сайта, чтобы создать новое веб-приложение Firebase.
- На следующем шаге вы увидите объект конфигурации. Скопируйте содержимое этого объекта в файл
environments/environment.ts
.
Включите вход в Google для аутентификации Firebase
Чтобы пользователи могли входить в веб-приложение со своими учетными записями Google, мы будем использовать метод входа Google .
Чтобы включить вход в Google :
- В консоли Firebase найдите раздел «Сборка» на левой панели.
- Нажмите «Аутентификация» , затем перейдите на вкладку «Метод входа» (или нажмите здесь, чтобы перейти непосредственно туда).
- Включите службу входа в систему Google , затем нажмите «Сохранить» .
- Установите общедоступное имя вашего приложения на <имя-вашего-проекта> и выберите адрес электронной почты поддержки проекта из раскрывающегося меню.
Включить Cloud Firestore
- В разделе «Сборка » консоли Firebase нажмите «База данных Firestore» .
- Нажмите Создать базу данных на панели Cloud Firestore.
- Укажите место, где хранятся данные Cloud Firestore. Вы можете оставить это значение по умолчанию или выбрать ближайший к вам регион.
Включить облачное хранилище
Веб-приложение использует Cloud Storage for Firebase для хранения, загрузки и обмена изображениями.
- В разделе «Сборка » консоли Firebase нажмите « Хранилище» .
- Если кнопки «Начать» нет, это означает, что облачное хранилище уже
включен, и вам не нужно выполнять действия, описанные ниже.
- Нажмите «Начать» .
- Прочтите заявление об отказе от ответственности о правилах безопасности для вашего проекта Firebase, затем нажмите «Далее» .
- Местоположение Cloud Storage предварительно выбирается в том же регионе, который вы выбрали для своей базы данных Cloud Firestore. Нажмите Готово, чтобы завершить настройку.
Благодаря правилам безопасности по умолчанию любой прошедший проверку подлинности пользователь может записывать в Cloud Storage что угодно. Позже в этой лаборатории мы сделаем наше хранилище более безопасным.
4. Подключитесь к своему проекту Firebase.
Интерфейс командной строки (CLI) Firebase позволяет вам использовать хостинг Firebase для локального обслуживания вашего веб-приложения, а также для развертывания вашего веб-приложения в проекте Firebase.
Убедитесь, что ваша командная строка обращается к локальному каталогу webframework
вашего приложения.
Подключите код веб-приложения к своему проекту Firebase. Сначала войдите в Firebase CLI в командной строке:
firebase login
Затем выполните следующую команду, чтобы создать псевдоним проекта. Замените $YOUR_PROJECT_ID
идентификатором вашего проекта Firebase.
firebase use $YOUR_PROJECT_ID
Добавить AngularFire
Чтобы добавить AngularFire в приложение, выполните команду:
ng add @angular/fire
Затем следуйте инструкциям командной строки и выберите функции, существующие в вашем проекте Firebase.
Инициализировать Firebase
Чтобы инициализировать проект Firebase, запустите:
firebase init
Затем, следуя подсказкам командной строки, выберите функции и эмуляторы, которые использовались в вашем проекте Firebase.
Запустите эмуляторы
В каталоге webframework
выполните следующую команду, чтобы запустить эмуляторы:
firebase emulators:start
В конечном итоге вы должны увидеть что-то вроде этого:
$ firebase emulators:start
i emulators: Starting emulators: auth, functions, firestore, hosting, functions
i firestore: Firestore Emulator logging to firestore-debug.log
i hosting: Serving hosting files from: public
✔ hosting: Local server: http://localhost:5000
i ui: Emulator UI logging to ui-debug.log
i functions: Watching "/functions" for Cloud Functions...
✔ functions[updateMap]: firestore function initialized.
┌─────────────────────────────────────────────────────────────┐
│ ✔ All emulators ready! It is now safe to connect your app. │
│ i View Emulator UI at http://localhost:4000 │
└─────────────────────────────────────────────────────────────┘
┌────────────────┬────────────────┬─────────────────────────────────┐
│ Emulator │ Host:Port │ View in Emulator UI │
├────────────────┼────────────────┼─────────────────────────────────┤
│ Authentication │ localhost:9099 │ http://localhost:4000/auth │
├────────────────┼────────────────┼─────────────────────────────────┤
│ Functions │ localhost:5001 │ http://localhost:4000/functions │
├────────────────┼────────────────┼─────────────────────────────────┤
│ Firestore │ localhost:8080 │ http://localhost:4000/firestore │
├────────────────┼────────────────┼─────────────────────────────────┤
│ Hosting │ localhost:5000 │ n/a │
└────────────────┴────────────────┴─────────────────────────────────┘
Emulator Hub running at localhost:4400
Other reserved ports: 4500
Issues? Report them at https://github.com/firebase/firebase-tools/issues and attach the *-debug.log files.
Как только вы увидите ✔All emulators ready!
сообщение, эмуляторы готовы к использованию.
Вы должны увидеть пользовательский интерфейс вашего приложения для путешествий, который (пока!) не работает:
Теперь приступим к строительству!
5. Подключите веб-приложение к эмуляторам.
Судя по таблице в журналах эмулятора, эмулятор Cloud Firestore прослушивает порт 8080, а эмулятор аутентификации — порт 9099.
Откройте интерфейс эмулятора.
В веб-браузере перейдите по адресу http://127.0.0.1:4000/ . Вы должны увидеть пользовательский интерфейс Emulator Suite.
Направьте приложение на использование эмуляторов
В src/app/app.module.ts
добавьте следующий код в список импорта AppModule
:
@NgModule({
declarations: [...],
imports: [
provideFirebaseApp(() => initializeApp(environment.firebase)),
provideAuth(() => {
const auth = getAuth();
if (location.hostname === 'localhost') {
connectAuthEmulator(auth, 'http://127.0.0.1:9099', { disableWarnings: true });
}
return auth;
}),
provideFirestore(() => {
const firestore = getFirestore();
if (location.hostname === 'localhost') {
connectFirestoreEmulator(firestore, '127.0.0.1', 8080);
}
return firestore;
}),
provideFunctions(() => {
const functions = getFunctions();
if (location.hostname === 'localhost') {
connectFunctionsEmulator(functions, '127.0.0.1', 5001);
}
return functions;
}),
provideStorage(() => {
const storage = getStorage();
if (location.hostname === 'localhost') {
connectStorageEmulator(storage, '127.0.0.1', 5001);
}
return storage;
}),
...
]
Приложение теперь настроено на использование локальных эмуляторов, что позволяет выполнять тестирование и разработку локально.
6. Добавление аутентификации
Теперь, когда для приложения настроены эмуляторы, мы можем добавить функции аутентификации, чтобы гарантировать, что каждый пользователь войдет в систему, прежде чем публиковать сообщения.
Для этого мы можем импортировать функции signin
непосредственно из AngularFire и отслеживать состояние аутентификации вашего пользователя с помощью функции authState
. Измените функции страницы входа в систему, чтобы страница проверяла состояние аутентификации пользователя при загрузке.
Внедрение аутентификации AngularFire
В src/app/pages/login-page/login-page.component.ts
импортируйте Auth
из @angular/fire/auth
и внедрите его в LoginPageComponent
. Поставщики аутентификации, такие как Google, и такие функции, как signin
signout
также можно напрямую импортировать из того же пакета и использовать в приложении.
import { Auth, GoogleAuthProvider, signInWithPopup, signOut, user } from '@angular/fire/auth';
export class LoginPageComponent implements OnInit {
private auth: Auth = inject(Auth);
private provider = new GoogleAuthProvider();
user$ = user(this.auth);
constructor() {}
ngOnInit(): void {}
login() {
signInWithPopup(this.auth, this.provider).then((result) => {
const credential = GoogleAuthProvider.credentialFromResult(result);
return credential;
})
}
logout() {
signOut(this.auth).then(() => {
console.log('signed out');}).catch((error) => {
console.log('sign out error: ' + error);
})
}
}
Теперь страница входа работает! Попробуйте войти в систему и проверить результаты в эмуляторе аутентификации.
7. Настройка Firestore
На этом этапе вы добавите функциональные возможности для публикации и обновления сообщений в блогах о путешествиях, хранящихся в Firestore.
Подобно аутентификации, функции Firestore уже включены в AngularFire. Каждый документ принадлежит коллекции, и каждый документ также может иметь вложенные коллекции. Для создания и обновления записи в блоге о путешествиях необходимо знать path
к документу в Firestore.
Внедрение TravelService
Поскольку многим различным страницам потребуется читать и обновлять документы Firestore в веб-приложении, мы можем реализовать функции в src/app/services/travel.service.ts
, чтобы воздержаться от многократного внедрения одних и тех же функций AngularFire на каждую страницу.
Начните с внедрения Auth
, аналогично предыдущему шагу, а также Firestore
в наш сервис. Также полезно определить наблюдаемый объект user$
, который прослушивает текущий статус аутентификации.
import { doc, docData, DocumentReference, Firestore, getDoc, setDoc, updateDoc, collection, addDoc, deleteDoc, collectionData, Timestamp } from "@angular/fire/firestore";
export class TravelService {
firestore: Firestore = inject(Firestore);
auth: Auth = inject(Auth);
user$ = authState(this.auth).pipe(filter(user => user !== null), map(user => user!));
router: Router = inject(Router);
Добавляем пост о путешествии
Сообщения о путешествиях будут существовать как документы, хранящиеся в Firestore, а поскольку документы должны существовать в коллекциях, коллекция, содержащая все сообщения о путешествиях, будет называться travels
. Таким образом, путь любого путевого сообщения будет: travels/
Используя функцию addDoc
из AngularFire, объект можно вставить в коллекцию:
async addEmptyTravel(userId: String) {
...
addDoc(collection(this.firestore, 'travels'), travelData).then((travelRef) => {
collection(this.firestore, `travels/${travelRef.id}/stops`);
setDoc(travelRef, {... travelData, id: travelRef.id})
this.router.navigate(['edit', `${travelRef.id}`]);
return travelRef;
})
}
Обновление и удаление данных
Зная uid любого сообщения о поездке, можно определить путь к документу, хранящемуся в Firestore, который затем можно прочитать, обновить или удалить с помощью функций AngularFire updateFoc
и deleteDoc
:
async updateData(path: string, data: Partial<Travel | Stop>) {
await updateDoc(doc(this.firestore, path), data)
}
async deleteData(path: string) {
const ref = doc(this.firestore, path);
await deleteDoc(ref)
}
Чтение данных как наблюдаемых
Поскольку путевые записи и остановки по пути можно изменить после создания, было бы полезнее получать объекты документа как наблюдаемые, чтобы подписаться на любые вносимые изменения. Эту функциональность предлагают функции docData
и collectionData
из @angular/fire/firestore
.
getDocData(path: string) {
return docData(doc(this.firestore, path), {idField: 'id'}) as Observable<Travel | Stop>
}
getCollectionData(path: string) {
return collectionData(collection(this.firestore, path), {idField: 'id'}) as Observable<Travel[] | Stop[]>
}
Добавление остановок в туристический пост
Теперь, когда операции с туристическими сообщениями настроены, пришло время рассмотреть остановки, которые будут существовать в подколлекции туристических сообщений, например: travels/ /stops/
travels/ /stops/
Это почти идентично созданию поста о путешествиях, поэтому попробуйте реализовать его самостоятельно или посмотрите реализацию ниже:
async addStop(travelId: string) {
...
const ref = await addDoc(collection(this.firestore, `travels/${travelId}/stops`), stopData)
setDoc(ref, {...stopData, id: ref.id})
}
Хороший! В сервисе «Путешествия» реализованы функции Firestore, и теперь вы можете увидеть их в действии.
Использование функций Firestore в приложении
Перейдите в src/app/pages/my-travels/my-travels.component.ts
и внедрите TravelService
, чтобы использовать его функции.
travelService = inject(TravelService);
travelsData$: Observable<Travel[]>;
stopsList$!: Observable<Stop[]>;
constructor() {
this.travelsData$ = this.travelService.getCollectionData(`travels`) as Observable<Travel[]>
}
TravelService
вызывается в конструкторе для получения массива Observable всех путешествий.
В случае, когда нужны только путешествия текущего пользователя, используйте функцию query
.
Другие методы обеспечения безопасности включают внедрение правил безопасности или использование облачных функций с Firestore, как описано в дополнительных шагах ниже.
Затем просто вызовите функции, реализованные в TravelService
.
async createTravel(userId: String) {
this.travelService.addEmptyTravel(userId);
}
deleteTravel(travelId: String) {
this.travelService.deleteData(`travels/${travelId}`)
}
Теперь страница «Мои путешествия» должна работать! Посмотрите, что происходит в вашем эмуляторе Firestore, когда вы создаете новую публикацию о путешествиях.
Затем повторите действия для функций обновления в /src/app/pages/edit-travels/edit-travels.component.ts
:
travelService: TravelService = inject(TravelService)
travelId = this.activatedRoute.snapshot.paramMap.get('travelId');
travelData$: Observable<Travel>;
stopsData$: Observable<Stop[]>;
constructor() {
this.travelData$ = this.travelService.getDocData(`travels/${this.travelId}`) as Observable<Travel>
this.stopsData$ = this.travelService.getCollectionData(`travels/${this.travelId}/stops`) as Observable<Stop[]>
}
updateCurrentTravel(travel: Partial<Travel>) {
this.travelService.updateData(`travels${this.travelId}`, travel)
}
updateCurrentStop(stop: Partial<Stop>) {
stop.type = stop.type?.toString();
this.travelService.updateData(`travels${this.travelId}/stops/${stop.id}`, stop)
}
addStop() {
if (!this.travelId) return;
this.travelService.addStop(this.travelId);
}
deleteStop(stopId: string) {
if (!this.travelId || !stopId) {
return;
}
this.travelService.deleteData(`travels${this.travelId}/stops/${stopId}`)
this.stopsData$ = this.travelService.getCollectionData(`travels${this.travelId}/stops`) as Observable<Stop[]>
}
8. Настройка хранилища
Теперь вы реализуете хранилище для хранения изображений и других типов мультимедиа.
Cloud Firestore лучше всего использовать для хранения структурированных данных, таких как объекты JSON. Облачное хранилище предназначено для хранения файлов или больших двоичных объектов. В этом приложении вы будете использовать его, чтобы позволить пользователям делиться фотографиями из своих путешествий.
Как и в случае с Firestore, для хранения и обновления файлов с помощью Storage требуется уникальный идентификатор для каждого файла.
Давайте реализуем функции в TraveService
:
Загрузка файла
Перейдите в src/app/services/travel.service.ts
и добавьте Storage из AngularFire:
export class TravelService {
firestore: Firestore = inject(Firestore);
auth: Auth = inject(Auth);
storage: Storage = inject(Storage);
И реализуем функцию загрузки:
async uploadToStorage(path: string, input: HTMLInputElement, contentType: any) {
if (!input.files) return null
const files: FileList = input.files;
for (let i = 0; i < files.length; i++) {
const file = files.item(i);
if (file) {
const imagePath = `${path}/${file.name}`
const storageRef = ref(this.storage, imagePath);
await uploadBytesResumable(storageRef, file, contentType);
return await getDownloadURL(storageRef);
}
}
return null;
}
Основное различие между доступом к документам из Firestore и файлам из Cloud Storage заключается в том, что, хотя они оба следуют путям, структурированным по папкам, базовый URL-адрес и комбинация путей получаются через getDownloadURL
, который затем можно сохранить и использовать в файл.
Использование функции в приложении
Перейдите в src/app/components/edit-stop/edit-stop.component.ts
и вызовите функцию загрузки, используя:
async uploadFile(file: HTMLInputElement, stop: Partial<Stop>) {
const path = `/travels/${this.travelId}/stops/${stop.id}`
const url = await this.travelService.uploadToStorage(path, file, {contentType: 'image/png'});
stop.image = url ? url : '';
this.travelService.updateData(path, stop);
}
Когда изображение будет загружено, сам медиафайл будет загружен в хранилище, а URL-адрес соответственно сохранится в документе в Firestore.
9. Развертывание приложения
Теперь мы готовы развернуть приложение!
Скопируйте конфигурации firebase
из src/environments/environment.ts
в src/environments/environment.prod.ts
и запустите:
firebase deploy
Вы должны увидеть что-то вроде этого:
✔ Browser application bundle generation complete.
✔ Copying assets complete.
✔ Index html generation complete.
=== Deploying to 'friendly-travels-b6a4b'...
i deploying storage, firestore, hosting
i firebase.storage: checking storage.rules for compilation errors...
✔ firebase.storage: rules file storage.rules compiled successfully
i firestore: reading indexes from firestore.indexes.json...
i cloud.firestore: checking firestore.rules for compilation errors...
✔ cloud.firestore: rules file firestore.rules compiled successfully
i storage: latest version of storage.rules already up to date, skipping upload...
i firestore: deploying indexes...
i firestore: latest version of firestore.rules already up to date, skipping upload...
✔ firestore: deployed indexes in firestore.indexes.json successfully for (default) database
i hosting[friendly-travels-b6a4b]: beginning deploy...
i hosting[friendly-travels-b6a4b]: found 6 files in .firebase/friendly-travels-b6a4b/hosting
✔ hosting[friendly-travels-b6a4b]: file upload complete
✔ storage: released rules storage.rules to firebase.storage
✔ firestore: released rules firestore.rules to cloud.firestore
i hosting[friendly-travels-b6a4b]: finalizing version...
✔ hosting[friendly-travels-b6a4b]: version finalized
i hosting[friendly-travels-b6a4b]: releasing new version...
✔ hosting[friendly-travels-b6a4b]: release complete
✔ Deploy complete!
Project Console: https://console.firebase.google.com/project/friendly-travels-b6a4b/overview
Hosting URL: https://friendly-travels-b6a4b.web.app
10. Поздравляем!
Теперь ваше приложение должно быть завершено и развернуто на хостинге Firebase! Все данные и аналитика теперь будут доступны в вашей консоли Firebase.
Чтобы узнать больше о AngularFire, функциях и правилах безопасности, не забудьте ознакомиться с дополнительными шагами ниже, а также с другими лабораториями Firebase Codelab !
11. Необязательно: защита аутентификации AngularFire.
Наряду с аутентификацией Firebase, AngularFire также предлагает защиту маршрутов на основе аутентификации, так что пользователи с недостаточным доступом могут быть перенаправлены. Это помогает защитить приложение от доступа пользователей к защищенным данным.
В src/app/app-routing.module.ts
импортируйте
import {AuthGuard, redirectLoggedInTo, redirectUnauthorizedTo} from '@angular/fire/auth-guard'
Затем вы можете определить функции, определяющие, когда и куда должны быть перенаправлены пользователи на определенных страницах:
const redirectUnauthorizedToLogin = () => redirectUnauthorizedTo(['signin']);
const redirectLoggedInToTravels = () => redirectLoggedInTo(['my-travels']);
Затем просто добавьте их в свои маршруты:
const routes: Routes = [
{path: '', component: LoginPageComponent, canActivate: [AuthGuard], data: {authGuardPipe: redirectLoggedInToTravels}},
{path: 'signin', component: LoginPageComponent, canActivate: [AuthGuard], data: {authGuardPipe: redirectLoggedInToTravels}},
{path: 'my-travels', component: MyTravelsComponent, canActivate: [AuthGuard], data: {authGuardPipe: redirectUnauthorizedToLogin}},
{path: 'edit/:travelId', component: EditTravelsComponent, canActivate: [AuthGuard], data: {authGuardPipe: redirectUnauthorizedToLogin}},
];
12. Необязательно: правила безопасности.
И Firestore, и Cloud Storage используют правила безопасности ( firestore.rules
и security.rules
соответственно) для обеспечения безопасности и проверки данных.
На данный момент данные Firestore и Storage имеют открытый доступ для чтения и записи, но вы не хотите, чтобы люди меняли сообщения других! Вы можете использовать правила безопасности, чтобы ограничить доступ к вашим коллекциям и документам.
Правила пожарного магазина
Чтобы разрешить просмотр сообщений о путешествиях только авторизованным пользователям, перейдите в файл firestore.rules
и добавьте:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/travels {
allow read: if request.auth.uid != null;
allow write:
if request.auth.uid == request.resource.data.userId;
}
}
Правила безопасности также можно использовать для проверки данных:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/posts {
allow read: if request.auth.uid != null;
allow write:
if request.auth.uid == request.resource.data.userId;
&& "author" in request.resource.data
&& "text" in request.resource.data
&& "timestamp" in request.resource.data;
}
}
Правила хранения
Аналогичным образом мы можем использовать правила безопасности для обеспечения доступа к базам данных хранилища в storage.rules
. Обратите внимание, что мы также можем использовать функции для более сложных проверок:
rules_version = '2';
function isImageBelowMaxSize(maxSizeMB) {
return request.resource.size < maxSizeMB * 1024 * 1024
&& request.resource.contentType.matches('image/.*');
}
service firebase.storage {
match /b/{bucket}/o {
match /{userId}/{postId}/{filename} {
allow write: if request.auth != null
&& request.auth.uid == userId && isImageBelowMaxSize(5);
allow read;
}
}
}
1. Что вы создадите
В этой лаборатории кода вы создадите блог о путешествиях с совместной картой в реальном времени с использованием новейшей версии нашей библиотеки Angular: AngularFire . Окончательное веб-приложение будет представлять собой блог о путешествиях, куда вы сможете загружать изображения каждого места, куда вы побывали.
AngularFire будет использоваться для создания веб-приложения, Emulator Suite для локального тестирования, аутентификации для отслеживания пользовательских данных, Firestore и Storage для сохранения данных и мультимедиа на базе облачных функций и, наконец, хостинга Firebase для развертывания приложения.
Что вы узнаете
- Как разрабатывать продукты Firebase локально с помощью Emulator Suite
- Как улучшить ваше веб-приложение с помощью AngularFire
- Как сохранить ваши данные в Firestore
- Как сохранить носитель в хранилище
- Как развернуть ваше приложение на хостинге Firebase
- Как использовать облачные функции для взаимодействия с вашими базами данных и API
Что вам понадобится
- Node.js версии 10 или выше
- Аккаунт Google для создания и управления вашим проектом Firebase.
- Firebase CLI версии 11.14.2 или новее.
- Браузер по вашему выбору, например Chrome.
- Базовое понимание Angular и Javascript
2. Получите пример кода
Клонируйте репозиторий GitHub codelab из командной строки:
git clone https://github.com/firebase/codelab-friendlychat-web
Альтернативно, если у вас не установлен git, вы можете загрузить репозиторий в виде ZIP-файла .
Репозиторий Github содержит примеры проектов для нескольких платформ.
Эта лаборатория кода использует только репозиторий веб-фреймворка:
- 📁 webframework : начальный код, который вы будете использовать во время этой лабораторной работы.
Установить зависимости
После клонирования установите зависимости в корневую папку и папку functions
, прежде чем создавать веб-приложение.
cd webframework && npm install
cd functions && npm install
Установите интерфейс командной строки Firebase
Установите Firebase CLI, используя эту команду в терминале:
npm install -g firebase-tools
Дважды проверьте, что ваша версия Firebase CLI выше 11.14.2, используя:
firebase --version
Если ваша версия ниже 11.14.2, обновите, используя:
npm update firebase-tools
3. Создайте и настройте проект Firebase.
Создать проект Firebase
- Войдите в Firebase .
- В консоли Firebase нажмите «Добавить проект» и назовите свой проект Firebase <your-project> . Запомните идентификатор вашего проекта Firebase.
- Нажмите Создать проект .
Важно : ваш проект Firebase будет называться <your-project> , но Firebase автоматически присвоит ему уникальный идентификатор проекта в форме <your-project>-1234 . Этот уникальный идентификатор фактически идентифицирует ваш проект (в том числе в CLI), тогда как <ваш-проект> — это просто отображаемое имя.
Приложение, которое мы собираемся создать, использует продукты Firebase, доступные для веб-приложений:
- Аутентификация Firebase , позволяющая пользователям легко входить в ваше приложение.
- Cloud Firestore для сохранения структурированных данных в облаке и мгновенного получения уведомлений при изменении данных.
- Облачное хранилище для Firebase для сохранения файлов в облаке.
- Хостинг Firebase для размещения и обслуживания ваших ресурсов.
- Функции для взаимодействия с внутренними и внешними API.
Некоторые из этих продуктов требуют специальных конфигураций или их необходимо включить с помощью консоли Firebase.
Добавьте веб-приложение Firebase в проект
- Нажмите значок веб-сайта, чтобы создать новое веб-приложение Firebase.
- На следующем шаге вы увидите объект конфигурации. Скопируйте содержимое этого объекта в файл
environments/environment.ts
.
Включите вход в Google для аутентификации Firebase
Чтобы пользователи могли входить в веб-приложение со своими учетными записями Google, мы будем использовать метод входа Google .
Чтобы включить вход в Google :
- В консоли Firebase найдите раздел «Сборка» на левой панели.
- Нажмите «Аутентификация» , затем перейдите на вкладку «Метод входа» (или нажмите здесь, чтобы перейти непосредственно туда).
- Включите службу входа в систему Google , затем нажмите «Сохранить» .
- Установите общедоступное имя вашего приложения на <имя-вашего-проекта> и выберите адрес электронной почты поддержки проекта из раскрывающегося меню.
Включить Cloud Firestore
- В разделе «Сборка » консоли Firebase нажмите «База данных Firestore» .
- Нажмите Создать базу данных на панели Cloud Firestore.
- Укажите место, где хранятся данные Cloud Firestore. Вы можете оставить это значение по умолчанию или выбрать ближайший к вам регион.
Включить облачное хранилище
Веб-приложение использует Cloud Storage for Firebase для хранения, загрузки и обмена изображениями.
- В разделе «Сборка » консоли Firebase нажмите « Хранилище» .
- Если кнопки «Начать» нет, это означает, что облачное хранилище уже
включен, и вам не нужно выполнять действия, описанные ниже.
- Нажмите «Начать» .
- Прочтите заявление об отказе от ответственности о правилах безопасности для вашего проекта Firebase, затем нажмите «Далее» .
- Местоположение Cloud Storage предварительно выбирается в том же регионе, который вы выбрали для своей базы данных Cloud Firestore. Нажмите Готово, чтобы завершить настройку.
Благодаря правилам безопасности по умолчанию любой прошедший проверку подлинности пользователь может записывать в Cloud Storage что угодно. Позже в этой лаборатории мы сделаем наше хранилище более безопасным.
4. Подключитесь к своему проекту Firebase.
Интерфейс командной строки (CLI) Firebase позволяет вам использовать хостинг Firebase для локального обслуживания вашего веб-приложения, а также для развертывания вашего веб-приложения в проекте Firebase.
Убедитесь, что ваша командная строка обращается к локальному каталогу webframework
вашего приложения.
Подключите код веб-приложения к своему проекту Firebase. Сначала войдите в Firebase CLI в командной строке:
firebase login
Затем выполните следующую команду, чтобы создать псевдоним проекта. Замените $YOUR_PROJECT_ID
идентификатором вашего проекта Firebase.
firebase use $YOUR_PROJECT_ID
Добавить AngularFire
Чтобы добавить AngularFire в приложение, выполните команду:
ng add @angular/fire
Затем следуйте инструкциям командной строки и выберите функции, существующие в вашем проекте Firebase.
Инициализировать Firebase
Чтобы инициализировать проект Firebase, запустите:
firebase init
Затем, следуя подсказкам командной строки, выберите функции и эмуляторы, которые использовались в вашем проекте Firebase.
Запустите эмуляторы
В каталоге webframework
выполните следующую команду, чтобы запустить эмуляторы:
firebase emulators:start
В конечном итоге вы должны увидеть что-то вроде этого:
$ firebase emulators:start
i emulators: Starting emulators: auth, functions, firestore, hosting, functions
i firestore: Firestore Emulator logging to firestore-debug.log
i hosting: Serving hosting files from: public
✔ hosting: Local server: http://localhost:5000
i ui: Emulator UI logging to ui-debug.log
i functions: Watching "/functions" for Cloud Functions...
✔ functions[updateMap]: firestore function initialized.
┌─────────────────────────────────────────────────────────────┐
│ ✔ All emulators ready! It is now safe to connect your app. │
│ i View Emulator UI at http://localhost:4000 │
└─────────────────────────────────────────────────────────────┘
┌────────────────┬────────────────┬─────────────────────────────────┐
│ Emulator │ Host:Port │ View in Emulator UI │
├────────────────┼────────────────┼─────────────────────────────────┤
│ Authentication │ localhost:9099 │ http://localhost:4000/auth │
├────────────────┼────────────────┼─────────────────────────────────┤
│ Functions │ localhost:5001 │ http://localhost:4000/functions │
├────────────────┼────────────────┼─────────────────────────────────┤
│ Firestore │ localhost:8080 │ http://localhost:4000/firestore │
├────────────────┼────────────────┼─────────────────────────────────┤
│ Hosting │ localhost:5000 │ n/a │
└────────────────┴────────────────┴─────────────────────────────────┘
Emulator Hub running at localhost:4400
Other reserved ports: 4500
Issues? Report them at https://github.com/firebase/firebase-tools/issues and attach the *-debug.log files.
Как только вы увидите ✔All emulators ready!
сообщение, эмуляторы готовы к использованию.
Вы должны увидеть пользовательский интерфейс вашего приложения для путешествий, который (пока!) не работает:
Теперь приступим к строительству!
5. Подключите веб-приложение к эмуляторам.
Судя по таблице в журналах эмулятора, эмулятор Cloud Firestore прослушивает порт 8080, а эмулятор аутентификации — порт 9099.
Откройте интерфейс эмулятора.
В веб-браузере перейдите по адресу http://127.0.0.1:4000/ . Вы должны увидеть пользовательский интерфейс Emulator Suite.
Направьте приложение на использование эмуляторов
В src/app/app.module.ts
добавьте следующий код в список импорта AppModule
:
@NgModule({
declarations: [...],
imports: [
provideFirebaseApp(() => initializeApp(environment.firebase)),
provideAuth(() => {
const auth = getAuth();
if (location.hostname === 'localhost') {
connectAuthEmulator(auth, 'http://127.0.0.1:9099', { disableWarnings: true });
}
return auth;
}),
provideFirestore(() => {
const firestore = getFirestore();
if (location.hostname === 'localhost') {
connectFirestoreEmulator(firestore, '127.0.0.1', 8080);
}
return firestore;
}),
provideFunctions(() => {
const functions = getFunctions();
if (location.hostname === 'localhost') {
connectFunctionsEmulator(functions, '127.0.0.1', 5001);
}
return functions;
}),
provideStorage(() => {
const storage = getStorage();
if (location.hostname === 'localhost') {
connectStorageEmulator(storage, '127.0.0.1', 5001);
}
return storage;
}),
...
]
Приложение теперь настроено на использование локальных эмуляторов, что позволяет выполнять тестирование и разработку локально.
6. Добавление аутентификации
Теперь, когда для приложения настроены эмуляторы, мы можем добавить функции аутентификации, чтобы гарантировать, что каждый пользователь войдет в систему, прежде чем публиковать сообщения.
Для этого мы можем импортировать функции signin
непосредственно из AngularFire и отслеживать состояние аутентификации вашего пользователя с помощью функции authState
. Измените функции страницы входа так, чтобы страница проверяла состояние аутентификации пользователя при загрузке.
Внедрение аутентификации AngularFire
В src/app/pages/login-page/login-page.component.ts
импортируйте Auth
из @angular/fire/auth
и внедрите его в LoginPageComponent
. Поставщики аутентификации, такие как Google, и такие функции, как signin
signout
также можно напрямую импортировать из того же пакета и использовать в приложении.
import { Auth, GoogleAuthProvider, signInWithPopup, signOut, user } from '@angular/fire/auth';
export class LoginPageComponent implements OnInit {
private auth: Auth = inject(Auth);
private provider = new GoogleAuthProvider();
user$ = user(this.auth);
constructor() {}
ngOnInit(): void {}
login() {
signInWithPopup(this.auth, this.provider).then((result) => {
const credential = GoogleAuthProvider.credentialFromResult(result);
return credential;
})
}
logout() {
signOut(this.auth).then(() => {
console.log('signed out');}).catch((error) => {
console.log('sign out error: ' + error);
})
}
}
Теперь страница входа работает! Попробуйте войти в систему и проверить результаты в эмуляторе аутентификации.
7. Настройка Firestore
На этом этапе вы добавите функции для публикации и обновления сообщений в блогах о путешествиях, хранящихся в Firestore.
Как и в случае с аутентификацией, функции Firestore уже встроены в AngularFire. Каждый документ принадлежит коллекции, и каждый документ также может иметь вложенные коллекции. Для создания и обновления записи в блоге о путешествиях необходимо знать path
к документу в Firestore.
Внедрение TravelService
Поскольку многим различным страницам потребуется читать и обновлять документы Firestore в веб-приложении, мы можем реализовать функции в src/app/services/travel.service.ts
, чтобы воздержаться от многократного внедрения одних и тех же функций AngularFire на каждую страницу.
Начните с внедрения Auth
, аналогично предыдущему шагу, а также Firestore
в наш сервис. Также полезно определить наблюдаемый объект user$
, который прослушивает текущий статус аутентификации.
import { doc, docData, DocumentReference, Firestore, getDoc, setDoc, updateDoc, collection, addDoc, deleteDoc, collectionData, Timestamp } from "@angular/fire/firestore";
export class TravelService {
firestore: Firestore = inject(Firestore);
auth: Auth = inject(Auth);
user$ = authState(this.auth).pipe(filter(user => user !== null), map(user => user!));
router: Router = inject(Router);
Добавляем пост о путешествии
Сообщения о путешествиях будут существовать как документы, хранящиеся в Firestore, а поскольку документы должны существовать в коллекциях, коллекция, содержащая все сообщения о путешествиях, будет называться travels
. Таким образом, путь любого путевого сообщения будет: travels/
Используя функцию addDoc
из AngularFire, объект можно вставить в коллекцию:
async addEmptyTravel(userId: String) {
...
addDoc(collection(this.firestore, 'travels'), travelData).then((travelRef) => {
collection(this.firestore, `travels/${travelRef.id}/stops`);
setDoc(travelRef, {... travelData, id: travelRef.id})
this.router.navigate(['edit', `${travelRef.id}`]);
return travelRef;
})
}
Обновление и удаление данных
Зная uid любого сообщения о поездке, можно определить путь к документу, хранящемуся в Firestore, который затем можно прочитать, обновить или удалить с помощью функций AngularFire updateFoc
и deleteDoc
:
async updateData(path: string, data: Partial<Travel | Stop>) {
await updateDoc(doc(this.firestore, path), data)
}
async deleteData(path: string) {
const ref = doc(this.firestore, path);
await deleteDoc(ref)
}
Чтение данных как наблюдаемых
Поскольку путевые записи и остановки по пути можно изменить после создания, было бы полезнее получать объекты документа как наблюдаемые, чтобы подписаться на любые вносимые изменения. Эту функциональность предлагают функции docData
и collectionData
из @angular/fire/firestore
.
getDocData(path: string) {
return docData(doc(this.firestore, path), {idField: 'id'}) as Observable<Travel | Stop>
}
getCollectionData(path: string) {
return collectionData(collection(this.firestore, path), {idField: 'id'}) as Observable<Travel[] | Stop[]>
}
Добавление остановок в туристический пост
Теперь, когда операции с туристическими сообщениями настроены, пришло время рассмотреть остановки, которые будут существовать в подколлекции туристических сообщений, например: travels/ /stops/
travels/ /stops/
Это почти идентично созданию поста о путешествиях, поэтому попробуйте реализовать его самостоятельно или посмотрите реализацию ниже:
async addStop(travelId: string) {
...
const ref = await addDoc(collection(this.firestore, `travels/${travelId}/stops`), stopData)
setDoc(ref, {...stopData, id: ref.id})
}
Хороший! В сервисе «Путешествия» реализованы функции Firestore, и теперь вы можете увидеть их в действии.
Использование функций Firestore в приложении
Перейдите в src/app/pages/my-travels/my-travels.component.ts
и внедрите TravelService
, чтобы использовать его функции.
travelService = inject(TravelService);
travelsData$: Observable<Travel[]>;
stopsList$!: Observable<Stop[]>;
constructor() {
this.travelsData$ = this.travelService.getCollectionData(`travels`) as Observable<Travel[]>
}
TravelService
вызывается в конструкторе для получения массива Observable всех путешествий.
В случае, когда нужны только путешествия текущего пользователя, используйте функцию query
.
Другие методы обеспечения безопасности включают внедрение правил безопасности или использование облачных функций с Firestore, как описано в дополнительных шагах ниже.
Затем просто вызовите функции, реализованные в TravelService
.
async createTravel(userId: String) {
this.travelService.addEmptyTravel(userId);
}
deleteTravel(travelId: String) {
this.travelService.deleteData(`travels/${travelId}`)
}
Теперь страница «Мои путешествия» должна работать! Посмотрите, что происходит в вашем эмуляторе Firestore, когда вы создаете новую публикацию о путешествиях.
Затем повторите действия для функций обновления в /src/app/pages/edit-travels/edit-travels.component.ts
:
travelService: TravelService = inject(TravelService)
travelId = this.activatedRoute.snapshot.paramMap.get('travelId');
travelData$: Observable<Travel>;
stopsData$: Observable<Stop[]>;
constructor() {
this.travelData$ = this.travelService.getDocData(`travels/${this.travelId}`) as Observable<Travel>
this.stopsData$ = this.travelService.getCollectionData(`travels/${this.travelId}/stops`) as Observable<Stop[]>
}
updateCurrentTravel(travel: Partial<Travel>) {
this.travelService.updateData(`travels${this.travelId}`, travel)
}
updateCurrentStop(stop: Partial<Stop>) {
stop.type = stop.type?.toString();
this.travelService.updateData(`travels${this.travelId}/stops/${stop.id}`, stop)
}
addStop() {
if (!this.travelId) return;
this.travelService.addStop(this.travelId);
}
deleteStop(stopId: string) {
if (!this.travelId || !stopId) {
return;
}
this.travelService.deleteData(`travels${this.travelId}/stops/${stopId}`)
this.stopsData$ = this.travelService.getCollectionData(`travels${this.travelId}/stops`) as Observable<Stop[]>
}
8. Настройка хранилища
Теперь вы реализуете хранилище для хранения изображений и других типов мультимедиа.
Cloud Firestore лучше всего использовать для хранения структурированных данных, таких как объекты JSON. Облачное хранилище предназначено для хранения файлов или больших двоичных объектов. В этом приложении вы будете использовать его, чтобы позволить пользователям делиться фотографиями из своих путешествий.
Как и в случае с Firestore, для хранения и обновления файлов с помощью Storage требуется уникальный идентификатор для каждого файла.
Давайте реализуем функции в TraveService
:
Загрузка файла
Перейдите в src/app/services/travel.service.ts
и добавьте Storage из AngularFire:
export class TravelService {
firestore: Firestore = inject(Firestore);
auth: Auth = inject(Auth);
storage: Storage = inject(Storage);
И реализуем функцию загрузки:
async uploadToStorage(path: string, input: HTMLInputElement, contentType: any) {
if (!input.files) return null
const files: FileList = input.files;
for (let i = 0; i < files.length; i++) {
const file = files.item(i);
if (file) {
const imagePath = `${path}/${file.name}`
const storageRef = ref(this.storage, imagePath);
await uploadBytesResumable(storageRef, file, contentType);
return await getDownloadURL(storageRef);
}
}
return null;
}
Основное различие между доступом к документам из Firestore и файлам из Cloud Storage заключается в том, что, хотя они оба следуют путям, структурированным по папкам, базовый URL-адрес и комбинация путей получаются через getDownloadURL
, который затем можно сохранить и использовать в файл.
Использование функции в приложении
Перейдите в src/app/components/edit-stop/edit-stop.component.ts
и вызовите функцию загрузки, используя:
async uploadFile(file: HTMLInputElement, stop: Partial<Stop>) {
const path = `/travels/${this.travelId}/stops/${stop.id}`
const url = await this.travelService.uploadToStorage(path, file, {contentType: 'image/png'});
stop.image = url ? url : '';
this.travelService.updateData(path, stop);
}
Когда изображение будет загружено, сам медиафайл будет загружен в хранилище, а URL-адрес соответственно сохранится в документе в Firestore.
9. Развертывание приложения
Теперь мы готовы развернуть приложение!
Скопируйте конфигурации firebase
из src/environments/environment.ts
в src/environments/environment.prod.ts
и запустите:
firebase deploy
Вы должны увидеть что-то вроде этого:
✔ Browser application bundle generation complete.
✔ Copying assets complete.
✔ Index html generation complete.
=== Deploying to 'friendly-travels-b6a4b'...
i deploying storage, firestore, hosting
i firebase.storage: checking storage.rules for compilation errors...
✔ firebase.storage: rules file storage.rules compiled successfully
i firestore: reading indexes from firestore.indexes.json...
i cloud.firestore: checking firestore.rules for compilation errors...
✔ cloud.firestore: rules file firestore.rules compiled successfully
i storage: latest version of storage.rules already up to date, skipping upload...
i firestore: deploying indexes...
i firestore: latest version of firestore.rules already up to date, skipping upload...
✔ firestore: deployed indexes in firestore.indexes.json successfully for (default) database
i hosting[friendly-travels-b6a4b]: beginning deploy...
i hosting[friendly-travels-b6a4b]: found 6 files in .firebase/friendly-travels-b6a4b/hosting
✔ hosting[friendly-travels-b6a4b]: file upload complete
✔ storage: released rules storage.rules to firebase.storage
✔ firestore: released rules firestore.rules to cloud.firestore
i hosting[friendly-travels-b6a4b]: finalizing version...
✔ hosting[friendly-travels-b6a4b]: version finalized
i hosting[friendly-travels-b6a4b]: releasing new version...
✔ hosting[friendly-travels-b6a4b]: release complete
✔ Deploy complete!
Project Console: https://console.firebase.google.com/project/friendly-travels-b6a4b/overview
Hosting URL: https://friendly-travels-b6a4b.web.app
10. Поздравляем!
Теперь ваше приложение должно быть завершено и развернуто на хостинге Firebase! Все данные и аналитика теперь будут доступны в вашей консоли Firebase.
Чтобы узнать больше о AngularFire, функциях и правилах безопасности, не забудьте ознакомиться с дополнительными шагами ниже, а также с другими лабораториями Firebase Codelab !
11. Необязательно: защита аутентификации AngularFire.
Наряду с аутентификацией Firebase, AngularFire также предлагает защиту маршрутов на основе аутентификации, так что пользователи с недостаточным доступом могут быть перенаправлены. Это помогает защитить приложение от доступа пользователей к защищенным данным.
В src/app/app-routing.module.ts
импортируйте
import {AuthGuard, redirectLoggedInTo, redirectUnauthorizedTo} from '@angular/fire/auth-guard'
Затем вы можете определить функции, определяющие, когда и куда должны быть перенаправлены пользователи на определенных страницах:
const redirectUnauthorizedToLogin = () => redirectUnauthorizedTo(['signin']);
const redirectLoggedInToTravels = () => redirectLoggedInTo(['my-travels']);
Затем просто добавьте их в свои маршруты:
const routes: Routes = [
{path: '', component: LoginPageComponent, canActivate: [AuthGuard], data: {authGuardPipe: redirectLoggedInToTravels}},
{path: 'signin', component: LoginPageComponent, canActivate: [AuthGuard], data: {authGuardPipe: redirectLoggedInToTravels}},
{path: 'my-travels', component: MyTravelsComponent, canActivate: [AuthGuard], data: {authGuardPipe: redirectUnauthorizedToLogin}},
{path: 'edit/:travelId', component: EditTravelsComponent, canActivate: [AuthGuard], data: {authGuardPipe: redirectUnauthorizedToLogin}},
];
12. Необязательно: правила безопасности.
И Firestore, и Cloud Storage используют правила безопасности ( firestore.rules
и security.rules
соответственно) для обеспечения безопасности и проверки данных.
На данный момент данные Firestore и Storage имеют открытый доступ для чтения и записи, но вы не хотите, чтобы люди меняли сообщения других! Вы можете использовать правила безопасности, чтобы ограничить доступ к вашим коллекциям и документам.
Правила пожарного магазина
Чтобы разрешить просмотр сообщений о путешествиях только авторизованным пользователям, перейдите в файл firestore.rules
и добавьте:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/travels {
allow read: if request.auth.uid != null;
allow write:
if request.auth.uid == request.resource.data.userId;
}
}
Правила безопасности также можно использовать для проверки данных:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/posts {
allow read: if request.auth.uid != null;
allow write:
if request.auth.uid == request.resource.data.userId;
&& "author" in request.resource.data
&& "text" in request.resource.data
&& "timestamp" in request.resource.data;
}
}
Правила хранения
Аналогичным образом мы можем использовать правила безопасности для обеспечения доступа к базам данных хранилища в storage.rules
. Обратите внимание, что мы также можем использовать функции для более сложных проверок:
rules_version = '2';
function isImageBelowMaxSize(maxSizeMB) {
return request.resource.size < maxSizeMB * 1024 * 1024
&& request.resource.contentType.matches('image/.*');
}
service firebase.storage {
match /b/{bucket}/o {
match /{userId}/{postId}/{filename} {
allow write: if request.auth != null
&& request.auth.uid == userId && isImageBelowMaxSize(5);
allow read;
}
}
}