sb8244/angular-tutorial

语言: Ruby

git: https://github.com/sb8244/angular-tutorial

README.md (中文)

大约4个月前,我开始了我的Angular + Rails集成之旅。我的公司SalesLoft决定尝试这个项目,我们现在都非常喜欢这个结果。一路上有一些障碍,我今天的目标是帮助你克服这些事情,详细解释一切,并留下一个可以应用于其他项目的模板。我们潜入吧!这将是一个非常新手友好的教程,如果你超过这个,跳过好的部分。

预请求数

  • 系统上的Ruby 2 / Rails(gem)
  • Bower安装(http://bower.io/)
  • HTTPS://GitHub.com/随便8244/angular-tutorial

创建新的Rails应用程序

让我们创建你的Rails应用程序(暂时没有测试单元)

rails new -T教程

一旦我们这样做,让我们添加我们的凉亭组件。我们将用ui-router替换默认的Angular路由器,Restangular用于数据访问,并且Angular需要lodash。

bower.json

{
  "name": "Tutorial",
  "private": true,
  "ignore": [
    "**/.*",
    "node_modules",
    "bower_components",
    "test",
    "tests"
  ],
  "dependencies": {
    "lodash": "~2.4.1",
    "angular": "~1.2.22",
    "angular-ui-router": "~0.2.10",
    "restangular": "~1.4.0"
  }
}

bowerrc文件将告诉bower在哪里安装组件,我们将把它放在比Rails更友好的Rails位置

.bowerrc

{
  "directory": "vendor/assets/components"
}

现在您可以运行bower install并观看组件下载。

服务器配置

我有一个goto Gemfile,当我开始时,我会进入大多数基础项目。我喜欢将SQLite用于这样的简单开发,并且我使用slim作为模板,因为它优于其他所有(同意或你错了)。您可以查看我的Gemfile并将其用于此项目。

让我们添加config / initializers / slim.rb文件,这将允许我们在模板中使用Angular的{{}}符号。默认情况下,Slim尝试将其解析为属性,这显然不是我们想要的:

Slim::Engine.set_default_options attr_delims: { '(' => ')', '[' => ']' }

在config / application.rb中,我们必须在Application类中添加config.assets.paths << Rails.root.join('vendor','assets','components')。这将允许资产管道合并我们的Bower供应商组件。

资产配置

要检查的第一件事是assets / javascripts / application.js文件。这是将加载我们所有脚本和引导Angular的文件。如果我们要一起使用Rails + Angular,那么我认为使用资产管道是完全可以的,所以// = require是可以的。我也在创建Angular APP并配置路由。记下RestangularProvider.setBaseUrl(“/ api”);.这是将Restangular服务配置为始终命中/ api端点。我们将以这种方式分离我们的Rails服务器。

下一个重要的是javascripts / angular / templates.js.erb文件。此文件将利用资产管道获取javascripts / angular / templates / *目录中的所有Slim模板,并将它们放在$ templateCache中。在引用模板时,始终会查看Angular中的$ templateCache,这将确保加载我们的模板。如果您遵循我的资产结构,您将不需要更改此文件,但它中有一些相对路径(如果您移动它,请小心)。

如果遇到templates.js.erb中没有body的问题,请将body更改为source。它是链轮的更新版本。

路由配置

让我们的应用程序在前端显示的最后一件事是设置我们的config / routes.rb文件。在文件的最底部,放:

get "*path", to: "application#index"
root 'application#index'

这将重定向之前未定义的任何路径到我们的catch-all应用程序#index action。

在您的controllers / application_controller.rb中,添加以下操作:

  def index
    render text: "", layout: "application"
  end

我选择不浏览我非常简单的布局/ application.html.slim文件,但我鼓励你去看看。在我的应用程序的这一点(提交“步骤2”),我有一个屏幕,只显示来自嵌套控制器视图的“hi”。我知道Angular正在工作,我可以添加一个简单的API路由供我们使用。

Rails API设置

我喜欢在API命名空间下隔离我的Rails API,因此我创建了一个所有API控制器都将继承的controllers / api / base_controller.rb文件。它禁用了真实性令牌检查,并表示它默认只响应json。

我要创建一个超级基本模型。这个模型就是这样,我们有一些东西要展示,运行它并在以下情况下迁移你的数据库:

rails g model widget title:text content:text cool:boolean

现在我们将在controllers / api / widgets_controller.rb中创建一个Api :: WidgetsController类。如果您执行rails g controller,请注意您将要删除由它创建的任何资产,帮助程序或视图。它们将不再需要,因为我们只是提供JSON。

class Api::WidgetsController < Api::BaseController
  def index
    respond_with :api, widgets
  end

  def show
    respond_with :api, widget
  end

  def create
    respond_with :api, widgets.create(widget_params)
  end

  def destroy
    respond_with :api, widget.destroy
  end

  private

  def widgets
    @widgets ||= Widget.all
  end

  def widget
    @widget ||= widgets.find(params[:id])
  end

  def widget_params
    params.permit(:title, :content)
  end
end

并且以下内容添加到config / routes.rb ABOVE中捕获所有路由。请记住,它必须始终是最后的。

  namespace :api do
    resources :widgets
  end

我非常喜欢CRUD控制器在使用respond_with和json响应时所采用的简单性。它太优雅了,我无法克服它。请务必查看规格以确保其有效!

我们在吗?

此时,我刚刚将代码提交为提交“步骤3”。我们在前端加载了Angular应用程序,我们的Rails后端为api提供服务。所以现在我们可以在Angular中使用api并显示一些数据。要获取该数据,请运行rake db:seed以获取为您创建的10个小部件。

创建角度控制器

我们将在assets / javascripts / angular / controllers / widgets.list.js创建我们的WidgetsListController。

APP.controller('WidgetsListController', ['Restangular', function(Restangular) {
  var self = this;

  Restangular.all("widgets").getList().then(function(widgets) {
    self.widgets = widgets;
  });
}]);

这不是一个非常复杂的控制器,但它确实展示了Restangular如何优雅地与您的Rails API进行交互。您可以免费获得一系列令人敬畏的功能。在assets / javascripts / angular / templates / widgets / index.html.slim中有一个模板。这只是一张简单的桌子。

我也继续创建了一个WidgetsShowController和视图,但它并不是很令人兴奋。创建小部件更有趣。

// This is in widgets.index.js
  self.create = function(widget) {
    Restangular.all("widgets").post(widget).then(function(widget) {
      self.widgets.push(widget);
    });
  };

我绑定一个接受模型的创建函数,将其发布到窗口小部件,并将返回的模型附加到列表中,该列表将立即显示在表格中!我用这个非常简单的形式做到了:

  form ng-show="showCreate === true" name="form"
    .form-controls.row
      .col-xs-3: label Title
      .col-xs-9: input.form-control name="title" ng-model="newWidget.title" required=true
    .form-controls.row
      .col-xs-3: label Content
      .col-xs-9: input.form-control name="content" ng-model="newWidget.content" required=true
    .form-controls.row
      .col-xs-3
      .col-xs-9: button.btn-block.btn.btn-success ng-click="create(newWidget)" ng-disabled="form.$invalid" Create

Angular表单指令将在具有名称时执行验证,然后我可以基于此禁用创建按钮。这是超级光滑!

对于我们的最后一个功能,将添加删除。这也将放在WidgetsListController中:

  self.destroy = function(widget) {
    widget.remove().then(function() {
      _.remove(self.widgets, function(w) {
        return w.id === widget.id;
      });
    });
  };

我想展示这个范围的原因是它在一个小部件中接受,然后在它上面调用remove()。 widget实际上是一个Restangular对象,并且有一些方便的方法,如.remove()就可以了。这简直太简单了!

包起来

很多东西都用于设置新的Angular应用程序(代码已经存在时大约需要30分钟)。这里的目标是让一个项目进入您的手中,集成Rails + Angular,而不依赖于基于类型的自选或基于节点的Angular服务器。完整的项目,请访问https://github.com/sb8244/angular-tutorial,提交更改将让您对我写这篇文章时的想法有所了解。

谢谢!

本文使用googletrans自动翻译,仅供参考, 原文来自github.com

en_README.md

About 4 months ago, I began my journey into Angular + Rails integrated. My company, SalesLoft, decided to try this out on a project and we are all really loving the outcome right now. There were some bumps along the way, and my goal today is to help you get past those things, explaining everything in detail, and leaving with a template that you can apply to other projects. Let's dive in! This is going to be a very newbie friendly tutorial, skip to the good parts if you are above that.

Pre-reqs

Create New Rails App

Let's create our Rails app (without testunit for now)

rails new -T tutorial

Once we do that, let's add in our bower components. We are going to replace the default Angular router with ui-router, Restangular for data access, and lodash is required with Angular.

bower.json

{
  "name": "Tutorial",
  "private": true,
  "ignore": [
    "**/.*",
    "node_modules",
    "bower_components",
    "test",
    "tests"
  ],
  "dependencies": {
    "lodash": "~2.4.1",
    "angular": "~1.2.22",
    "angular-ui-router": "~0.2.10",
    "restangular": "~1.4.0"
  }
}

The bowerrc file will tell bower where to install the components at, and we will put it in a more Rails-friendly place than the default

.bowerrc

{
  "directory": "vendor/assets/components"
}

Now you can run bower install and watch your components download.

Server Config

I have a goto Gemfile that I pull into most basic projects when I'm starting. I like to use SQLite for simple development things like this, and I use slim for templates because it is superior to everything else (agree or you're wrong). You can check out my Gemfile and use that for this project.

Let's add in our config/initializers/slim.rb file which will allow us to use Angular's {{}} symbols in our templates. By default, Slim tries to parse this as attributes which is obviously not what we want:

Slim::Engine.set_default_options attr_delims: { '(' => ')', '[' => ']' }

In config/application.rb we must add config.assets.paths << Rails.root.join('vendor', 'assets', 'components') inside the Application class. This will allow the asset pipeline to incorporate our Bower vendor components.

Assets Config

The first thing to check out is the assets/javascripts/application.js file. This is the file that will load all of our scripts and bootstrap Angular. If we're going to use Rails + Angular together, then I think it is totally okay to use the asset pipeline, and so //= require is okay. I'm also creating the Angular APP and configuring the routes. Take note of RestangularProvider.setBaseUrl("/api");. This is configuring the Restangular service to always hit /api endpoints. We will separate our Rails server this way.

The next big thing is the javascripts/angular/templates.js.erb file. This file will take advantage of the asset pipeline to grab all Slim templates in the javascripts/angular/templates/* directory and put them in the $templateCache. The $templateCache in Angular is always looked at when referencing a template, and this will ensure that our templates are loaded. If you follow my assets structure, you will not need to change this file, but it has some relative paths in it (be cautious if you move it).

If you run into an issue where body isn't available in templates.js.erb, change body to source. It is an updated version of sprockets.

Routes Config

The last thing to get our application displaying on the frontend is to set up our config/routes.rb file. At the very bottom of the file, put:

get "*path", to: "application#index"
root 'application#index'

This will redirect any paths not defined previously to our catch-all application#index action.

Inside of your controllers/application_controller.rb, add the following action:

  def index
    render text: "", layout: "application"
  end

I'm choosing to not go over my very simple layouts/application.html.slim file, but I encourage you to go check it out. At this point in my application (commit "Step 2"), I have a screen that simply displays "hi" which is coming from a nested controller view. I know that Angular is working and I can add in a simple API route for us to consume.

Rails API Setup

I like to isolate my Rails API under an API namespace, so I create a controllers/api/base_controller.rb file that all API controllers will inherit from. It disables authenticity token checks and says that it only responds to json by default.

I'm going to create a super basic model. This model will just be so we have something to demonstrate, run this and migrate your DB after:

rails g model widget title:text content:text cool:boolean

Now we will create an Api::WidgetsController class at controllers/api/widgets_controller.rb. If you do rails g controller, then please note that you will want to remove any assets, helpers, or views created by it. They will not be needed because we are just serving out JSON.

class Api::WidgetsController < Api::BaseController
  def index
    respond_with :api, widgets
  end

  def show
    respond_with :api, widget
  end

  def create
    respond_with :api, widgets.create(widget_params)
  end

  def destroy
    respond_with :api, widget.destroy
  end

  private

  def widgets
    @widgets ||= Widget.all
  end

  def widget
    @widget ||= widgets.find(params[:id])
  end

  def widget_params
    params.permit(:title, :content)
  end
end

And the following is added to config/routes.rb ABOVE the catch all routes. Remember that it must always be last.

  namespace :api do
    resources :widgets
  end

I really like the simplicity that the CRUD controller takes when using respond_with and json responses. It is so elegant that I can't get over it. Make sure you check out the specs to make sure it works!

We are we at?

At this point, I have just committed my code as commit "Step 3". We have our Angular app loaded on the frontend and our Rails backend serving an api. So now we can use the api in Angular and display some data. To get that data, run rake db:seed to get 10 widgets created for you.

Creating an Angular Controller

We're going to create our WidgetsListController at assets/javascripts/angular/controllers/widgets.list.js

APP.controller('WidgetsListController', ['Restangular', function(Restangular) {
  var self = this;

  Restangular.all("widgets").getList().then(function(widgets) {
    self.widgets = widgets;
  });
}]);

This isn't a very complex controller, but it really shows how elegant Restangular makes interaction with your Rails API. There's a whole slew of awesome features you get for free. There is a template that goes with this in assets/javascripts/angular/templates/widgets/index.html.slim. It's just a simple table.

I also went ahead and created a WidgetsShowController and view, but it isn't very exciting. The creation of a widget is much more interesting.

// This is in widgets.index.js
  self.create = function(widget) {
    Restangular.all("widgets").post(widget).then(function(widget) {
      self.widgets.push(widget);
    });
  };

I bind a create function that accepts a model, posts it to widgets, and appends the returned model onto the list which will immediately display in the table! I do that with this pretty simple form:

  form ng-show="showCreate === true" name="form"
    .form-controls.row
      .col-xs-3: label Title
      .col-xs-9: input.form-control name="title" ng-model="newWidget.title" required=true
    .form-controls.row
      .col-xs-3: label Content
      .col-xs-9: input.form-control name="content" ng-model="newWidget.content" required=true
    .form-controls.row
      .col-xs-3
      .col-xs-9: button.btn-block.btn.btn-success ng-click="create(newWidget)" ng-disabled="form.$invalid" Create

Angular's form directive will perform validations when it has a name and then I can disable the create button based on that. It's super slick!

For our last feature, remove will be added in. This will also go in the WidgetsListController:

  self.destroy = function(widget) {
    widget.remove().then(function() {
      _.remove(self.widgets, function(w) {
        return w.id === widget.id;
      });
    });
  };

The reason I wanted to show this scope is that it accepts in a widget, and then calls remove() on it. widget is actually a Restangular object and has some convenience methods like .remove() on it. It couldn't be simpler!

Wrapping Up

A lot goes into the setup of a new Angular app (about 30 minutes worth when the code already exists). The goal here was to get a project into your hands that integrates Rails + Angular without relying on the typial yeoman or node based Angular server. The full project, again, is at https://github.com/sb8244/angular-tutorial and the commit changes will give you a bit of insight into what I was thinking as I wrote this.

Thanks!