RailsAPI×AngularでかっこいいWebアプリを作(ろうと試み)る【API呼んでみる編】

ruby Ruby
rails Rails
angular-icon AngularJS
webpack webpack

RailsAPI×AngularでかっこいいWebアプリを作(ろうと試み)る【環境構築編】の続きです。

前回のあらすじ

RailsAPIアプリ作る

Angularアプリをwebpackして/publicにぶっこむ

RailsAPIアプリといいつつrootのみ画面返しちゃってSPA

というわけで今回はただただAngularからAPI呼んでみます。
大したことはしないのでつまらないです。
ここで帰るときもストックだけはしていってね。

ソースも置いとくので参考にどうぞ。

API作る

scaffoldコマンドを実行。

$rails g scaffold User

するとAPIモードなので勝手に判断してmigration,model,controllerのみを生成してくれます。賢すぎる。

Running via Spring preloader in process 4795
      invoke  active_record
      create    db/migrate/20161001061821_create_users.rb
      create    app/models/user.rb
      invoke    test_unit
      create      test/models/user_test.rb
      create      test/fixtures/users.yml
      invoke  resource_route
       route    resources :users
      invoke  scaffold_controller
      create    app/controllers/users_controller.rb
      invoke    test_unit
      create      test/controllers/users_controller_test.rb

コントローラーもAPI仕様になっているのでもう特にやることはないです、ハイ。

UserController.rb
class UsersController < ApplicationController
  before_action :set_user, only: [:show, :update, :destroy]

  # GET /users
  def index
    @users = User.all

    render json: @users
  end

  # GET /users/1
  def show
    render json: @user
  end

  # POST /users
  def create
    @user = User.new(user_params)

    if @user.save
      render json: @user, status: :created, location: @user
    else
      render json: @user.errors, status: :unprocessable_entity
    end
  end

  # PATCH/PUT /users/1
  def update
    if @user.update(user_params)
      render json: @user
    else
      render json: @user.errors, status: :unprocessable_entity
    end
  end

  # DELETE /users/1
  def destroy
    @user.destroy
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_user
      @user = User.find(params[:id])
    end

    # Only allow a trusted parameter "white list" through.
    def user_params
      params.fetch(:user, {})
    end
end

ただサンプルでDB作るのもめんどうなのでGET /usersで固定値を返すようにしてしまいます。なんだかすいませんね。

UserController.rb
def index
  @users = []
  (1..10).each{|i|
    @users.push({id: i, name: 'ユーザー'<<i.to_s})
  }

  render json: @users
end
  }

  render json: @users
end

migrationで怒られないようにconfigもちょっといじる

config/environments/development.rb
config.active_record.migration_error = false

API呼ぶ

AngularアプリからRestAPIを呼び出すためには$resourceファクトリーを使用します。
その場合別途angular-resourceというモジュールのインストールが必要になりますが、前回記載したpackage.jsonを使用している場合は既にインストール済みのはずです。
ただしモジュールが違えばもちろん型定義ファイルも別になるのでこれはインストールする必要あり。

型定義ファイルをインストール

$cd app/angular/javascripts/
$dtsm install angularjs/angular-resource --save

APIを呼ぶためのUserResourceというResourceを定義します。

mkdir resources
touch resources/user-resource.ts
user-resource.ts
/// <reference path="../typings/bundle.d.ts"/>

export interface User {
    id: number
    name: string
}

interface Resource extends ng.resource.IResource<User>, User {
}

export default class UserResource {

    get userResource(): ng.resource.IResourceClass<Resource>{
        return <ng.resource.IResourceClass<Resource>>this.resource('/users')
    }

    static $inject = ['$resource']
    constructor(private resource: ng.resource.IResourceService) {
    }

    all(success: (v: Array<User>) => void) {
        let res = this.userResource.query({
            },
            () => {
                success(res)
            })
    }
}

angular.module('App').service('userResource', UserResource)

resourceからユーザーの一覧を取得して表示するだけのComponentを定義します。

mkdir components
touch components/users.ts
users.ts
///<reference path="../typings/bundle.d.ts"/>

import angular = require('angular')
import UserResource from '../resources/user-resource'
import {User} from '../resources/user-resource'

class Users {
    private users: Array<User>

    static $inject = ['userResource']
    constructor(private userResource: UserResource) {
        this.userResource.all((users)=> {
            this.users = users
        })
    }
}

angular.module('App').component('users', {
    controller: Users,
    bindings: {},
    template:
        `
    <md-list>
        <md-list-item ng-repeat="user in $ctrl.users">
            <p> {{ user.name }} </p>
            <md-checkbox class="md-secondary"></md-checkbox>
        </md-list-item>
    </md-list>
        `
})

エントリーポイントであるappliaction.tsに追加した2つのクラスをrequireする。

application.ts
/// <reference path="./typings/bundle.d.ts"/>

// angular
import angular = require('angular')
require('angular-material')
require('angular-cookies')
require('angular-resource')
require('angular-sanitize')
require('angular-route')
require('angular-animate')
require('angular-material-icons')
require('es5-shim')

let app = angular.module('App', [
    'ngMaterial',
    'ngCookies',
    'ngResource',
    'ngSanitize',
    'ngRoute',
    'ngMdIcons',
    'ngAnimate'
]);

//stylesheets
require('../stylesheets/application')

//components
require('./components/users')

//services
require('./resources/user-resource')

//angular-materialのTheme
app.config(($mdThemingProvider) => {
    $mdThemingProvider.theme('default')
                      .primaryPalette('grey', {
                          'default': '100'
                      })
                      .accentPalette('pink', {
                          'default': '700'
                      });
});

//Controllerも一緒に定義しちゃいました
export default class AppCtrl {
    private title: string = 'Hello RailsAPI × Angular!'
    static $inject = ['$rootScope', '$scope', '$cookies', '$window', '$timeout', '$location', 'userResource']
    constructor(
        private rootScope: ng.IRootScopeService,
        private scope: ng.IScope,
        private cookies: any,
        private window: ng.IWindowService,
        private timeout: ng.ITimeoutService,
        private location: ng.ILocationService
    ) {
    }
}

app.controller('AppCtrl', AppCtrl)

そんで作ったComponentを使ってみる。

index.erb
<html ng-app="App">
<head>
  <meta charset="UTF-8">
  <meta name="viewport", content="width=device-width, initial-scale=1, maximum-scale=1">
  <title>rails-api-angular</title>
</head>
<body ng-controller="AppCtrl as appCtrl" ng-cloak>
  <header class="header md-whiteframe-3dp">
    <md-toolbar md-scroll-shrink>
      <div class="md-toolbar-tools">
        <h3>
          <span>rails-api-angular</span>
        </h3>
        <span flex></span>
        <md-button class="md-accent">Sign up</md-button>
        <md-button class="md-accent">Log in</md-button>
      </div>
    </md-toolbar>
  </header>

  <div layout="column" layout-align="center center">
    <h1>{{appCtrl.title}}</h1>
    <!--component-->
    <users></users>
  </div>

<%= stylesheet_link_tag webpack_asset_path('application.css') %>
<%= javascript_include_tag webpack_asset_path('application.js')%>
</body>
</html>

webpackとrails起動実行

$npm run dev
$rails s

きた!寂しいのでチェックボックスもつけた。

Kobito.78ye0L.png

次回はangular-materialをとっても便利にする話をしようと思います。