AngularInDepth уходит от Medium. Более свежие статьи размещаются на новой платформе inDepth.dev. Спасибо за то, что участвуете в глубоком движении!

В последнее время я много работал с Google Firebase, чтобы создать несколько побочных проектов, и я хотел написать о своем опыте. Платформа Firebase действительно мощная и предоставляет услуги, которые покрывают большинство потребностей вашего приложения. Он предлагает услуги хостинга, аутентификации, хранения баз данных, файловых хранилищ и т. Д. Чтобы использовать эти сервисы, вы можете создать настройку Node Express или даже использовать Firebase Cloud Functions и создать API… или есть еще более простой способ сделать это с библиотекой AngularFire.

В следующем посте мы подробно расскажем о библиотеке AngularFire и о том, как она работает под капотом. Я собираюсь пройти через написанное мною простое приложение для создания списка покупок и показать, как с помощью AngularFire все работает как по волшебству.

Обратите внимание, что в этом сообщении предполагается, что вы уже знакомы с приложениями Firebase и консолью Firebase. Для более подробного изучения, пожалуйста, ознакомьтесь с моим сообщением на RhythmAndBinary.com здесь, а также с моим сообщением Angular-In-Depth о развертывании здесь.

AngularFire

AngularFire - это официальная библиотека, соединяющая Angular с Firebase. На момент написания этой статьи AngularFire предоставляет подключения к следующим службам Firebase:

  1. Аутентификация
  2. Хранилище файлов
  3. Cloud Firestore (база данных NoSQL)

Служба аутентификации предоставляет OAuth через несколько провайдеров. На следующем снимке экрана показано, что доступно в консоли после настройки приложения Firebase:

AngularFire предоставляет обновления в реальном времени через наблюдаемые потоки и сильно оптимизирован. Под капотом AngularFire также используется специальный протокол, называемый WebChannel, который работает для создания синхронизации в реальном времени (см. Ссылку здесь для получения дополнительной информации). Существуют также конечные точки API действий, которые хорошо интегрируются с NgRx, если вы хотите использовать это вместе с AngularFire.

AngularFire также имеет отличный README, который проведет вас через множество основ от начальной установки до реальных примеров кода для различных сервисов. Ознакомьтесь с их README здесь.

В оставшейся части сообщения я буду использовать аутентификацию и Cloud Firestore. Функция файлового хранилища также действительно хороша и хорошо документирована здесь.

Продукты Firebase

Чтобы продемонстрировать AngularFire, я собираюсь обратиться к базовому приложению со списком покупок, которое можно просмотреть здесь. Вы также можете просмотреть исходный код на GitHub здесь.

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

В примере приложения есть только один компонент. Все фрагменты кода, которые я показываю ниже, взяты из одних и тех же файлов grocery-list.component.ts и grocery-list.component.html.

Я собираюсь сосредоточиться на аутентификации AngularFire и Cloud Firestore. Я собираюсь представить, как подключить эти службы к вашему приложению, а затем несколько заметок о внутреннем устройстве и о том, как все это работает (как по волшебству).

Аутентификация

Всем приходится иметь дело с аутентификацией для приложений. Очевидно, что степень, в которой вы это делаете, зависит от того, что вы храните. Firebase упрощает работу с аутентификацией, предоставляя службу аутентификации. Это экономит ваше время и усилия, так что вам не нужно внедрять серверную часть провайдера. Просто вставив объект AngularFireAuth в свое приложение, вы сразу же получите доступ к службе проверки подлинности Firebase и очень быстро начнете работать.

Итак, перейдем к коду!

Во-первых, вам нужно включить аутентификацию в вашем проекте Firebase. В консоли Firebase обязательно перейдите к аутентификации и включите ее, выбрав метод входа. Firebase позволяет использовать несколько поставщиков OAuth, включая Google, Facebook и несколько других. Для проекта, который я демонстрирую, я просто использую стандартную электронную почту / пароль.

Включив его (и добавив конечную точку в файл environment.ts), вы можете подключить его к своему проекту, внедрив экземпляр AngularFireAuth в конструктор компонента. Вот что я сделал для своего приложения со списком покупок:

constructor(public afs: AngularFirestore, public afAuth: AngularFireAuth) {
    this.afAuth.auth.onAuthStateChanged(user => {
      if (user) {
        // show email in welcome message
        this.email = user.email;
        // call method that selects when authenticated
        this.selectItems(user.uid);
      }
    });
  }

Вдобавок, если вы заметили, я вызываю onAuthStateChanged, чтобы проверить, вошел ли в систему пользователь. Вызов onAuthStateChanged возвращает наблюдаемый поток, который имеет объект auser, который можно опросить. Этот user объект будет определен, если лицо, использующее приложение, было аутентифицировано. Здесь вы можете использовать базовый оператор if, чтобы определить, вошел ли кто-то в систему. Это особенно полезно, если кто-то нажимает кнопку «Обновить» на странице или уходит с главной страницы. Как только компонент сгенерирован, он проверяет, аутентифицирован ли пользователь, если да, то данные выбираются с помощью this.selectItems(user.uid).

Я также добавляю сюда объект AngularFirestore, но пока не обращаю на него внимания, поскольку это часть обсуждения позже.

Аутентификация с помощью AngularFireAuth

У объекта AngularFireAuth есть метод, который позволяет легко создавать пользователей в вашем приложении. В приложении со списком покупок я сделал следующее:

createUser(email: string, password: string) {
    this.afAuth.auth.createUserWithEmailAndPassword(email, password)
      .then(() => {
          // on success hide form and store variables in login
          // and then call the login method
          this.showCreateUserInputForm = false;
          this.loginUser(email, password);
      })
      .catch((error) => {
        alert(error);
      });
  }

В конечном итоге пользователь создается простым вызовом afAuth.auth.createUserWithEmailAndPassword. Все, что нужно передать здесь, - это просто имя пользователя и пароль, и метод вернет объект Promise с сообщением об успехе или ошибке. Модуль AngularFireAuth автоматически выполняет базовые проверки таких вещей, как уникальное имя пользователя и другие, которые вы обычно применяете с помощью стандартного кода.

В модуле AngularFireAuth также есть метод, который аутентифицирует (входит в систему) пользователей. Вот что я сделал для приложения со списком покупок:

loginUser(email: string, password: string) {
    this.afAuth.auth.signInWithEmailAndPassword(email, password)
      .then(() => {
        // on success populate variables and select items
        this.selectItems(this.afAuth.auth.currentUser.uid);
      })
      .catch((error) => {
        alert(error);
      });
  }

Как вы видите здесь, как только вы создали пользователя, он может пройти аутентификацию с помощью вызова afAuth.auth.signInWithEmailAndPassword. Как и при вызове метода create, вы просто передаете здесь адрес электронной почты и пароль. Модуль AngularFireAuth выполняет проверку адреса электронной почты и пароля за вас, без необходимости писать шаблонный код для обработки различных условий и т. Д. Даже сообщения об ошибках довольно легко понять и могут быть показаны пользователям напрямую.

Наконец, когда ваш пользователь готов покинуть приложение, существует базовый signOut метод, который обрабатывает этот процесс. Для приложения со списком покупок я использовал это здесь:

// async is not necessary here, just controlling the event loop
  async logoutUser() {
    await this.afAuth.auth.signOut()
      .catch(function(error) { alert(error); });

    this.email = '';
    this.password = '';
    this.showLoginUserInputForm = false;
    this.showCreateUserInputForm = false;
  }

Подключение AngularFireAuth к просмотру

Так что все это отлично подходит для кода компонента, но как это выглядит в шаблоне?

Самое замечательное в AngularFire заключается в том, что наблюдаемые потоки, которые возвращаются, могут просто использовать канал async. При некотором использовании директив *ngIf и *ngFor ваши данные могут отображаться в представлении без особых дополнительных усилий. Больше не нужно использовать флаги или различные условные проверки, просто используйте директивы, привязанные к наблюдаемым, и логика скрытия / отображения обрабатывается за вас.

Начнем с диалогового окна «Вход». Я использовал канал async с объектом AngularFireAuth в приложении со списком покупок следующим образом:

Как видите, *ngIf проверяет состояние user наблюдаемого с помощью канала async, чтобы определить, когда показывать контент, когда пользователь вошел в систему. Это действительно удобно, потому что с этой единственной строкой кода ваше приложение настроено для отображения или не показывать определенные места в зависимости от аутентифицируемого пользователя. Без этой настройки вам потребовались бы флаги или какая-то версия диалогового окна с компонентами для обработки такого же типа поведения. Наблюдаемые потоки, возвращаемые службой аутентификации, делают весь этот процесс очень простым.

Я также хочу отметить, что у службы аутентификации Firebase есть бесплатное всплывающее окно, которое позволяет вашим пользователям аутентифицироваться с помощью Google. Вы можете вызвать это всплывающее окно со своим кодом, следуя документации здесь.

В модуле AngularFireAuth есть много других замечательных моментов, и я рекомендую просмотреть официальные документы, когда у вас будет время.

Cloud Firestore

Cloud Firestore - это база данных NoSQL, предоставляемая Firebase. Эта служба обеспечивает синхронизацию данных в реальном времени, что делает обновления почти мгновенными. Используя AngularFire, вы также подключаете различные операции с базой данных для отправки событий, подобных NgRx. Эти события отправки работают аналогично тому, как редуктор и состояние управления хранилищем данных в приложении NgRx. Эффект, представленный здесь, в основном заключается в управлении удаленным состоянием, при котором обновления отправляются в Cloud Firestore, а затем передаются обратно всем потребителям.

Данные хранятся в документах и ​​коллекциях. Каждая запись в базе данных NoSQL считается «документом», который связан с другим «документом» через пару «ключ-значение». Это явно отличается от реляционной базы данных, которая хранит данные в столбцах и строках. При работе с этими данными в коде вы создаете объекты observablecollection и document, которые передают обновления прямо в ваше приложение из базы данных. Вы также можете визуально просмотреть данные в консоли после того, как они будут созданы, если вам нужно.

В моем приложении со списком покупок используется documents, что требует некоторых пояснений. Сначала я просто покажу пример с collection. Инструкции в README AngularFire показывают создание объектов коллекции со следующим:

Если вы посмотрите на пример здесь, объект items является наблюдаемым, который реагирует на любые изменения базы данных в реальном времени с помощью наблюдаемого потока valueChanges(). Это делается с помощью кода, совместимого с Redux, и скрытых веб-сокетов.

Пример в README продолжается, показывая, как канал async может использоваться для отображения информации базы данных непосредственно с *ngFor следующим образом:

Поскольку объект items является наблюдаемым потоком, любые обновления немедленно отображаются в вашем приложении. Это действительно мощно, потому что вся тяжелая работа с веб-сокетами и управлением состоянием сделана за вас.

Объекты документов Cloud Firestore

Для своего приложения со списком покупок я сначала выбираю документ, а затем ссылаюсь на вложенную коллекцию. Это немного сложнее, но я сделал это, потому что ввел данные на основе идентификаторов пользователей (подробнее об этом позже). Вот как я это сделал в приложении со списком покупок:

Как видите, я сначала выбираю конкретный документ с ключом userId, а затем подписываюсь на valueChanges() наблюдаемый поток коллекции GroceryItems в документе. Это следует той же модели, что и в примере из README, за исключением того, что это вложенная коллекция. Здесь valueChanges() относятся к коллекции GroceryItems, которая хранится в документе, созданном для userId аутентифицированного пользователя. Я расскажу подробнее о том, почему я сделал это ниже, но этот подход задокументирован в файле README angularFire2 здесь.

Взаимодействие с базой данных Cloud Firestore с помощью обычных операций CRUD также очень просто. AngularFire имеет set, update и delete методы, которые отправляют изменения непосредственно в экземпляр базы данных вашего приложения. Это упрощает использование основных операций в вашем коде, как вы видите с помощью методов addItem() и deleteItem() моего приложения со списком покупок здесь:

Для вызова методов вам просто нужен объект, который вы хотите изменить, и передать значение id, которое Cloud Firestore использует для сохранения уникальности каждой записи. Операция возвращает обещание, которое легко обрабатывается вашим кодом, как вы видите выше. Для получения дополнительной информации об этих операциях (и примерах) ознакомьтесь с README здесь.

Оптимизация операций с облачным хранилищем

Кроме того, я использовал подписку toauditTrail, чтобы иметь возможность отображать в консоли чтение и запись, которые передаются в консоли. Это использование auditTrail показывает, что настройка AngularFire очень похожа на то, как NgRx использует диспетчеризацию для управления состоянием. Включение auditTrail дает вам хороший способ увидеть, как поддерживается удаленное состояние с помощью наблюдаемых, как показано здесь в консоли (обратите внимание на add и removed):

Наконец, когда вы хотите отобразить данные Cloud Firestore в шаблоне представления, вы просто ссылаетесь на наблюдаемый объект, аналогично тому, как объект AngularFireAuth делал ранее. Вот как я сделал это с помощью приложения со списком покупок:

Здесь я ссылаюсь на наблюдаемый valueChanges(), чтобы получить ту же привязку, которую я могу использовать с каналом async. Любые обновления данных обновляются в реальном времени, поскольку удаленное состояние передается в наблюдаемый объект, созданный здесь с помощью valueChanges(). Существует также наблюдаемый поток для snapshotChanges(), который обрабатывает вариант использования, когда вы просто хотите визуализировать представление. Подробнее на snapshotChanges() здесь.

Безопасность

Теперь я хочу объяснить, почему я ранее вводил данные на основе идентификатора пользователя в Cloud Firestore. Поскольку файл environment.ts, развернутый с вашим приложением, открыт, любой может увидеть конечную точку базы данных для вашего экземпляра Cloud Firestore. Если хакеры посмотрят исходный код вашей страницы, они смогут увидеть конечную точку и затем попытаться получить данные с помощью консоли в браузере. Чтобы остановить это (и обезопасить данные), нужно создать в базе данных правила, определяющие, какие операции кто разрешает.

Когда вы впервые настраиваете свой экземпляр Firebase Cloud Firestore, вы можете либо настроить его в режиме «тестирования» (где он полностью открыт). После того, как вы будете готовы, вы можете заблокировать это с помощью настраиваемых правил сопоставления, встроенных прямо в консоль.

Firebase рекомендует создавать правила для управления чтением. Если вы вводите данные на основе userId запросов к базе данных, это означает, что чтение данных может выполняться только пользователем, который их создал в сеансе с аутентификацией. Вы делаете это, зайдя в консоль и установив значение на вкладке «Правила», как вы видите здесь:

Если вы заметили установленные правила, то в основном это просто совпадение с регулярными выражениями. Когда вы используете фигурные скобки {}, вы показываете значение как переменную, которую можно запросить (подробнее об этом через секунду). Значения для каждого match проходят по документам / коллекциям, составляющим экземпляр базы данных NoSQL.

Первое совпадение с /databases/{database}/documents - это просмотр вложенных коллекций базы данных NoSQL и применяется к любому документу в экземпляре базы данных, подключенном к вашему приложению.

Второе совпадение с /user/{userId}/GroceryItems/{document=**} означает любой документ, выбранный в коллекции user->userId->GroceryItems. Если вы заметили использование {userId} с фигурными скобками, «{}» делает значение userId доступным как переменную в консоли (то же самое делается и с {database}). Мы собираемся исследовать это значение, чтобы в следующий раз усилить безопасность.

Во втором матче вы заметите

allow read, create, update, delete: if request.auth.uid == userId;

Это первое правило среди совпадений, которое гласит: «Разрешены любые операции чтения, обновления или удаления, если авторизованный userId в сеансе совпадает с переменной userId». Это userId переменная, которую вы указали в фигурных скобках «{}». Это действительно важно, поскольку позволяет изолировать разрешения внутри этой коллекции, чтобы данные пользователя могли быть прочитаны только этим пользователем.

В документации Firebase есть дополнительная информация о том, как сделать больше правил здесь.

Заключение

Как я упоминал ранее, вся эта «магия» стала возможной с помощью наблюдаемых потоков и двунаправленных почти мгновенных каналов связи. AngularFire следует модели, аналогичной реализации NgRx, с управлением удаленным состоянием, но без формального редуктора. Наблюдаемые конечные точки потока, такие как valueChanges(), получают обновления состояния в базе данных в реальном времени. Эти обновления отправляются во все экземпляры, на которых запущено ваше приложение. Таким образом, где бы вы ни находились, данные вашего приложения синхронизируются с удаленными данными, хранящимися в Cloud Firestore. Все это значительно упрощает вашу жизнь как разработчика, поскольку вам нужно только подписаться на наблюдаемые потоки в вашем приложении. С помощью канала async вы можете контролировать то, что отображается, и ваши данные обновляются в реальном времени без необходимости вручную опрашивать источник данных на предмет обновлений.

Один из соавторов AngularFire (Дэвид Ист) дал хороший доклад, в котором все это объясняется гораздо более подробно. Особая благодарность Николасу Джеймисону из Angular-In-Depth за то, что прислал мне ссылку. Посмотрите выступление Дэвида, чтобы увидеть хорошие визуальные эффекты и лучшее понимание того, как работает AngularFire по сравнению с NgRx. Надеюсь, этот пост помог хорошо начать работу с AngularFire и помог вам начать использовать его в своих проектах.