Когда я начал программировать свое самое первое одностраничное приложение Angular (SPA), я заметил, что ресурсы для настройки и интеграции с Devise невелики или фрагментированы. Самое полезное руководство, которое я нашел, на самом деле было просто частью общего пошагового руководства по Angular с Rails. Были и другие ресурсы, которые были либо слишком сложными, либо продвинутыми, и они не прошли через начальные детские шаги. Одна из самых сложных задач для начинающего программиста — начать с нуля. Я знаю, потому что я один из этих людей.
Большая часть того, чему я научился на своем онлайн-курсе, была представлена небольшими, все более сложными компонентами. Я бы открыл лабораторию, а основа уже заложена, так что в настройке приложения с чистого листа не так много практики. Ради конечного времени завершения это имеет смысл. Кроме того, вам нужно всего лишь создать пару приложений с нуля, чтобы понять, как это делается. Если вы еще не добрались туда, это пошаговое руководство будет прямо в вашем переулке.
Как только я, наконец, заработал все части и мой первый проект Angular был запущен и запущен, я счел уместным отдать должное сообществу. Поскольку в настоящее время у меня недостаточно «очков репутации», чтобы отвечать на вопросы о StackOverflow, следующим лучшим решением будет создание собственного пошагового руководства по настройке Angular SPA на Rails с помощью Devise и Bootstrap. Ниже приводится ИМЕННО то, что я хотел бы найти в своих первоначальных исследованиях по этой теме.
Конечно, огромная часть веб-разработки заключается в том, чтобы решать сложные проблемы, не получая решения. Я чувствую, что иногда новому разработчику просто нужна рука помощи. Итак, вот оно.
Это руководство предназначено для начала работы. Предполагается, что у вас уже есть базовые знания об Angular, Rails, Devise и Bootstrap. Я решил не исследовать Active Record, однако я затронул Active Model Serializer, так как он необходим для отправки моделей на ваш внешний интерфейс Javascript. Об этом предмете можно узнать гораздо больше, и для этого потребовалась бы собственная серия руководств. Точно так же я приступаю к установке Bootstrap только до тех пор, пока не смогу убедиться, что он работает.
Вот репозиторий GitHub для этого пошагового руководства: https://github.com/jessenovotny/angular-devise-demo
Не стесняйтесь читать вместе с видео, которое я создал:
Для начала вам нужно открыть Терминал и перейти в папку, в которой вы хотите создать новое приложение. В этой демонстрации я на рабочем столе.
В Терминале вы запустите $ rails new YOUR-APP, который инициализирует Rails, создаст каталог со всем фреймворком и упакует все запеченные драгоценные камни. Если вы не знакомы, $ обозначает команду Терминала.
Откройте свой Gemfile, удалите gem ‘turbolinks’ (поскольку это вызывает проблемы с javascript и jQuery) и добавьте следующее:
gem ‘bower-rails’ gem ‘devise’ gem ‘angular-rails-templates’ #=> allows us to place our html views in the assets/javascript directory gem ‘active-model-serializer’ gem ‘bootstrap-sass’, ‘~> 3.3.6’ #=> bootstrap also requires the ‘sass-rails’ gem, which should already be included in your gemfile
Хотя Bower не является обязательным для этого проекта, я решил использовать его по одной простой причине; опыт. Рано или поздно я, вероятно, обнаружу, что работаю над приложением, созданным с помощью Bower, так почему бы не начать играть с ним прямо сейчас?
Что такое Бауэр? Вы можете узнать больше на их веб-сайте, bower.io, но, насколько я могу судить, по сути это менеджер пакетов, такой же, как ruby gems или npm. Вы можете установить его с помощью npm, однако я решил включить гем boer-rails в это руководство.
Теперь мы установим/инициализируем эти гемы, создадим нашу базу данных, добавим миграцию, чтобы пользователи могли регистрироваться с помощью имени пользователя, а затем применим эти миграции к нашей схеме с помощью следующих команд:
$ bundle install $ rake db:create #=> create database $ rails g bower_rails:initialize json #=> generates bower.json file for adding “dependencies” $ rails g devise:install #=> generates config/initializers/devise.rb, user resources, user model, and user migration with a TON of default configurations for authentication $ rails g migration AddUsernametoUsers username:string:uniq #=> generates, well, exactly what it says. $ rake db:migrate
К тому времени, когда вы наберете обороты для создания своего приложения, у вас, вероятно, будет гораздо больше зависимостей или «пакетов», но вот что вам нужно для начала. Добавьте следующие зависимости поставщиков в bower.json:
… “vendor”: { “name”: “bower-rails generated vendor assets”, “dependencies”: { “angular”: “v1.5.8”, “angular-ui-router”: “latest”, “angular-devise”: “latest” } }
После того, как вы сохранили эти изменения в bower.json, вы захотите установить эти пакеты с помощью следующей команды, а затем сгенерировать свой пользовательский сериализатор из установленного ранее гема «active-model-serializer»:
$ rake bower:install $ rails g serializer user
Найдите app/serializers/user_serializer.rb и добавьте , :username непосредственно после attributes :id, чтобы при запросе Devise информации о пользователе с рельсов вы можете отобразить выбранное ими имя пользователя. Это намного приятнее, чем сказать Добро пожаловать, «[email protected]» или, что еще хуже, Добро пожаловать, 5UPer$3CREtP4SSword. Шучу, а если серьезно, не делайте этого.
Добавьте следующее в config/application.rb непосредственно в раздел class Application ‹ Rails::Application:
config.to_prepare do DeviseController.respond_to :html, :json end
Поскольку Angular будет запрашивать информацию о пользователе с помощью .json, нам нужно убедиться, что DeviseController ответит соответствующим образом, чего он не делает по умолчанию.
Мы ООООООООООчень близки к завершению нашего бэкенда. Еще немного корректировок…
Откройте config/routes.rb и добавьте следующую строку в раздел devise_for :users:
root ‘application#index’
Затем замените содержимое app/controllers/application_controller.rb всем этим фрагментом:
class ApplicationController < ActionController::Base protect_from_forgery with: :exception before_action :configure_permitted_parameters, if: :devise_controller? skip_before_action :verify_authenticity_token respond_to :json def index render ‘application/index’ end protected def configure_permitted_parameters added_attrs = [:username, :email, :password, :password_confirmation, :remember_me] devise_parameter_sanitizer.permit :sign_up, keys: added_attrs devise_parameter_sanitizer.permit :account_update, keys: added_attrs end end
Мы сделали здесь несколько вещей. Во-первых, мы сообщаем Rails, что :json — наш друг, наше ЕДИНСТВЕННОЕ представление находится в views/application/index.html.erb, не беспокойтесь о маркерах подлинности. когда вам позвонит Devise, о, и у нашего пользователя будет имя пользователя.
Затем откройте app/controllers/users_controller.rb и убедитесь, что вы можете получить доступ к пользователю в формате JSON с любым запросом /users/:id.json:
class UsersController < ApplicationController def show user = User.find(params[:id]) render json: user end end
Не беспокойтесь о настройке ресурса :show в routes.rb, Devise уже сделал это за нас!
По умолчанию Rails инициализируется с помощью views/layouts/application.html.erb, но нам это не нужно (точнее, мне это не нужно), поэтому сделайте следующее:
- ПЕРЕМЕСТИТЕ этот файл в app/views/application/
- Переименуйте его в index.html.erb
- Замените ‹% = yield %› с ‹ui-view›‹/ui-view› (мы не будем отображать никаких erb, кроме тегов script/style в нашем заголовке)
– Удалите любые упоминания о «туроблинках» в тегах erb скрипта и таблицы стилей
– добавьте ng-app="myApp" в качестве атрибута к ‹body› тег. Когда мы запускаем наш сервер, Angular загружает и лихорадочно ищет это в нашей DOM, прежде чем инициализировать наше приложение.
Последний шаг к настройке нашего бэкенда — это создание конвейера ресурсов. Bower уже установил для нас кучу всего в vendor/assets/bower_components, и мы также ранее установили кучу приятных драгоценных камней. Давайте удостоверимся, что наше приложение может найти эти скрипты и таблицы стилей:
Требуйте следующее в app/assets/javascript/application.js:
//= require jquery //= require jquery_ujs //= require angular //= require angular-ui-router //= require angular-devise //= require angular-rails-templates //= require bootstrap-sprockets //= require_tree .
** не забудьте удалить require turbolinks **
Наконец, мы должны переименовать app/assets/stylesheets/application.css в application.scss и добавить эти две строки @import в конец нашей таблицы стилей:
* *= require_tree . *= require_self */ @import “bootstrap-sprockets”; @import “bootstrap”;
Бум!! Теперь у нас все настроено, и мы можем начать работать над нашим интерфейсом.
Вот предварительный просмотр того, как будет выглядеть наше дерево приложений angular. Поскольку мы установили гем «angular-templates», мы можем хранить все наши html-файлы в каталоге assets/javascript со всеми другими нашими угловыми файлами.
/javascript/controllers/AuthCtrl.js /javascript/controllers/HomeCtrl.js /javascript/controllers/NavCtrl.js /javascript/directives/NavDirective.js /javascript/views/home.html /javascript/views/login.html /javascript/views/register.html /javascript/views/nav.html /javascript/app.js /javascript/routes.js
Прежде всего, давайте объявим наше приложение в app.js и внедрим необходимые зависимости:
(function(){ angular .module(‘myApp’, [‘ui.router’, ‘Devise’, ‘templates’]) }())
Обертывание ваших компонентов AngularJS в выражение немедленно вызываемой функции (IIFE). Это помогает предотвратить более длительное существование переменных и объявлений функций в глобальной области видимости, чем ожидалось, что также помогает избежать конфликтов переменных. Это становится еще более важным, когда ваш код минимизируется и объединяется в один файл для развертывания на рабочем сервере, предоставляя переменную область действия для каждого файла. — из http://www.codestyle.co/Guidelines/angularjs
Далее мы собираемся заглушить наш файл routes.js… Некоторые из них на шаг впереди того, что мы имеем сейчас, но я бы предпочел избавиться от этого сейчас, чем возвращаться:
angular .module(‘myApp’) .config(function($stateProvider, $urlRouterProvider){ $stateProvider .state(‘home’, { url: ‘/home’, templateUrl: ‘views/home.html’, controller: ‘HomeCtrl’ }) .state(‘login’, { url: ‘/login’, templateUrl: ‘views/login.html’, controller: ‘AuthCtrl’, onEnter: function(Auth, $state){ Auth.currentUser().then(function(){ $state.go(‘home’) }) } }) .state(‘register’, { url: ‘/register’, templateUrl: ‘views/register.html’, controller: ‘AuthCtrl’, onEnter: function(Auth, $state){ Auth.currentUser().then(function(){ $state.go(‘home’) }) } }) $urlRouterProvider.otherwise(‘/home’) })
То, что мы только что сделали, называется нашим угловым приложением myApp и вызывает функцию конфигурации, передавая $stateProvider и $routerUrlProvider в качестве параметров. Сразу же мы можем вызвать $stateProvider и начать цепочку методов .state(), которые принимают два параметра, имя состояния (например, «home») и объект данных, описывающий состояние. например, его URL-адрес, HTML-шаблон и используемый контроллер. Мы также используем $urlRouterProvider только для того, чтобы убедиться, что пользователь не может перейти куда-либо, кроме наших предопределенных состояний.
Некоторые вещи, с которыми вы, возможно, еще не были знакомы, — это onEnter, $state и Auth. Мы вернемся к этому позже.
Теперь давайте создадим наши home.html и HomeCtrl.js:
<div class=”col-lg-8 col-lg-offset-2"> <h1>{{hello}}</h1> <h3 ng-if=”user”>Welcome, {{user.username}}</h3> </div> angular .module(‘myApp’) .controller(‘HomeCtrl’, function($scope, $rootScope, Auth){ $scope.hello = “Hello World” })
Вы можете прокомментировать состояния входа/регистрации и запустить $ rails s, чтобы убедиться, что все работает. Если это так, вы увидите большой красивый «Hello World». Если он находится прямо вверху посередине, сделайте глубокий вдох с облегчением, потому что Bootstrap срабатывает, и этот элемент col-lg правильно позиционирует его, а не застревает в верхнем левом углу.
Что сделал Angular, так это просмотрел DOM, нашел атрибут ng-app, инициализировал «myApp», перешел в /home по умолчанию с нашего маршрутизатора, нашел ‹ui-view›, создала экземпляр нашего HomeCtrl, внедрила объект $scope, добавила ключ hello, назначила его значение "Hello World", а затем отображал `home.html` с этой информацией в элементе ‹ui-view›. Оказавшись в представлении, Angular сканирует любые значимые команды, такие как привязки {{…}} и директиву ng-if, и отображает информацию контроллера по мере необходимости. Я допускаю, что порядок этих операций может немного отличаться, но вы понимаете, что происходит под капотом.
Поскольку у нас есть вся эта мельчайшая закулисная информация, давайте создадим наши AuthCtrl.js и login.html/регистр. HTML файлы:
# login.js <div class=”col-lg-8 col-lg-offset-2"> <h1 class=”centered-text”>Log In</h1> <form ng-submit=”login()”> <div class=”form-group”> <input type=”email” class=”form-control” placeholder=”Email” ng-model=”user.email” autofocus> </div> <div class=”form-group”> <input type=”password” class=”form-control” placeholder=”Password” ng-model=”user.password”> </div> <input type=”submit” class=”btn btn-info” value=”Log In”> </form> </div> # register.js <div class=”col-lg-8 col-lg-offset-2"> <h1 class=”centered-text”>Register</h1> <form ng-submit=”register()”> <div class=”form-group”> <input type=”email” class=”form-control” placeholder=”Email” ng-model=”user.email” autofocus> </div> <div class=”form-group”> <input type=”username” class=”form-control” placeholder=”Username” ng-model=”user.username” autofocus> </div> <div class=”form-group”> <input type=”password” class=”form-control” placeholder=”Password” ng-model=”user.password”> </div> <input type=”submit” class=”btn btn-info” value=”Log In”> </form> <br> <div class=”panel-footer”> Already signed up? <a ui-sref=”home.login”>Log in here</a>. </div> </div>
Прежде чем я перегружу вас AuthCtrl, я просто хочу указать, что большая часть того, что вы видите, — это классы Bootstraped CSS, так что вы все очень впечатлены тем, как красиво это отображается. Не обращайте внимания на все атрибуты класса, все остальное должно быть вам знакомо, например ng-submit, ng-model и ui-sref. , который заменяет наш обычный атрибут тега href. Теперь о AuthCtrl… вы готовы?
angular .module(‘myApp’) .controller(‘AuthCtrl’, function($scope, $rootScope, Auth, $state){ var config = {headers: {‘X-HTTP-Method-Override’: ‘POST’}} $scope.register = function(){ Auth.register($scope.user, config).then(function(user){ $rootScope.user = user alert(“Thanks for signing up, “ + user.username); $state.go(‘home’); }, function(response){ alert(response.data.error) }); }; $scope.login = function(){ Auth.login($scope.user, config).then(function(user){ $rootScope.user = user alert(“You’re all signed in, “ + user.username); $state.go(‘home’); }, function(response){ alert(response.data.error) }); } })
Большая часть этого кода взята из документации Angular Devise (https://github.com/cloudspace/angular_devise), поэтому я не буду вдаваться в подробности. Что вам нужно знать сейчас, так это то, что Auth — это служба, созданная angular-device, и она поставляется с некоторыми замечательными функциями, такими как Auth.login(userParameters , config) и Auth.register(userParameters, config). Они создают обещание, которое возвращает вошедшего в систему пользователя после разрешения.
Я признаю, что здесь я немного схитрил и назначил этого пользователя в $rootScope, однако более эффективным и масштабируемым подходом было бы создание UserService, сохранение пользователя там, а затем внедрение UserService в любой из ваших контроллеров, которым нужен пользователь. Для краткости я также использовал простую функцию alert() вместо интеграции ngMessages или другой службы, такой как ngFlash, чтобы делать объявления о ошибки или успешные события входа в систему.
Остальное должно быть самоочевидным, формы ng-submit прикреплены к этим функциям $scope, $scope.user извлекает информацию из ng-model во входных данных формы, а $state.go() — отличная функция для перенаправления в другое состояние.
Если вы сейчас вернетесь к routes.js, вся эта логика onEnter станет более понятной.
И, конечно же, самое лучшее я оставил напоследок, так что давайте создадим небольшие NavDirective.js и nav.html, чтобы собрать все вместе:
angular .module(‘myApp’) .directive(‘navBar’, function NavBar(){ return { templateUrl: ‘views/nav.html’, controller: ‘NavCtrl’ } }) <div class=”col-lg-8 col-lg-offset-2"> <ul class=”nav navbar-nav” > <li><a ui-sref=”home”>Home</a></li> <li ng-hide=”signedIn()”><a ui-sref=”login”>Login</a></li> <li ng-hide=”signedIn()”><a ui-sref=”register”>Register</a></li> <li ng-show=”signedIn()”><a ng-click=”logout()”>Log Out</a></li> </ul> </div>
И более надежный NavCtrl.js:
angular .module(‘myApp’) .controller(‘NavCtrl’, function($scope, Auth, $rootScope){ $scope.signedIn = Auth.isAuthenticated; $scope.logout = Auth.logout; Auth.currentUser().then(function (user){ $rootScope.user = user }); $scope.$on(‘devise:new-registration’, function (e, user){ $rootScope.user = user }); $scope.$on(‘devise:login’, function (e, user){ $rootScope.user = user }); $scope.$on(‘devise:logout’, function (e, user){ alert(“You have been logged out.”) $rootScope.user = undefined }); })
Все, что мы здесь делаем, — это настраиваем функции для использования в навигационных ссылках, такие как ng-hide="signedIn()" и ng-click="logout()» и добавление слушателей в $scope, чтобы мы могли запускать действия, когда происходят определенные события devise. Мы также вызываем Auth.currentuser(), чтобы при создании экземпляра этого контроллера мы могли дважды проверить наш объект $rootScope.user и отобразить правильные навигационные ссылки.
Снова найдем app/views/application/index.html и добавим ‹nav-bar›‹/nav-bar› в строку выше ‹ui-view ›. Поскольку это не привязано ни к одному из маршрутов, оно всегда будет отображаться над нашим основным контентом.
Идите вперед и обновите свою страницу сейчас. Разве ты не любишь, когда все просто работает? Надеюсь, у вас нет странных проблем с устаревшим пакетом, версией Ruby или чем-то в этом роде. Просто помните, Google — ваш лучший друг.
В любом случае, я надеюсь, что это помогло! Пожалуйста, оставляйте комментарии с вопросами, комментариями или предложениями :)