Статьи

Создание трехстороннего связывания данных с FireBase и AngularJS

Как вы, возможно, знаете, FireBase действует как бэкэнд для ваших приложений в реальном времени. Ну, мы все знаем, как это круто. Но знаешь что круче? Это AngularJS + FireBase. FireBase имеет официально выпущенную привязку под названием AngularFire, которая приносит всю радость и радость FireBase в AngularJS. Как разработчики AngularJS, мы так любим привязку данных, что даже мечтали об этом! С AngularFire мы можем создавать некоторые действительно крутые вещи, используя преимущества трехсторонней привязки данных в отличие от традиционной двусторонней привязки данных в AngularJS. Из этого туториала вы узнаете о трехсторонней привязке при создании простого приложения реального времени с использованием AngularFire. В этой статье предполагается, что читатель уже знает о FireBase и уже проделал некоторую предварительную разработку в AngularJS.

Что такое трехстороннее связывание данных

В AngularJS наша объемная модель и представление остаются синхронизированными благодаря двустороннему связыванию данных. Но если вы представите AngularFire и тем самым будете использовать FireBase в качестве бэкэнда, вы получите трехстороннюю привязку. По сути, вы можете привязать данные вашей модели к местоположению FireBase, чтобы при каждом изменении ваших моделей эти изменения автоматически передавались в FireBase. Точно так же всякий раз, когда данные в определенном местоположении FireBase изменяются, ваша локальная модель области также обновляется. И, поскольку наше представление и модель области уже синхронизированы, это создает трехстороннюю привязку данных. Очевидное преимущество заключается в том, что он позволяет создавать классные приложения в реальном времени, в которых данные часто изменяются и эти изменения передаются всем подключенным пользователям. Все это происходит без каких-либо значительных усилий. Если вы создаете приложение для чата, многопользовательское игровое приложение или систему вещания, вы можете использовать эту функцию.

Начиная

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

  • Начать трансляцию
  • Посмотреть трансляцию

Чтобы начать трансляцию, пользователь должен указать название для трансляции. Как только пользователи введут имя и нажмут кнопку «Пуск», они могут начать трансляцию. Чтобы посмотреть трансляцию, им нужно выбрать трансляцию из выпадающего меню.

Базовая Архитектура

Для начала нам нужно выяснить, как хранить наши широковещательные данные. Сначала войдите в FireBase и создайте новое хранилище FireBase. Я создал FireBase под названием angularfiredemo , и поэтому мы будем использовать URL-адрес https://angularfiredemo.firebaseio.com для хранения данных. Обратите внимание, что каждое местоположение FireBase представлено URL-адресом, и мы можем иметь несколько широковещательных рассылок для обработки множества пользователей, которые будут использовать нашу систему. Давайте сохраним все наши трансляции по https://angularfiredemo.firebaseio.com/broadcasts . Каждая трансляция состоит из двух частей: имени и контента, представленных парами ключ / значение. Образцы данных вещания сохраняются, как показано на следующем рисунке.

Пример трансляции

Чтобы просмотреть трансляцию, пользователям нужно будет выбрать конкретное имя трансляции, представленное ключом в нашей FireBase. Следующим шагом для нас является создание приложения AngularJS вокруг этих ключевых моментов.

Те из вас, кто заинтересован в конечном продукте, могут ознакомиться с демонстрацией в Plunker . Чтобы получить опыт в реальном времени, откройте два экземпляра вашего браузера рядом. Во-первых, дайте вашей трансляции имя, запустите его, а затем начните писать. Во втором случае выберите вещание, которое вы только что начали, из выпадающего меню. Теперь вы можете посмотреть трансляцию в режиме реального времени.

Сборка приложения

Для начала нам понадобятся следующие скрипты:

  • AngularJS : основной скрипт AngularJS.
  • FireBase : скрипт для включения поддержки FireBase.
  • AngularFire : привязка FireBase для AngularJS.
  • Angular Route : для поддержки маршрутизации в AngularJS.
  • Angular Sanitize : для санации входящих данных из FireBase.

Для быстрого создания макета мы будем использовать Bootstrap CSS.

Шаг 1

Первым шагом является создание нашего основного модуля приложения, который делается так:

 angular.module('firebaseDemo', ['firebase', 'ngSanitize', 'ngRoute']); angular.module('firebaseDemo').constant('FIREBASE_URL','https://angularfiredemo.firebaseio.com/broadcasts'); 

Наш основной модуль зависит от трех других модулей: ngSanitize , ngRoute и ngRoute . Все функциональные возможности AngularFire заключены в его собственный модуль, firebase . ngSanitize и ngRoute используются для ngRoute данных и поддержки маршрутизации соответственно. Мы также определили константу, FIREBASE_URL , которая представляет местоположение, в котором хранятся все трансляции.

Шаг 2

Теперь давайте создадим фабрику, которая извлекает трансляции из FireBase.

 angular.module('firebaseDemo').factory('broadcastFactory', function($firebase,FIREBASE_URL) { return { getBroadcast: function(key) { return $firebase(new Firebase(FIREBASE_URL + '/' + key)); }, getAllBroadcasts: function() { return $firebase(new Firebase(FIREBASE_URL)); } }; }); 

Наша фабрика broadcastFactory объявляет зависимость от FIREBASE_URL , которая представляет местоположение наших трансляций. Наша фабрика также зависит от сервиса AngularFire, который называется $firebase . Он принимает объект FireBase и возвращает специальный объект, который синхронизируется с удаленным местоположением FireBase. Он имеет такие функции, как $add() , $set() , $child() и т. Д. Для работы с данными. Всякий раз, когда выполняется обновление для этого локального объекта, изменение отправляется в удаленное местоположение FireBase.

Фабрика имеет две функции:

  • getBroadcast(key) : возвращает один объект, представляющий трансляцию. Этот объект имеет свойство с именем $value которое представляет контент для трансляции. Мы используем этот объект для создания трехстороннего связывания, чтобы все типы пользователей постоянно синхронизировались с удаленным местоположением FireBase.
  • getBroadcasts() : эта функция возвращает объект, который имеет все широковещательные сообщения в качестве своих свойств. Мы предоставляем эти данные пользователям, чтобы они могли выбрать трансляцию для просмотра.

Шаг 3

Следующим шагом является создание нашего основного представления в index.html :

 <!DOCTYPE html> <html ng-app="firebaseDemo"> <head> <meta charset="utf-8" /> <title>AngularFire Demo</title> <link rel="stylesheet" href="style.css" /> <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" /> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.min.js" data-semver="1.2.16"></script> <script src="https://code.angularjs.org/1.2.16/angular-sanitize.js" data-semver="1.2.16"></script> <script src="https://code.angularjs.org/1.2.16/angular-route.js" data-semver="1.2.16"></script> <script src="https://cdn.firebase.com/js/client/1.0.6/firebase.js"></script> <script src="https://cdn.firebase.com/libs/angularfire/0.7.1/angularfire.min.js"></script> <script src="app.js"></script> </head> <body> <div class="container"> <br/> <div class="row"> <div class="col-xs-5 col-xs-offset-1 text-right"> <a class="btn btn-lg btn-primary" href="/write">Write Something</a> </div> <div class="col-xs-5 text-left"> <a class="btn btn-lg btn-success" href="/view">View a Broadcast</a> </div> </div> <div ng-view></div> </div> </body> </html> 

Основной вид имеет две ссылки:

  • Write Something : загружает новый маршрут AngularJS, который позволяет пользователям начать трансляцию.
  • View a Broadcast : загружает маршрут AngularJS, который позволяет пользователям просматривать трансляцию.

Вы также можете увидеть директиву ng-view будут загружены разные маршруты. Весь наш код AngularJS присутствует в app.js

Примечание . Любое серьезное приложение AngularJS должно учитывать модульность кода по уровням или функциям. Для этого простого приложения я поместил все компоненты AngularJS, такие как controllers и directives в один файл app.js Но это, конечно, не тот путь, который нужно использовать для крупномасштабных приложений AngularJS.

Шаг 4

Затем создайте два разных вида: один для трансляции, а другой для просмотра. Мы также настроим маршруты с помощью $routeProvider . Следующий код взят из views/write.html .

 <hr/> <div class="row"> <div class="col-xs-4 col-xs-offset-3"> <input type="text" class="form-control input-lg" ng-model="broadcastName" placeholder="Type your broadcast name here" /> </div> <div class="col-xs-5"> <button class="btn btn-lg btn-success" ng-click="startBroadcast()" ng-disabled='isButtonEnabled()'>Start</button> </div> </div> <h1 class="text-center">Write Something. . .</h1> <div class="row"> <div class="col-xs-8 col-xs-offset-2"> <div id="editor" demo-editor model="broadcast" class="well"> </div> </div> </div> 

Не беспокойтесь о <div id="editor" demo-editor></div> . demoEditor – это пользовательская директива, которая будет показана далее.

Соответствующий контроллер, связанный с этим представлением:

 angular.module('firebaseDemo').controller('BroadcastController', function($scope, broadcastFactory) { $scope.isEditable = false; $scope.broadcastName = ''; $scope.isButtonEnabled = function() { return ($scope.broadcastName === 'undefined') || ($scope.broadcastName.length < 1); }; $scope.startBroadcast = function() { $scope.isEditable = true; $scope.broadcastFromFireBase = broadcastFactory.getBroadcast($scope.broadcastName); $scope.broadcastFromFireBase.$set(''); $scope.broadcastFromFireBase.$bind($scope, 'broadcast'); }; }); 

Наш контроллер имеет две зависимости: $scope и broadcastFactory . Область видимости isEditable используется, чтобы указать, активен ли наш редактор. Когда пользователь нажимает кнопку «Пуск» в нашем представлении, редактор становится активным и принимает входные данные. Наше представление также содержит текстовое поле, которое привязано к модели объема, broadcastName . Перед началом трансляции мы просим наших пользователей дать название трансляции. Содержимое трансляции будет храниться против этого ключа в нашей FireBase.

Функция isButtonEnabled() используется для отслеживания пустого имени трансляции. Если так, то мы отключаем кнопку «Пуск». Эта функция используется с директивой ng-disabled прикрепленной к кнопке Пуск.

Функция startBroadcast() используется для startBroadcast() трансляции. Эта функция вызывается при нажатии кнопки «Пуск» благодаря директиве ng-click . Внутри этой функции мы устанавливаем isEditable модели isEditable значение true , активируя наш редактор. Затем мы вызываем функцию broadcastFactory.getBroadcast() , передавая ключ broadcastName в качестве ключа. Затем мы устанавливаем пустую строку в этом месте, используя $set() . Это действует как начальный широковещательный контент. Обратите внимание, что эта операция создает нового дочернего https://angularfiredemo.firebaseio.com/broadcasts . Имя этого дочернего элемента совпадает со значением $scope.broadcastName . Таким образом, место, где будет храниться наша новая трансляция, – https://angularfiredemo.firebaseio.com/broadcasts/<broadcastName> . Обратите внимание, что исходное содержимое этого местоположения будет пустой строкой.

В конце мы выполним самую важную операцию, которая создаст трехстороннюю привязку. Операция $scope.broadcastFromFireBase.$bind($scope, 'broadcast'); Является ли это. Из-за этого местоположение удаленного FireBase синхронизируется с нашей локальной моделью охвата, broadcast . Эта локальная модель также привязана к нашему редактору. В результате, когда пользователь вводит что-то в редактор, модель broadcast обновляется. И, благодаря трехсторонней привязке, удаленный контент FireBase также обновляется этим новым широковещательным контентом.

Теперь давайте перейдем к нашему следующему представлению, которое существует в views/view.html .

 <h1 class="text-center">Live Broadcast</h1> <div class="row"> <div class="col-xs-4 col-xs-offset-4"> <select ng-model="broadcastToView" ng-change="broadcastSelected()" class="form-control" ng-options="k as k for (k, v) in broadcasts"> <option value="">{{dropdownMessage}}</option> </select> </div> </div> <div class="row"> <div class="col-xs-8 col-xs-offset-2"> <div id="editor" class="well" ng-bind-html="broadcast.$value"> </div> </div> </div> 

Соответствующий код контроллера показан ниже.

 angular.module('firebaseDemo').controller('BroadcastViewerController', function($scope, broadcastFactory) { $scope.dropdownMessage = 'Retrieving Broadcasts...'; $scope.broadcasts = broadcastFactory.getAllBroadcasts(); $scope.broadcastSelected = function() { $scope.broadcast = broadcastFactory.getBroadcast($scope.broadcastToView); } $scope.broadcasts.$on('loaded', function() { $scope.dropdownMessage = 'Select a broadcast'; }); }); 

Когда маршрут загружается, мы получаем все трансляции, вызывая broadcastFactory.getAllBroadcasts() . Помните, что это единственный объект, который содержит все дочерние объекты в качестве свойств. Имена свойств являются широковещательными именами, а их значения представляют широковещательный контент. Эти имена трансляций показываются пользователям в раскрывающемся списке, чтобы они могли выбрать трансляцию для просмотра.

Когда значение раскрывающегося списка изменяется из-за выбора, вызывается функция broadcastSelected() . Эта функция использует broadcastFactory.getBroadcast() для получения конкретного вещательного объекта. Этот объект имеет свойство с именем $value которое представляет фактическое содержимое. Наконец, мы привязываем это значение к нашему div помощью ng-bind-html чтобы пользователь мог видеть трансляцию в реальном времени. ng-bind-html используется, потому что местоположение FireBase также может иметь HTML-контент. Из-за этой директивы мы включили модуль ngSanitize (для ngSanitize данных), без которого директива сгенерирует исключение.

Также обратите внимание, что при загрузке представления данные FireBase не будут синхронизироваться немедленно. $scope.broadcasts будет иметь действительное значение через несколько секунд. Итак, пока данные не синхронизируются с сервера, хорошо показать, что мы извлекаем выпадающие данные. Вот почему у меня есть модель $scope.dropdownMessage которая используется в качестве первого <option> в раскрывающемся списке. Когда данные фактически синхронизируются с сервера, запускается loaded событие, и мы изменяем значение dropdownMessage на Select a broadcast .

Шаг 5

У нас также есть пользовательская директива demoEditor , которая преобразует div в редактируемый div чтобы пользователи могли вводить в него demoEditor . Я знаю, что мы могли бы пойти с простой textarea , но что если вы захотите дать пользователям возможность писать HTML? Может быть, какой-то WYSIWYG редактор? В этом случае нам нужен div , где пользователи могут печатать. Вот наше определение директивы:

 angular.module('firebaseDemo').directive('demoEditor', function(broadcastFactory) { return { restrict: 'AE', link: function(scope, elem, attrs) { scope.$watch('isEditable', function(newValue) { elem.attr('contenteditable', newValue); }); elem.on('keyup keydown', function() { scope.$apply(function() { scope[attrs.model] = elem.html().trim(); }); }); } }; }); 

Директива довольно проста. div редактора изначально недоступен для редактирования и становится редактируемым, когда пользователь нажимает кнопку «Пуск». Поскольку это элемент div , вы не можете присоединить ng-model для синхронизации его содержимого с моделью. Итак, мы подключаем слушатель keyup keydown для синхронизации модели области с этим содержимым div . Имя модели scope (которое мы хотим сохранить обновленным) передается директиве как атрибут. В случае, если вы пропустили это, директива используется в виде:

 <div id="editor" demo-editor model="broadcast" class="well"></div> 

Атрибут model определяет модель scope для синхронизации. Также обратите внимание, что эта директива не создает новую область видимости. Он использует родительскую область.

Шаг 6

Давайте настроим маршруты и получим удовольствие от трехстороннего связывания в действии!

 angular.module('firebaseDemo').config(function($routeProvider, $locationProvider) { $routeProvider.when('/write', { controller: 'BroadcastController', templateUrl: '/views/write.html' }).when('/view', { controller: 'BroadcastViewerController', templateUrl: '/views/view.html' }).otherwise({ redirectTo: '/write' }); $locationProvider.html5Mode(true); }); 

Вывод

Надеюсь, вам понравилось экспериментировать с AngularJS и FireBase. AngularJS в сочетании с FireBase может использоваться для создания потрясающих приложений, которые синхронизируются в реальном времени. Я рекомендую вам ознакомиться с документацией AngularFire, чтобы узнать больше о методах API. Приятного реального времени !!

Полный исходный код можно скачать с GitHub .