Гид по технологиям

Создание составного индекса Firebase и сложные запросы в Angular

7 min read Разработка Обновлено 13 Dec 2025
Составной индекс Firebase в Angular — шаги и примеры
Составной индекс Firebase в Angular — шаги и примеры

Большая библиотека с множеством книг на полках

Введение

Firebase Cloud Firestore — это NoSQL база данных в облаке, которая отлично интегрируется с Angular через библиотеку @angular/fire. При выполнении запросов с фильтрами по нескольким полям Firestore может потребовать создание составного индекса (composite index). Индекс ускоряет поиск и позволяет системе сопоставлять комбинации полей в запросе.

В этом материале вы получите:

  • Полный пошаговый пример настройки Angular + Firebase.
  • Пример сервисного слоя для выполнения двух связанных запросов: сначала поиск поставщика по имени, затем поиск продукта с наименьшим количеством на складе.
  • Как создать составной индекс в Firebase и проверить его статус.
  • Альтернативные подходы, чеклисты ролей, сценарии отказа и рекомендации по безопасности.

Важно: даны практические решения и советы по отладке; не используются фиктивные метрики.

Основные понятия в одну строку

  • Cloud Firestore: NoSQL документная база данных от Firebase.
  • Составной индекс: индекс, покрывающий комбинацию нескольких полей для одного запроса.
  • AngularFire: официальная библиотека интеграции Angular с Firebase.

Подготовка Angular-приложения и базы данных Firebase

Перед тем как писать запросы, выполните базовую настройку: создайте Angular-проект, проект Firebase, подключите Angular к Firebase и заполните коллекции Product и Supplier.

  1. Если у вас ещё нет Angular-приложения, создайте новый проект:
ng new new-angular-app
  1. Создайте проект в консоли Firebase и добавьте Cloud Firestore.

  2. В Cloud Firestore создайте две коллекции: Product и Supplier. Связь реализована через поле supplierId в документах Product — поставщик может иметь множество товаров.

  3. Пример данных для коллекции Product (поля name и productId и supplierId — строки; price и inStock — числа):

Document IDFields
product1- name: “Ribbons”\n- price: 12.99\n- inStock: 10\n- productId: “P1”\n- supplierId: “S1”
product2- name: “Balloons”\n- price: 1.5\n- inStock: 2\n- productId: “P2”\n- supplierId: “S1”
product3- name: “Paper”\n- price: 2.99\n- inStock: 20\n- productId: “P3”\n- supplierId: “S1”
product4- name: “Table”\n- price: 199\n- inStock: 1\n- productId: “P4”\n- supplierId: “S2”

Вот пример как это выглядит в консоли: Таблица Product в базе Firebase

  1. Пример данных для коллекции Supplier (все поля — строки):
Document IDFields
supplier1- name: “Arts and Crafts Supplier”\n- location: “California, USA”\n- supplierId: “S1”
supplier2- name: “Amazing Tables”\n- location: “Sydney, Australia”\n- supplierId: “S2”

Пример записи: Таблица Supplier в базе Firebase

  1. Установите AngularFire в приложение:
npm i @angular/fire
  1. Войдите в консоль Firebase, откройте Project settings и добавьте Firebase в ваше приложение (кнопка со значком угловых скобок). Настройки проекта и кнопка добавления

  2. Скопируйте конфигурацию Firebase для вашего приложения. Конфигурация Firebase для подключения

  3. В файл environments/environment.ts вставьте объект конфигурации, заменив значения на свои:

export const environment = {
  production: false,
  firebaseConfig: {
    apiKey: "ВАШ_API_KEY",
    authDomain: "ВАШ_PROJECT.firebaseapp.com",
    projectId: "ВАШ_PROJECT",
    storageBucket: "ВАШ_PROJECT.appspot.com",
    messagingSenderId: "ВАШ_SENDER_ID",
    appId: "ВАШ_APP_ID"
  }
};
  1. В src/app/app.module.ts импортируйте модули AngularFire и AngularFirestore:
import { environment } from "../environments/environment";
import { AngularFireModule } from '@angular/fire/compat';
import { AngularFirestoreModule } from "@angular/fire/compat/firestore";

@NgModule({
  imports: [
    AngularFirestoreModule,
    AngularFireModule.initializeApp(environment.firebaseConfig)
  ]
})
export class AppModule { }

Пример: сложный запрос в сервисе Angular

Архитектурное решение: вынесите логику работы с Firestore в сервис. В нашем примере сначала получаем поставщика по имени, затем по supplierId — продукт с минимальным значением inStock.

Создайте папку services и файл service.ts в ней. Новый файл services

service.ts:

import { Injectable } from '@angular/core';
import { AngularFirestore } from '@angular/fire/compat/firestore';

@Injectable({
  providedIn: 'root'
})
export class Service {
  constructor(private db: AngularFirestore) { }

  getSupplier(name: string) {
    return new Promise((resolve) => {
      this.db.collection('Supplier', ref => ref.where('name', '==', name)).valueChanges().subscribe(supplier => resolve(supplier));
    });
  }

  getProductsFromSupplier(supplierId: string) {
    return new Promise((resolve) => {
      this.db.collection('Product', ref => ref
        .where('supplierId', '==', supplierId)
        .orderBy('inStock')
        .startAt(0)
        .limit(1)
      ).valueChanges().subscribe(product => resolve(product));
    });
  }
}

Пояснения по коду:

  • getSupplier возвращает массив совпавших документов (даже если это один объект) — используйте supplier[0] для первого результата.
  • getProductsFromSupplier сортирует по inStock и ограничивает результат одним документом с наименьшим inStock.
  • В реальном приложении лучше отписываться от подписок и обрабатывать ошибки.

Интеграция в компонент

В src/app/app.component.ts импортируйте сервис и используйте асинхронную логику:

import { Component, OnInit } from '@angular/core';
import { Service } from 'src/app/services/service';

@Component({ selector: 'app-root', templateUrl: './app.component.html' })
export class AppComponent implements OnInit {
  products: any;

  constructor(private service: Service) { }

  ngOnInit(): void {
    this.getProductStock();
  }

  async getProductStock() {
    try {
      let supplier = await this.service.getSupplier('Arts and Crafts Supplier');
      if (!supplier || !supplier.length) {
        console.warn('Поставщик не найден');
        this.products = [];
        return;
      }
      this.products = await this.service.getProductsFromSupplier(supplier[0].supplierId);
    } catch (err) {
      console.error('Ошибка при загрузке данных', err);
      this.products = [];
    }
  }
}

Шаблон компонента (src/app/app.component.html):

Products with lowest stock from "Arts and Crafts Supplier"

Name: {{item.name}}

Number in stock: {{item.inStock}}

Price: ${{item.price}}

Запустите приложение:

ng serve

Откройте http://localhost:4200

Если данные не отображаются, откройте инструменты разработчика: правый клик → Inspect. Опция Inspect в Chrome

В консоли может появиться ошибка с приглашением создать индекс. Ошибка в консоли с требованием индексировать запросы

Создание составного индекса

Когда Firestore требует индекс, он генерирует ссылку в сообщении об ошибке, ведущую прямо на страницу создания индекса.

  1. Нажмите на ссылку в сообщении об ошибке.
  2. Войдите в аккаунт Firebase, если требуется.
  3. Подтвердите создание индекса, нажав Create index. Создать составной индекс в Firebase
  4. Ожидайте изменения статуса индекса с Building на Enabled. Время сборки зависит от размера коллекций. Список составных индексов в Firebase
  5. После того как индекс станет Enabled, обновите страницу приложения — запрос выполнится корректно. Страница Chrome с данными товара с минимальным запасом

Важно: индекс создаётся на уровне проекта Firebase и влияет на все приложения, использующие этот проект.

Когда составной индекс не нужен или не подходит

  • Запрос использует только одно поле и порядок по нему — для таких случаев чаще всего хватает автоматических индексов Firestore.
  • Частые и сложные агрегации лучше выносить в фоновые задачи (Cloud Functions) и хранить результаты в отдельной коллекции.
  • Если вы делаете много фильтров и соединений, рассмотрите денормализацию данных — добавление полей или документов, чтобы сократить количество соединений.

Альтернативные подходы

  • Денормализация данных: дублируйте необходимые значения (например, количество на складе) в документе поставщика, если он содержит небольшую часть информации.
  • Cloud Functions: выполняйте сложную агрегацию на сервере по расписанию и сохраняйте результат в отдельной коллекции.
  • Realtime Database: если нужна очень простая модель и низкая задержка, но без сложных запросов — рассмотреть Realtime Database.
  • Внешняя аналитика: экспорт данных в BigQuery для сложной аналитики.

Модель мышления (mental model)

Представьте индекс как телефонную книгу: вместо поиска по всему списку документов, Firestore использует индекс, чтобы быстро найти документы по комбинации полей, указанной в запросе. Без индекса система вынуждена выполнять более дорогой обход, поэтому Firestore требует создать индекс заранее.

Процесс создания индекса — мини-методология

  1. Выполнить запрос из приложения.
  2. Получить сообщение об ошибке с ссылкой на создание индекса.
  3. Перейти по ссылке и подтвердить создание индекса.
  4. Подождать пока статус станет Enabled.
  5. Обновить приложение и проверить результат.

Decision tree для реагирования на ошибку индекса

flowchart TD
  A[Пользователь запускает запрос] --> B{Появилась ошибка в консоли}
  B -- Нет --> C[Запрос выполнен успешно]
  B -- Да --> D[Перейти по ссылке из ошибки]
  D --> E{Создать индекс?}
  E -- Да --> F[Нажать Create index и ждать Enabled]
  E -- Нет --> G[Рассмотреть альтернативу: денормализация/Cloud Function]
  F --> H[Обновить страницу]
  H --> I[Проверить результат]

Чеклист по ролям

Разработчик:

  • Развернуть Angular и подключить AngularFire.
  • Вынести логику запросов в сервис.
  • Обрабатывать отсутствие данных и ошибки.

DevOps / Администратор Firebase:

  • Создать проект Firebase и включить Firestore.
  • Проверить права доступа правил безопасности Firestore.
  • Отслеживать статус индексов в консоли Firebase.

Безопасность / Privacy:

  • Проверить правила безопасности Firestore, чтобы пользователи видели только разрешённые данные.
  • Минимизировать хранение личных данных.

Playbook: добавить составной индекс — пошагово

  1. Выполнить запрос в приложении и просмотреть ошибку в консоли браузера.
  2. Перейти по ссылке «Create index».
  3. Подтвердить параметры индекса и создать.
  4. Мониторить процесс сборки индекса (может занять минуты).
  5. После статуса Enabled — проверить приложение.
  6. При повторяющихся проблемах проверить порядок полей в запросе и индексах.

Критерии приёмки

  • Индекс создан и имеет статус Enabled.
  • Запрос возвращает ожидаемые данные на странице приложения.
  • В консоли браузера нет ошибок связанных с индексами.
  • Правила безопасности Firestore позволяют доступ к запрашиваемым документам.

Обработка ошибок и план отката

  • Если индекс строится слишком долго: проверить объём коллекции, временно использовать тестовую выборку или выполнить агрегацию через Cloud Function.
  • Если новый индекс нарушил производительность других запросов: удалить индекс или скорректировать его поля.
  • Откат изменений конфигурации — восстановить предыдущее состояние rules или удалить добавленный индекс через консоль.

Безопасность и приватность

  • Ограничьте чтение коллекций правилами Firestore: разрешать чтение только аутентифицированным пользователям или по условию.
  • Не храните в открытых документах чувствительные личные данные.
  • Для соответствия локальным требованиям приватности (например, GDPR) минимизируйте объем сохраняемых персональных данных и документируйте процесс обработки данных.

Совместимость и миграция

  • angular/fire имеет версии compat и modular; в примерах используется compat API. При переходе на модульную версию API потребует другой синтаксис импорта и инициализации.
  • При обновлении Angular и angular/fire проверьте changelog и миграционные инструкции.

Частые ошибки и как их исправлять

  • Ошибка индекса — перейти по ссылке в консоли и создать индекс.
  • Пустой результат из getSupplier — проверяйте точность имени и регистр символов.
  • Подписка без отписки — используйте take(1) или unsubscribe, чтобы избежать утечек памяти.

Локальные рекомендации для разработчиков

  • Разработчикам, работающим из локальной сети, стоит убедиться, что доступ к Firebase не ограничён прокси/брандмауэром.
  • Для тестирования больших наборов данных используйте эмулятор Firebase (Firestore emulator) — он позволяет быстро тестировать индексирование и запросы локально.

Заключение

Составные индексы — обычная часть работы с Cloud Firestore при выполнении запросов по комбинации полей. Используйте автоматически сгенерированную ссылку из ошибки, чтобы быстро создать индекс, следите за статусом индекса и учитывайте альтернативные подходы (денормализация, Cloud Functions) для случаев с высокой нагрузкой или сложной агрегацией.

Краткие рекомендации:

  • Держите запросы простыми и предсказуемыми.
  • Создавайте сервисы для работы с Firestore и обрабатывайте ошибки и отсутствующие данные.
  • Планируйте создание индексов вместе с дизайном схемы данных.

Конец руководства.

Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

Twitter-бот на Raspberry Pi: твиты с температурой и фото
Raspberry Pi

Twitter-бот на Raspberry Pi: твиты с температурой и фото

Навигация и выделение текста на iPhone и iPad
Руководство

Навигация и выделение текста на iPhone и iPad

Неограниченная история Bash — настройка и советы
Linux

Неограниченная история Bash — настройка и советы

Исправить ошибку vcruntime140_app.dll в Windows
Windows

Исправить ошибку vcruntime140_app.dll в Windows

Как превратить монитор в телевизор
Гаджеты

Как превратить монитор в телевизор

Аппаратное ускорение в Opera GX: включить или отключить
Браузеры

Аппаратное ускорение в Opera GX: включить или отключить