在我开始为自己的首个 Angular 单页面应用程序 (SPA) 编写代码时,我意识到用于设置和集成 Devise 的资源都比较零碎。我找到的堪称最实用的指南,也只是照常将 Angular 同 Rails 的运用过一遍了,其 内容只有一小节。其它的一些资源不是太复杂就是太高深,而且步骤描述也不够详尽。对于一个新手而言,入门成了一大问题。
我在线了解的课程多是让我们积累高级组件,课程实验也已经准备好了基础框架,我们没法自己从零开始演练。而在我看来,学 习的关键就是要自己动手从领开始构建几个应用。
所有工作都完成后,我的第一个 Angular 项目就正式启动并运行了。我为在 Rails 上构建 Angular 单页应用并集成 Devise 和 Bootstrap 制作了一份指导手册。以下内容是我在相关主题上做的初步研究。
开始
该指南只做入门介绍。适合对 Angular, Rails, Devise 和 Bootstrap 有基本了解的用户群体。本文不深入探究 Active Record,但会提及 Active Model Serializer,因其是发送模型到 JavaScript 前端的必要途径。为深化主题,我会安装 Bootstrap,并做相应的验证。
你可通过以下视频进行辅助学习:
视频:https://youtu.be/CtsC0iRxrAk
开发配置
打开一个终端窗口,进入你想要创建项目的工作目录,就可以开始开发工作了。在这个演示示例里,我使用的是 Desktop (桌面) 目录。
在终端窗口中,执行 `$ rails new YOUR-APP` 即可初始化 Rails 项目,初始化程序创建了一个目录,目录将会包含了整个框架 ,以及相关依赖包,目前这些依赖关系都被记录在 gems 配置文件 Gemfile 里面。(PS, 文中 $ 符合表示终端提示符)
接着,打开 Gemfile ,删除 `gem turbolinks`,然后添加以下内容:
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 的情况做准备。
Bower 是和 Ruby gems 或者 Node npm 类似的包管理工具,更多信息请参阅:https://bower.io。你可以通过 npm (npm install -g bower) 将其安装,但是在这份指南中,我使用 bower-rails 来安装 bower。
初始化 Gem 资源库,创建一个数据库并添加一个迁移文件
现在我们准备进行这些 gem 资源库的安装与初始化,然后创建数据库,并添加一个迁移文件以便用户可以使用一个用户名进行注 册操作,然后用如下命令将这些迁移应用到我们方案中去:
$ 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 use
r 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”gem 库中生成用户串号器:
$ rake bower:install $ rails g serializer user
找到 app/serializers/user_serializer.rb 文件,然后在当 Devise 从 Rails 请求用户信息的时候直接在属性 :idso 后面添 加 :username。这样要比提示说 “Welcome, jesse@email.com” 要好看许多,不过如果提示成了 “Welcome, 5UPer$3CREtP4SSword”,那也蛮糟糕的。这只是开个玩 笑,不过说真的,不要这样做。
在 config/application.rb 文件中直接在 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 获取一次调用时,不要担心有关于认证令牌的事情;第四,用户将会有一个用户名称(username)。
接下来要打开 app/controllers/users_controller.rb 文件,然后确定你可以使用任何 /users/:id.json 这样 JSON 形式的请 求访问用户:
class UsersController < ApplicationController
def show
user = User.find(params[:id])
render json: user
end
end
不要担心 routes.rb 文件中的 :show 的设置问题。Devise 已为我们处理好!
Rails 会默认使用 views/layouts/application.html.erb 进行初始化,但这不是我们所需要的,所以我们需要做进一步操 作:
-
将这个文件挪到 app/views/application/ 目录下。
-
将它重新命名为 index.html.erb。
-
使用<ui-view></ui-view>替换<%= yield %> (除了头部的 scritp/style 标记,我们 不会对其它任何的 erb 进行渲染)。
-
移除脚本和样式表的 erb 标记中所有的 “turoblinks” 提示信息。
-
向 <body>标签添加一个 ng-app="myApp" 的属性。当我们启动服务器的时候,Angular 将会进行加载操作 ,并会为此在初始化应用之前加紧对 DOM 的查找。
配置好后端的最后一个步骤就是放置好资源管道。Bower 已经在 vendor/assets/bower_components 中为我们安装好系列东西。 同时我们也早将较好的 gem 资源库安装好。现在,我们要确保应用可以找到下列脚本和样式表:
在 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 的 turbolink。
最后,我们必须将 app/assets/stylesheets/application.css 重新命名为 application.scss,然后在样式的最后加上这两行 @import 代码:
*
*= require_tree .
*= require_self
*/
@import "bootstrap-sprockets";
@import "bootstrap";
Duang!! 现在,我们已经把所有设置都搞定,可以着手弄前端了。
前端
这里可以预览一下 Angular 应用程序树的样子。因为我们已经安装了 ‘angular-templates’ 的 gem 库,所以可以 将所有的 HTML 文件保持在 assets/javascript 目录中,里面有我们所有其它的 Angular 文件:
/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'])
}())
这里我用到了一个 IIFE,理由引用了下面这段话:
要将 AngularJS 组件封装到一个立即执行函数表达式(IIFE)里面。这样做可以帮助你防止变量和函数声明在全局范围内存活时 间过长而超出你的预期, 也可以帮助你避免变量冲突。这个为你想要把自己写的代码最小化,然后打包到一个单独的文件部署到生产服务器上提供了方便,以为每一个文件提供一个变量作用域 。— Codestyle.co 上的 AngularJS 指南
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 和 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"
})
你可以尝试注释掉 login/register 状态,然后运行 $ rails 来看看是否一切正常。如果正常的话,就可以看到一个好看的 “Hello World”大字。如果它就在界面的中上部,那就屏息静待,因为 Bootstrap 正在介入,然后 col-lg 会将它放到一个漂亮的位置,而不是死死的杵在左上角。
Angular 所做的就是搜寻 DOM,找到属性 ng-app,初始化 “myApp”, 从路由程序默认导航到 /hom, 定 位到 <ui-view> 指令,实例化 HomeCtrl,注入 $scope 对象,加入一个 hello 的键,给它分配一个值 "Hello World",然后用 <ui-view> 元素中的信息渲染出 home.html。一旦到了视图中,Angular 就可以扫描到任何有意义的诸如 {{...}} 绑定以及 ng-if 指令这样的命令,然后按照需要渲染出控制器的信息。这些是接下来的操作的要点,但 操作顺序会有不同。
附加 AuthCtrl.js 和 login.html/register.html 文件
因为我们已经有了所有这些在幕后的细节信息,让我们再附加上 AuthCtrl.js 和 login.html/register.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,我只想指出,因为你看到的大多数 是 Bootstrap 的 CSS 类, 所以会对其渲染留下不错的印象。如果忽略所有类的属性,其他内容都很类似,例如:asng-submit,ng-model,andui-sref,通常它们都有 href 锚 的 tag 属性。现在,为了 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');
}, 1pxfunction(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 创建的,并且有一些非常棒的功能,例如:sAuth.login (userParameters, config) 和 Auth.register(userParameters, config),它们创建了一个 promise,并由登陆用户一次性处理。
本文标题:在 Rails 上构建 Angular 单页应用并集成 Devise 和 Bootstrap
本文地址:https://www.oschina.net/translate/setting-up-an-angular-spa-on-rails-with-devise-and-bootstrap
参与翻译:leoxu, 无若, Tocy, Robbie_Zhu, 吴亮弟