1. Что вы создадите
В этой лабораторной работе вы создадите блог о путешествиях с картой, создаваемой в режиме реального времени, используя новейшие возможности нашей библиотеки Angular: AngularFire . В результате веб-приложение будет представлять собой блог о путешествиях, куда вы сможете загружать изображения каждого места, где вы побывали.
AngularFire будет использоваться для создания веб-приложения, Emulator Suite — для локального тестирования, Authentication — для отслеживания пользовательских данных, Firestore и Storage — для сохранения данных и мультимедиа на базе Cloud Functions и, наконец, Firebase Hosting — для развертывания приложения.
Чему вы научитесь
- Как разрабатывать продукты Firebase локально с помощью Emulator Suite
- Как улучшить ваше веб-приложение с помощью AngularFire
- Как сохранить данные в Firestore
- Как сохранить медиафайлы в хранилище
- Как развернуть приложение на Firebase Hosting
- Как использовать облачные функции для взаимодействия с базами данных и API
Что вам понадобится
- Node.js версии 10 или выше
- Учетная запись Google для создания и управления вашим проектом Firebase
- Firebase CLI версии 11.14.2 или более поздней
- Браузер по вашему выбору, например Chrome
- Базовое понимание Angular и Javascript
2. Получите пример кода
Клонируйте репозиторий GitHub кодовой лаборатории из командной строки:
git clone https://github.com/firebase/codelab-friendlychat-web
Кроме того, если у вас не установлен git, вы можете загрузить репозиторий в виде ZIP-файла .
Репозиторий Github содержит примеры проектов для нескольких платформ.
В этой лабораторной работе используется только репозиторий webframework:
- 📁 webframework : начальный код, на основе которого вы будете работать во время этой лабораторной работы.
Установка зависимостей
После клонирования установите зависимости в корневую папку и папку functions
перед сборкой веб-приложения.
cd webframework && npm install
cd functions && npm install
Установить Firebase CLI
Установите 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, используя свою учетную запись Google.
- Нажмите кнопку, чтобы создать новый проект, а затем введите название проекта (например,
FriendlyChat
). - Нажмите «Продолжить» .
- При появлении соответствующего запроса ознакомьтесь с условиями Firebase и примите их, а затем нажмите кнопку «Продолжить» .
- (Необязательно) Включите помощь ИИ в консоли Firebase (так называемая «Gemini в Firebase»).
- Для этой лабораторной работы вам не понадобится Google Analytics, поэтому отключите опцию Google Analytics.
- Нажмите «Создать проект» , дождитесь завершения подготовки проекта, а затем нажмите «Продолжить» .
Добавьте веб-приложение Firebase в проект
- Щелкните значок Интернета, чтобы создать новое веб-приложение Firebase.
- На следующем шаге вы увидите объект конфигурации. Скопируйте содержимое этого объекта в файл
environments/environment.ts
.
Настройка продуктов Firebase
Приложение, которое мы собираемся создать, использует продукты Firebase, доступные для веб-приложений:
- Аутентификация Firebase , позволяющая пользователям легко входить в ваше приложение.
- Cloud Firestore для сохранения структурированных данных в облаке и мгновенного получения уведомлений об изменении данных.
- Облачное хранилище для Firebase для сохранения файлов в облаке.
- Firebase Hosting для размещения и обслуживания ваших активов.
- Функции взаимодействия с внутренними и внешними API.
Некоторые из этих продуктов требуют специальных настроек или должны быть включены с помощью консоли Firebase.
Включить вход через Google для аутентификации Firebase
Чтобы разрешить пользователям входить в веб-приложение с помощью своих учетных записей Google, мы воспользуемся методом входа через Google .
Чтобы включить вход через Google :
- В консоли Firebase найдите раздел «Сборка» на левой панели.
- Нажмите «Аутентификация» , затем нажмите вкладку «Метод входа» (или нажмите здесь , чтобы перейти туда).
- Включите поставщика входа Google , затем нажмите «Сохранить» .
- Установите публичное имя вашего приложения на <имя-вашего-проекта> и выберите адрес электронной почты службы поддержки проекта из раскрывающегося меню.
Включить Cloud Firestore
- В разделе «Сборка» консоли Firebase нажмите «База данных Firestore» .
- Нажмите Создать базу данных на панели Cloud Firestore.
- Укажите место хранения данных Cloud Firestore. Вы можете оставить его по умолчанию или выбрать ближайший к вам регион.
Включить облачное хранилище
Веб-приложение использует облачное хранилище для Firebase для хранения, загрузки и обмена изображениями.
- В разделе «Сборка» консоли Firebase нажмите «Хранилище» .
- Если кнопки «Начать» нет, это означает, что облачное хранилище уже доступно.
включено, и вам не нужно выполнять следующие шаги.
- Нажмите «Начать» .
- Ознакомьтесь с отказом от ответственности о правилах безопасности для вашего проекта Firebase, затем нажмите кнопку Далее .
- Расположение облачного хранилища предварительно выбрано в том же регионе, что и для базы данных Cloud Firestore. Нажмите «Готово» , чтобы завершить настройку.
Благодаря правилам безопасности по умолчанию любой аутентифицированный пользователь может записывать любые данные в облачное хранилище. Позже в этой лабораторной работе мы сделаем наше хранилище более безопасным.
4. Подключитесь к своему проекту Firebase
Интерфейс командной строки (CLI) Firebase позволяет использовать Firebase Hosting для локального обслуживания вашего веб-приложения, а также для развертывания вашего веб-приложения в проекте 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.
Откройте EmulatorUI
В веб-браузере перейдите по адресу 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, который затем можно прочитать, обновить или удалить с помощью функций updateFoc
и deleteDoc
AngularFire:
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/
Это почти идентично созданию поста о путешествии, поэтому поставьте себе задачу реализовать это самостоятельно или ознакомьтесь с реализацией ниже:
async addStop(travelId: string) {
...
const ref = await addDoc(collection(this.firestore, `travels/${travelId}/stops`), stopData)
setDoc(ref, {...stopData, id: ref.id})
}
Отлично! Функции Firestore были реализованы в сервисе Travel, так что теперь вы можете увидеть их в действии.
Использование функций 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
вызывается в конструкторе для получения наблюдаемого массива всех путешествий.
В случае, когда необходимы только перемещения текущего пользователя, используйте функцию 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-объекты. Cloud Storage предназначен для хранения файлов и двоичных объектов. В этом приложении вы будете использовать его, чтобы позволить пользователям делиться фотографиями из путешествий.
Аналогично с 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 Hosting! Все данные и аналитика будут доступны в вашей консоли Firebase.
Для получения дополнительных возможностей, касающихся AngularFire, функций и правил безопасности, не забудьте ознакомиться с дополнительными шагами ниже, а также с другими Firebase Codelabs !
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;
}
}
}