Nastavení Angular SPA na Rails pomocí Devise a Bootstrap

Tento článek byl původně publikován na jessenovotny.com.

Když jsem začal programovat svou úplně první Angular single page application (SPA), všiml jsem si, že zdroje pro nastavení a integraci s Devise jsou tenké nebo roztříštěné. Nejužitečnější průvodce, který jsem našel, byl ve skutečnosti jen část obecného návodu Angular with Rails. Existovaly další zdroje, které byly buď příliš složité, nebo pokročilé, a ve skutečnosti neprošly počátečními dětskými kroky. Jednou z nejnáročnějších výzev pro nového programátora je začít od nuly. Vím to, protože jsem jedním z těchto lidí.

Většina toho, co jsem se naučil prostřednictvím svého online kurzu, byla dodána v malých, stále pokročilejších komponentách. Otevřu laboratoř a základy jsou již připraveny, takže není spousta praxe při nastavování aplikace z prázdného listu. Kvůli času dokončení kurzu to dává smysl. Kromě toho stačí vytvořit několik aplikací od základů, abyste získali pocit, jak se to dělá. Pokud jste se tam ještě nedostali, tento návod bude přímo ve vaší uličce.

Jakmile jsem konečně zprovoznil všechny části a můj první projekt Angular byl spuštěn a rozběhl se, cítil jsem, že je vhodné to komunitě vrátit. Vzhledem k tomu, že v současné době nemám dostatek „bodů reputace“ k zodpovězení otázek o Stack Overflow, další nejlepší věcí by bylo vytvořit vlastní návod k nastavení Angular SPA na Rails pomocí Devise a Bootstrap. Následující text je přesně co bych si přál, abych našel ve svém počátečním výzkumu na toto téma.

Je pravda, že velká část vývoje webu je schopna řešit složité problémy, aniž by jim bylo předáno řešení. Mám pocit, že někdy nový vývojář prostě potřebuje pomocnou ruku. Tak tady to je.

Začínáme

Tento průvodce má sloužit jako skokanský můstek pro začátek. Předpokládá se, že již máte základní znalosti o Angular, Rails, Devise a Bootstrap. Rozhodl jsem se neprozkoumat Active Record, ale dotknu se Active Model Serializer, protože je nezbytný pro odesílání modelů do vašeho rozhraní JavaScript. O tomto tématu se lze dozvědět mnohem více a to by si zasloužilo vlastní řadu průvodců. Stejně tak jdu do instalace Bootstrapu pouze do bodu, kdy mohu ověřit, že funguje.

Neváhejte a přečtěte si video, které jsem vytvořil pro tento tutoriál:

Nastavení

Chcete-li začít, otevřete Terminál a přejděte do složky, kde chcete vytvořit novou aplikaci. V této ukázce jsem na ploše.

V Terminálu spustíte $ rails new YOUR-APP který inicializuje Rails, vytvoří adresář s celým frameworkem a spojí všechny pečené v drahokamy. (V případě, že neznáte, $ označuje příkaz Terminál.)

Otevřete Gemfile , odstraňte gem 'turbolinks' a přidejte následující:


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

I když Bower není pro tento projekt nezbytný, rozhodl jsem se jej použít z jednoho prostého důvodu:zkušenosti. Dříve nebo později pravděpodobně zjistím, že pracuji na aplikaci, která byla vytvořena pomocí Bowera, tak proč si s ní nezačít hrát hned teď?

Co je Bower? Více se můžete dozvědět na jejich webu bower.io, ale pokud mohu říci, je to v podstatě správce balíčků, stejně jako Ruby gems nebo npm. Můžete jej nainstalovat pomocí npm, ale rozhodl jsem se zahrnout bower-rails klenot pro tohoto průvodce.

Inicializace drahokamů, vytvoření databáze a přidání migrace

Nyní nainstalujeme/inicializujeme tyto skvosty, vytvoříme naši databázi, přidáme migraci, aby se uživatelé mohli zaregistrovat pomocí uživatelského jména, a poté tyto migrace aplikujeme na naše schéma pomocí následujících příkazů:


$ 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

Ve chvíli, kdy získáte hybnou sílu při vytváření své aplikace, budete mít pravděpodobně mnohem více závislostí nebo „balíčků“, ale zde je to, co budete potřebovat, abyste mohli začít. Přidejte následující závislosti dodavatelů do bower.json :


...
"vendor": {
  "name": "bower-rails generated vendor assets",
  "dependencies": {
    "angular": "v1.5.8",
    "angular-ui-router": "latest",
    "angular-devise": "latest"
  }
}

Jakmile uložíte tyto změny do bower.json, budete chtít tyto balíčky nainstalovat pomocí následujícího příkazu a poté vygenerovat uživatelský serializátor z dříve nainstalovaného drahokamu ‘active-model-serializer’:


$ rake bower:install
$ rails g serializer user

Vyhledejte app/serializers/user_serializer.rb a přidejte , :username přímo za attributes :id takže když Devise požádá o informace uživatele od Rails, můžete zobrazit jeho zvolené uživatelské jméno. To je mnohem hezčí než říkat „Vítejte, [email protected]“ nebo ještě hůř, „Vítejte, 5UPer$3CREtP4SSword“. Dělám si legraci, ale vážně, nedělej to.

Přidejte následující do config/application.rb přímo pod class Application < Rails::Application :


config.to_prepare do
  DeviseController.respond_to :html, :json
end

Protože Angular bude požadovat informace o uživateli pomocí .json , musíme se ujistit, že DeviseController bude reagovat správně, což ve výchozím nastavení nedělá.

Dokončení Back-end Setup

Začínáme tak moc blízko dokončení našeho back-endu. Ještě pár úprav…

Otevřete config/routes.rb a přidejte následující řádek pod devise_for :users :root 'application#index' . Poté nahraďte obsah app/controllers/application_controller.rb s celým tímto úryvkem:


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

Udělali jsme zde několik věcí. Nejprve říkáme Rails, že :json je náš přítel; naše jediné zobrazit životy v views/application/index.html.erb; nebojte se o tokeny pravosti, když vám zavolá Devise; jo, a náš uživatel bude mít uživatelské jméno.

Dále otevřete app/controllers/users_controller.rb a ujistěte se, že máte přístup k uživateli ve formátu JSON pomocí libovolného /users/:id.json požadavek:


class UsersController < ApplicationController
  def show
    user = User.find(params[:id])
    render json: user
  end  
end

Nedělejte si starosti s nastavením :show zdroj v routes.rb . Devise to už za nás udělal!

Ve výchozím nastavení se Rails inicializuje s views/layouts/application.html.erb , ale to nechceme (nebo spíše nechci toto), takže udělejte následující:

  • Přesunout tento soubor na app/views/application/ .
  • Přejmenujte jej na index.html.erb .
  • Nahraďte <%= yield %> s <ui-view></ui-view> (nebudeme vykreslovat žádné erb kromě značek script/style v naší hlavičce).
  • Odstraňte veškeré zmínky o „turoblinks“ ve značkách erb skriptu a šablony stylů.
  • Přidejte ng-app="myApp" jako atribut k <body> štítek. Když spustíme náš server, Angular se načte a zuřivě vyhledá náš DOM, než inicializuje naši aplikaci.

Posledním krokem k nakonfigurování našeho back-endu je uspořádání našeho portfolia aktiv. Bower pro nás již nainstaloval spoustu věcí v vendor/assets/bower_components . Stejně tak jsme dříve nainstalovali spoustu sladkých drahokamů. Ujistíme se, že naše aplikace dokáže najít tyto skripty a šablony stylů:

V app/assets/javascripts/application.js vyžadovat následující :


//= require jquery
//= require jquery_ujs
//= require angular
//= require angular-ui-router
//= require angular-devise
//= require angular-rails-templates
//= require bootstrap-sprockets
//= require_tree .

Poznámka:Nezapomeňte odstranit require turbolinks

Nakonec musíme přejmenovat app/assets/stylesheets/application.css na application.scss a přidejte tyto dva @import řádky na konci naší šablony stylů:


*
 *= require_tree .
 *= require_self
 */
@import "bootstrap-sprockets";
@import "bootstrap";

Výložník!! Nyní máme vše nastaveno a můžeme začít pracovat na našem frontendu.

Rozhraní frontend

Zde je náhled toho, jak bude náš strom aplikací Angular vypadat. Protože jsme nainstalovali drahokam „angular-templates“, můžeme všechny naše soubory HTML uchovávat v assets/javascripts adresář se všemi našimi dalšími soubory 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

Nejprve:pojďme deklarovat naši aplikaci v app.js a vložit potřebné závislosti:


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

Zde používám IIFE z důvodů vysvětlených v této citaci:

Zabalení vašich komponent AngularJS do výrazu okamžitě vyvolané funkce (IIFE). To pomáhá zabránit tomu, aby proměnné a deklarace funkcí žily déle, než se očekává v globálním rozsahu, což také pomáhá předcházet kolizím proměnných. To se stává ještě důležitějším, když je váš kód minifikován a spojen do jednoho souboru pro nasazení na produkční server tím, že každému souboru poskytnete variabilní rozsah. — Průvodce Codestyle.co AngularJS

Routes.js

Dále se chystáme vyřadit naše routes.js soubor. Něco z toho je o krok napřed oproti tomu, kde jsme teď, ale raději bych to teď odstranil z cesty, než se vracet:


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')
  })

To, co jsme právě udělali, se nazývá naše úhlová aplikace „myApp“ a nazývá se funkce config, která předává $stateProvider a $routerUrlProvider jako parametry. Okamžitě můžeme volat $stateProvider a začněte řetězit .state() metody, které přebírají dva parametry, název stavu (například ‚domov‘) a objekt dat, který stav popisuje, jako je jeho adresa URL, šablona HTML a použitý ovladač. Používáme také $urlRouterProvider jen abychom se ujistili, že uživatel nemůže přejít jinam než do našich předem určených stavů.

Několik věcí, se kterými jste možná dosud nebyli obeznámeni, je onEnter , $state a Auth . K tomu se dostaneme později.

Nyní sestavme náš home.html a 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"
  })

Možná budete chtít okomentovat stavy přihlášení/registrace a spustit $ rails s abyste se ujistili, že vše funguje. Pokud ano, uvidíte velké krásné „Ahoj světe“. Pokud je to přímo nahoře ke středu, zhluboka se nadechněte, protože Bootstrap nastupuje a col-lg věci ho umísťují pěkně, než aby uvízly v levém horním rohu.

Angular provedl prohledání DOM, našel atribut ng-app , inicializoval „myApp“, přešel na /home ve výchozím nastavení z našeho routeru s umístěním <ui-view> direktiva, vytvořila instanci našeho HomeCtrl , vložil $scope objekt, přidal klíč hello , přiřadil mu hodnotu "Hello World" a poté vykreslen home.html s těmito informacemi v <ui-view> živel. Jakmile je Angular v zobrazení, hledá jakékoli smysluplné příkazy, jako je {{...}} vazby a ng-if směrnici a poskytuje informace správce podle potřeby. Připouštím, že pořadí těchto operací může být mírně posunuté, ale pochopíte podstatu toho, co se děje pod kapotou.

Sestavení souborů AuthCtrl.js a login.html/register.html

Vzhledem k tomu, že jsme všechny tyhle hloupé informace ze zákulisí odstranili, pojďme sestavit naše AuthCtrl.js a login.html /register.html soubory:


# 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>

Než vás zavalím AuthCtrl , Chci jen zdůraznit, že většina toho, co vidíte, jsou Bootstraped CSS třídy, takže na vás všechny udělalo velký dojem, jak krásně se to vykresluje. Ignorujte všechny atributy třídy a vše ostatní by mělo být dobře známé, například ng-submit , ng-model a ui-sref , který zaujímá místo našeho obvyklého href atribut tag kotvy. Nyní k AuthCtrl… jste připraveni?


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)
      });
    }
  })

Většina tohoto kódu je odvozena z dokumentace Angular Devise, takže nebudu zacházet do přílišných podrobností. Nyní potřebujete vědět, že Auth je služba vytvořená angular-device a přichází s některými docela úžasnými funkcemi, jako je Auth.login(userParameters, config) a Auth.register(userParameters, config) . Ty vytvářejí příslib, který po vyřešení vrátí přihlášeného uživatele.

Přiznám se, že jsem zde trochu podváděl a přiřadil tohoto uživatele k $rootScope . Výkonnější a škálovatelnější přístup by však bylo vytvořit UserService, uložit tam uživatele a poté vložit UserService do libovolného z vašich řadičů, které uživatele potřebují. Pro stručnost jsem také použil jednoduchý alert() funkce namísto integrace ngMessages nebo jinou službu jako ngFlash pro zasílání oznámení o chybách nebo úspěšných událostech přihlášení.

Zbytek by měl být docela samozřejmý. ng-submit formuláře jsou připojeny k těmto $scope funkce, $scope.user přebírá informace z ng-model s na formulářových vstupech a $state.go() je šikovná funkce pro přesměrování do jiného stavu.

Pokud se vrátíte na routes.js nyní všech těch onEnter logika by měla dávat mnohem větší smysl.

Spojení všeho dohromady

To nejlepší jsem si nechal na konec, takže pojďme postavit parádní malý NavDirective.js a nav.html dát vše dohromady:


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>

A robustnější 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
    });
  })

Vše, co zde děláme, je nastavování funkcí pro použití v navigačních odkazech, jako je ng-hide="signedIn()" a ng-click="logout()" a přidání posluchačů do $scope abychom mohli spustit akce při určité devise dochází ke konkrétním událostem. Také voláme Auth.currentuser() takže po vytvoření instance tohoto ovladače můžeme znovu zkontrolovat naše $rootScope.user objekt a zobrazit správné navigační odkazy.

Pojďme najít app/views/application/index.html znovu a přidejte <nav-bar></nav-bar> na řádku nad <ui-view> . Protože to není vázáno na žádnou z tras, vždy se zobrazí nad naším hlavním obsahem.

Pokračujte a obnovte svou stránku. Nemáte rádi, když věci fungují? Doufejme, že nemáte žádné podivné problémy se zastaralým balíčkem, verzí Ruby nebo něčím podobným. Pamatujte, že Google je váš nejlepší přítel.

Anyhoo, doufám, že to pomohlo! Zanechte prosím jakékoli dotazy, komentáře nebo návrhy níže!