Статьи

Настройка Angular SPA на Rails с помощью Devise и Bootstrap

Настройка Angular SPA на Rails с помощью Devise и Bootstrap

Эта статья была первоначально опубликована на jessenovotny.com .

Когда я начал программировать свое самое первое одностраничное приложение Angular (SPA), я заметил, что ресурсы для настройки и интеграции с Devise были тонкими или фрагментированными. Самое полезное руководство, которое я нашел, было на самом деле просто частью общего руководства по Angular с Rails. Были и другие ресурсы, которые были либо слишком сложными, либо продвинутыми, и они на самом деле не проходили первые шаги ребенка. Одна из самых сложных задач для нового программиста начинается с нуля. Я знаю, потому что я один из этих людей.

Большая часть того, что я узнал из моего онлайн-курса, была представлена ​​в виде небольших, все более совершенных компонентов. Я открываю лабораторию, и основы уже заложены, так что нет никакой практики в настройке приложения с чистого листа. Ради времени завершения курса это имеет смысл. Кроме того, вам нужно всего лишь создать пару приложений с нуля, чтобы понять, как это делается. Если вы еще не добрались туда, это прохождение будет прямо по вашему переулку.

После того, как я наконец заработал все части и мой первый Angular проект был запущен и запущен, я почувствовал, что уместно отдать его сообществу. Поскольку в настоящее время у меня не хватает «очков репутации», чтобы отвечать на вопросы о переполнении стека, следующей лучшей вещью было бы сделать мое собственное пошаговое руководство по настройке Angular SPA на Rails с Devise и Bootstrap. Вот то, что я хотел бы найти в своем первоначальном исследовании по этой теме.

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

Начиная

Это руководство предназначено для дайвинга для начала. Предполагается, что у вас уже есть базовое понимание Angular, Rails, Devise и Bootstrap. Я решил не изучать Active Record, но я касаюсь Active Model Serializer, так как это необходимо для отправки моделей в ваш интерфейс JavaScript. Существует гораздо больше, чтобы узнать об этом предмете, и это потребует отдельной серии руководств. Аналогично, я только начинаю установку Bootstrap до такой степени, что могу убедиться, что она работает.

Не стесняйтесь читать вместе с видео, которое я создал для этого урока:

Настройка

Для начала вы хотите открыть терминал и перейти к папке, в которой вы хотите создать новое приложение. В этой демонстрации я на рабочем столе.

В терминале вы запустите $ rails new YOUR-APP которое инициализирует Rails, создает каталог со всей структурой и объединяет все выпеченные в гемах. (Если вы незнакомы, $ обозначает команду терминала.)

Откройте ваш Gemfile , удалите gem 'turbolinks' и добавьте следующее:

 gem 'bower-rails' gem 'devise' gem 'angular-rails-templates' #=> allows us to place our html views in the assets/javascripts directory gem 'active_model_serializers' gem 'bootstrap-sass', '~> 3.3.6' #=> bootstrap also requires the 'sass-rails' gem, which should already be included in your gemfile 

Хотя Бауэр не является необходимым для этого проекта, я решил использовать его по одной простой причине: опыт. Рано или поздно я, вероятно, обнаружу, что работаю над приложением, созданным с помощью Bower, так почему бы не начать играть с ним сейчас?

Что такое Бауэр? Вы можете узнать больше на их сайте, bower.io, но, насколько я могу судить, это по сути менеджер пакетов, такой как Ruby gems или npm. Вы можете установить его с помощью npm, но я решил включить гем bower-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 запрашивал информацию о пользователе у Rails, вы могли отобразить выбранное имя пользователя. Это гораздо лучше, чем сказать «Добро пожаловать, [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> (мы не будем отображать никаких ошибок, кроме тегов script / style в нашем заголовке).
  • Удалите все упоминания о «turoblinks» в тегах скрипта и erb таблицы стилей.
  • Добавьте ng-app="myApp" в качестве атрибута <body> . Когда мы запускаем наш сервер, Angular будет загружать и отчаянно искать в нашей DOM, прежде чем инициализировать наше приложение.

Последний шаг к настройке нашего бэк-энда — это разработка нашего конвейера активов. Бауэр уже установил для нас кучу вещей в vendor/assets/bower_components . Аналогично, мы установили кучу сладких драгоценных камней ранее. Давайте удостоверимся, что наше приложение может найти эти сценарии и таблицы стилей:

Требуется следующее в app/assets/javascripts/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"; 

Boom !! Теперь у нас все настроено, и мы можем начать работать над нашим интерфейсом.

Передний конец

Вот предварительный просмотр того, как будет выглядеть наше дерево приложений Angular. Поскольку мы установили гем ‘angular-templates’, мы можем хранить все наши HTML-файлы в каталоге assets/javascripts вместе со всеми другими нашими Angular-файлами:

 /javascripts/controllers/AuthCtrl.js /javascripts/controllers/HomeCtrl.js /javascripts/controllers/NavCtrl.js /javascripts/directives/NavDirective.js /javascripts/views/home.html /javascripts/views/login.html /javascripts/views/register.html /javascripts/views/nav.html /javascripts/app.js /javascripts/routes.js 

Перво- app.js : давайте объявим наше приложение в app.js и app.js необходимые зависимости:

 (function(){ angular .module('myApp', ['ui.router', 'Devise', 'templates']) }()) 

Я использую IIFE здесь, по причинам, объясненным в этой цитате:

Обертывание ваших компонентов AngularJS в выражении немедленного вызова функции (IIFE). Это помогает не дать переменным и объявлениям функций жить дольше, чем ожидалось, в глобальной области, что также помогает избежать конфликтов переменных. Это становится еще более важным, когда ваш код минимизируется и объединяется в один файл для развертывания на рабочем сервере, предоставляя переменную область действия для каждого файла. Codestyle.co AngularJS Руководство

Routes.js

Далее мы собираемся routes.js наш файл 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» и называется функцией config, передавая в качестве параметров $stateProvider и $routerUrlProvider . Сразу же мы можем вызвать $stateProvider и начать цепочку .state() , которые принимают два параметра: имя состояния (например, «home») и объект данных, который описывает состояние, например его URL, шаблон HTML и какой контроллер использовать. Мы также используем $urlRouterProvider просто для того, чтобы убедиться, что пользователь не может перемещаться никуда, кроме как в наши заранее определенные состояния.

Несколько вещей, с которыми вы, возможно, еще не знакомы, это onEnter , $state и Auth . Мы вернемся к этому позже.

Теперь давайте home.html наши 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 с нашего маршрутизатора, HomeCtrl директиву <ui-view> , HomeCtrl наш HomeCtrl , HomeCtrl объект $scope , добавил ключ hello , присвоил ему значение "Hello World" , а затем home.html с этой информацией в элементе <ui-view> . Оказавшись в представлении, Angular сканирует любые значимые команды, такие как привязки {{...}} и директива ng-if и при необходимости отображает информацию контроллера. Я признаю, что порядок этих операций может быть немного неточным, но вы понимаете, что происходит под капотом.

Создание файлов AuthCtrl.js и login.html / register.html

Поскольку у нас есть вся эта AuthCtrl.js login.html информация, давайте AuthCtrl.js наши AuthCtrl.js и login.html / register.html :

 # login.html <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.html <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 , я просто хочу отметить, что большая часть того, что вы видите, это классы начальной загрузки 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 , поэтому я не буду вдаваться в подробности. Теперь вам нужно знать, что Auth — это сервис, созданный angular-device , и он поставляется с некоторыми довольно удивительными функциями, такими как Auth.login(userParameters, config) и Auth.register(userParameters, config) . Они создают обещание, которое возвращает зарегистрированного пользователя после разрешения.

Я признаю, что немного обманул и назначил этого пользователя в $rootScope . Однако более эффективный и более масштабируемый подход заключается в создании UserService, сохранении там пользователя и последующем внедрении UserService в любой из ваших контроллеров, которым нужен пользователь. Для краткости я также использовал простую функцию alert() вместо интеграции ngMessages или другого сервиса, такого как ngFlash для объявления об ошибках или успешных событиях входа в систему.

Остальное должно быть довольно понятным. Формы ng-submit присоединяются к этим функциям $scope , $scope.user информацию из ng-model s на входах формы, а $state.go() — изящная функция для перенаправления в другое состояние.

Если вы сейчас вернетесь к routes.js , вся onEnter логика onEnter должна иметь намного больше смысла.

Объединяя все вместе

Я сохранил лучшее для последнего, поэтому давайте NavDirective.js немного причудливых 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() чтобы при Auth.currentuser() экземпляра этого контроллера мы могли дважды проверить наш объект $rootScope.user и отобразить правильные навигационные ссылки.

Давайте снова найдем app/views/application/index.html и добавим <nav-bar></nav-bar> в строке выше <ui-view> . Поскольку это не связано ни с одним из маршрутов, оно всегда будет отображаться выше нашего основного контента.

Иди и обнови свою страницу сейчас. Разве тебе не нравится, когда все работает? Надеюсь, у вас нет странных проблем с устаревшим пакетом, версией Ruby или чем-то в этом роде. Просто помните, Google — ваш лучший друг.

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