vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

Angularにも触れてみる 1

はじめに

前回はReactを使ってブログ記事を表示するページを作りかけてみたわけですが、表示するための記事を投稿するためのページが欲しくなりました。

投稿するページは自分だけが利用できればそれでよく、正直DBにそのままデータを突っ込んでも。。。と思ったりもしましたが、それは流石に不便かな、と。

そこでふと、以前聞いたRebuild.fmでReactについて話していた回を思い出しました。
管理画面のようなページを、短時間で作成するには現時点ではAngularの方がやりやすい、というような話題があったように思うのですが、今回の投稿ページはかなりそれに近いのでは?と思ったので、Angularに挑戦してみることにしました。

…というのが表向きの理由です。

裏の理由としては、たまたまKindleストアで見つけたコレの表紙が気になっていたからですw

という感じで、投稿ページはAngularを、閲覧ページはReactを使って作ってみようと思います(挫折するまで)。

準備

  • プロジェクトは前回のものにページを追加することにします。
  • JavascriptCSSは閲覧ページとは別のものを使用することにします(ごちゃごちゃになりそうなので)。
  • Angularは公式ページからver.1.3.15のminified版をダウンロードします。

ファイルの追加(ソースコードは後述)

  1. views ディレクトリに「newpost.slim」を新規作成します。
  2. public > js ディレクトリに「addnewpost.js」を新規作成します。
  3. ダウンロードした「angular.min.js」をpublic > js ディレクトリに置きます。

「angular.min.js」の変更

以前BackboneでもありましたがChromeで「angular.min.js」を開くとmin.mapが見つからないと404エラーになります。

そのため、最終行のコメントアウトを変更してやります。

//# sourceMappingURL=angular.min.js.map
↓
// # sourceMappingURL=angular.min.js.map

SlimでAngularを使う

newpost.slim

DOCTYPE
html lang='ja' ng-app='NewPostApp'
  head
    meta charset='utf-8'
    title Add New Post
  body
    header
      a.header_title href='/' Vaguely
    body
      main#newpost ng-controller='NewPostCtrl'
        form ng-submit='addPost()'
          input#new_title type='text' placeholder='タイトルを入力' ng-model='postTitle'
          textarea#new_article placeholder='本文を入力' ng-model='postArticle'
          input#new_category type='text' ng-model='postCategory'
          div ng-bind='postCategory'
          input#new_btn_submit type='submit' value='投稿'
          input#new_cancel type='reset' value='キャンセル'

      script src='/js/angular.min.js'
      script src='/js/addnewpost.js'

「ng-XXX」の部分がAngular特有の部分です。
処理の内容は以下のような感じです。

  1. ng-app: Angularを使用するための宣言。今回は「NewPostApp」を指定していますが、空のままでも使用できます。
  2. ng-controller: MVCの中心となる、コントローラをHTML要素(今回はmainタグ)に追加します。
  3. ng-submit: submitボタンを押した時に、AngularのSubmitイベントを発火させます。
  4. ng-model: HTML要素にモデルを紐付けます。これによって「$scope.postTitle」の形でTextareaなどの値を取得したり、 「ng-bind」で該当のモデルを紐付けているHTML要素にリアルタイムに値を出力することができます。

基本的にはHTMLと同じように書くことができますが、いくつか注意点があります。

  1. まずページ内でAngularを使うことを宣言する「ng-app」。今回は「NewPostApp」を指定していますが、ここを空にしたい場合、「< html lang='ja' ng-app >」のように、「html lang='ja' ng-app」と指定するとエラーになります。

そのため、「html lang='ja' ng-app=''」としてやる必要があります。

  1. ng-modelを設定したテキストボックスなどへの入力内容を、ページ上にリアルタイムで反映させるデータバインディングを使いたい場合、HTMLのように「div {{postCategory}}」とするとエラーになります。

回避するためには文字列として出力する、ng-bindを使うといった方法があるようですが、見た目的に一番何をやっているかがわかりやすそうなのでng-bindを使いました。

div ng-bind='category'

ちなみに文字列として出力する場合は、「div '{{category}}'」のようにしてもエラー回避はできますが、出力先に「''」が表示されますw

Javascript

ようやくメインにたどり着きました。
現在以下のように書いています。

addnewpost.js

(function () {
  // Angularモジュールを作成する.
  var newPostApp = angular.module('NewPostApp', []);
  // コントローラの作成.scope、httpを引数とする.
  newPostApp.controller('NewPostCtrl',[
    '$scope', '$http',
    function($scope ,$http) {
      $scope.addPost = function()
      {
        // Postメソッドでデータを送信する(タイトル、記事本文をJSONとして送信).
        $http.post('/create', {title: $scope.postTitle, article: $scope.postArticle}).
            success(function(data){
              console.log('success');
            }).
            error(function(data, status, headers, config) {
              console.log('fail');
            });
        // タイトルのテキストボックスを空にする.
        $scope.postTitle = '';
      };
    }
  ]);
}).call(this);
  • Submitボタンが押された時に「$scope.addPost」が呼ばれ、Postメソッドを投げます。これを後述のSinatra側のコントローラで受け取ります。
  • HTMLの要素やデータにアクセスするには、引数として渡されている「$scope」を使用します。
  • 「$scope」を使うと、Textareaなどのデータを受け取るだけでなく、新しい値を代入することもできます。
  • データを送受信するためには同じく引数として渡されている「$http」を使用します。
  • jQueryを使ったAjaxと同じように、データ送信に成功すればsuccessが、失敗すればerrorが呼ばれます。

Sinatraのコントローラで受け取る

app.rb

require 'json'
require 'rss/maker'
require 'sass'
require 'sinatra'
require 'sinatra/base'
require 'sinatra/reloader'
require 'slim'

〜省略〜

get '/newpost' do
  # 投稿ページの表示.
  slim :newpost
end
post '/create' do
  # JSON形式でデータを受け取る.
  jsnParams = ActiveSupport::JSON.decode(request.body.read)
  print jsnParams['title']
  print jsnParams['article']
end

〜省略〜

Postメソッドで投げたJSONデータは、「request.body.read」で受け取ることができます。
そのまま、または「request.body.read.to_json」とすれば、「jsnParams[:title]」や「jsnParams['title']」のような形で受け取れるかな?と思いきやうまくいかず...。
(エラーとなったり、「title」という文字列が返ったりしました)

ActiveSupport::JSON.decode」で変換し、「jsnParams['title']」と指定することでJavascript側で設定していた値が取得出来ました。

本当のところはまず投稿ページでログイン認証が必要だったり、フォームデータのサニタイジングやDBへのデータ追加などもありますが、これは次回。

感想など

今回触ったところだけでいうと、Angularは既存のHTMLのイベントやAjaxを置き換える形となっており、なんとなくとっつきやすいような印象を受けました。
もちろん作成上のルールに従う形で書いたり、今回未使用のfactoryなどを使用してみるとまた違って見えるかもしれませんが。

Reactと比較してどちらが優れているか?ということではなく、お互い得意分野があり、作りたいものに合わせて選択できるようになれば良いかな、と思っています。

どちらもまだHello worldレベルではありますが、色々と試しながら身につけていけたらと思います。

Angular.js

Sinatra