Когда я начал программировать свое самое первое одностраничное приложение 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 — ваш лучший друг.

В любом случае, я надеюсь, что это помогло! Пожалуйста, оставляйте комментарии с вопросами, комментариями или предложениями :)