2014/07/27 @jsCafe21
サーバサイドで動的にhtml生成していたり
jQueryをガッツし使っている
既存プロジェクトにAngularJSを
部分的につっこんでみた
名前:佐藤俊輔
twitter : @ushisantoasobu
所属 : 株式会社ジークレスト
経歴 : 主にフロントエンドエンジニア
担当 : 現在はポケットランド(スマホweb)というアバターサービス
自己紹介
その前の発表がVue.jsで、そこでおそらく
Angularがディスられるので省略
Angularとは
「AngularってSPAつくるためのものでしょ?」
自分の勝手な思い込み
- Rebuild 27
- mozaic.fm #3 (こちらはAngularのみを70分くらい)
「ハイブリッド」もありのようだ
- step1 : データバインディングで効率化
- step2 : 共通モジュールをディレクティブ化
- step3 : 中規模程度の新機能をSPAっぽく
担当プロジェクトでのAngular導入ステップ
- step1 : データバインディングで効率化 ✓
- step2 : 共通モジュールをディレクティブ化 ✓
- step3 : 中規模程度の新機能をSPAっぽく
担当プロジェクトでのAngular導入ステップ
データバインディング
<p>{{message}}</p>
$scope.message = “hoge”;
.html
.js
使っているところ = xhrで表示更新するところ
(初期表示のhtmlについてはJavaのvelocityで生成している)
- ゲームのポイントやランキング
- アイテム所持数
- 報酬アイテム
などなど...結構ある
- 同じデータを表示する箇所が複数あっても安心
- データのオブジェクトさえつっこめばいいので楽
(もうjQueryではやりたくない)
特にいいと思ったところ
同じデータを複数のDOMで表示することが多々ある
<div class=”point_total”>×{{targetRewardPoint}}</div>
...
<div class=”point_total”>×{{targetRewardPoint}}</div>
...
<div class=”result_point_total”>×{{targetRewardPoint}}</div>
.html
<div class=”point_total”>×{{targetRewardPoint}}</div>
...
<div class=”point_total”>×{{targetRewardPoint}}</div>
...
<div class=”result_point_total”>×{{targetRewardPoint}}</div>
$scope.targetRewardPoint = data.point;
.html
.js
もちろんこれだけでOK
{
" "bannerDataList":null,
" "boxLevelBonusAchieveLevel":1,
" "boxLevelBonusList":[],
" "completeFlg":false,
" "completePoint":0,
" "countRewardDataList":null,
" "currentPoint":327,
" "currentRank":27910,
" "deliveryCount":0,
" "deliveryCountRewardDataList":null,
" "orderPoint":0,
" "pointRewardDataList":[
" " {
" " " "categoryName":"アクセサリ/手系",
" " " "count":1,
" " " "dataItemFlg":false,
" " " "imageUrl":"shop/clothes/acceh/acceh_10824494_shop.png",
" " " "itemId":10824494,
" " " "itemName":"ふしぎな実",
" " " "itemRare":0,
" " " "rewardPoint":300
" " }
" ],
" "restRewardCount":55,
" "resultCd":0,
" "token":null
}
あるAPIでこのようなjsonが返ってくるとする
{
" "bannerDataList":null,
" "boxLevelBonusAchieveLevel":1,
" "boxLevelBonusList":[],
" "completeFlg":false,
" "completePoint":0,
" "countRewardDataList":null,
" "currentPoint":327,
" "currentRank":27910,
" "deliveryCount":0,
" "deliveryCountRewardDataList":null,
" "orderPoint":0,
" "pointRewardDataList":[
" " {
" " " "categoryName":"アクセサリ/手系",
" " " "count":1,
" " " "dataItemFlg":false,
" " " "imageUrl":"shop/clothes/acceh/acceh_10824494_shop.png",
" " " "itemId":10824494,
" " " "itemName":"ふしぎな実",
" " " "itemRare":0,
" " " "rewardPoint":300
" " }
" ],
" "restRewardCount":55,
" "resultCd":0,
" "token":null
}
枠部分(報酬アイテム情報)を表示したい
<div>
" <div>
" <img src="http://img.atgames.jp/sp/update/2014/04/icon_point.png">
" " <span id=”reward_span_reward_point”></span>達成
" </div>
" <div><img id=”reward_img_reward_url”></div>
" <div id=”reward_div_reward_itemname”></div>
" <div id=”reward_div_reward_itemcount”></div>
</div>
var reward = data.pointRewardData[0];
jQuery(“#reward_span_reward_point”).html(reward.rewardPoint);
jQuery(“#reward_img_reward_url”).attr('src', reward.imageUrl);
jQuery(“#reward_div_reward_itemname”).html(“<em>” + reward.categoryName +
“</em><br>” + reward.itemName);
jQuery(“#reward_div_reward_itemcount”).html(“×” + reward.count);
.html
.js
これまで自分がjQueryで書いてた方法(もっとキレイに書ける?汗)
AngularJSで書いた方法。ロジック側の記述が楽
<div>
" <div>
" " <img src="http://img.atgames.jp/sp/update/2014/04/icon_point.png">
" " <span>{{pointRewardData.rewardPoint}}</span>達成
" </div>
" <div><img src="http://img.atgames.jp/{{pointRewardData.imageUrl}}"></div>
" <div><em>{{pointRewardData.categoryName}}</em><br>{{pointRewardData.itemName}}</div>
" <div ng-show="pointRewardData.count > 1">×{{pointRewardData.count}}</div>
</div>
$scope.pointRewardDataList = pointRewardDataList;
.html
.js
負の遺産もつくってしまった、、、恥ずかしいけど書く
ページ表示時に
サーバサイドで動的に値を埋め込んでいる、
でもその後xhrでも値を更新したい、
といった箇所
<!-- 初期表示にvelocityで値を埋め込みたい -->
<p>$!{rank}</p>
<!-- でもangularでも書きたい(どんどん更新していくので) -->
<p>{{rank}}</p>
.html
.js
$scope.rank = 0;
どうする?
<p>{{rank}}</p>
 
 
<script>
var setupAngular = function(){
" var _scope = angular.element(ngCtrl).scope();
" _scope.$apply(function(){
" " _scope.rank = $!{rank};
" });
};
</script>
.html
.js
$scope.rank = 0;
 
angular.element(document).ready(function() {
" if(setupAngular && typeof setupAngular === "function"){
" " setupAngular(); //本体htmlのグローバルメソッドにアクセス
" }
});
html側のスクリプトにvelocityで吐き出される値を書き出して、、、
みたいなことをやってる。しかも不要にグローバル変数をつくってしまってる
- 簡単にいえば「カスタムタグ」or「カスタム属性」
ディレクティブ
サービス内の複数のページで共通で使用している機能
をディレクティブ化する
ディレクティブ化する前
イベント毎にソースをコピペして、値の一部を変えることで対応していた(惰性)
<canvas id=” canvas_100522”></canvas>
<script>
(function(){
" var self = {}, canvas, stage, exportRoot,
" POS_X = 0, POS_Y = 0, SCALE = 0.55; //※ここ調整する
"
" self.init = function() {
" " canvas = document.getElementById("canvas_100522");
" " df100522_images = df100522_images||{};
 
" " var len = df100522.properties.manifest.length;
" " for(var i = 0; i < len; i++){
" " " var url = df100522.properties.manifest[i].src;
" " " url = window.IMAGE_DOMAIN + "/high/roomfurniture/deco/" + url;
" " " df100522.properties.manifest[i].src = url;
" " }
 
" " var loader = new createjs.LoadQueue(false);
" " loader.addEventListener("fileload", self.handleFileLoad);
" " loader.addEventListener("complete", self.handleComplete);
" " loader.loadManifest(df100522.properties.manifest);
" }
 
" self.handleFileLoad = function(evt) {
"" if (evt.item.type == "image") { df100522_images[evt.item.id] = evt.result; }
" }
 
" self.handleComplete = function() {
"" //ここは省略
" }
 
" window.selfytown.df100522_shop = self;
}());
</script>
.html
ディレクティブ化する
.directive('df', function() {
" return {
" " restrict: 'E',
" " transclude: true,
" " scope: {
" " " furnitureid: '@',
" " " category: '@',
" " " posx: '@',
" " " posy: '@',
" " " scale: '@'
" " },
" " template:
" " " '<canvas id="js_canvas_dynamic_furniture_{{furnitureid}}"></canvas>',
" " link:function($scope, $element){
 
" " " var canvas,
" " " " len,
" " " " url,
" " " " loader,
" " " " exportRoot,
" " " " stage;
" " "
" " " canvas = $element[0];
 
" " " len = window["df" + $scope.furnitureid].properties.manifest.length;
" " " for(i = 0; i < len; i++){
" " " " url = window["df" + $scope.furnitureid].properties.manifest[i].src;
" " " " url = window.IMAGE_DOMAIN + "/high/roomfurniture/" + $scope.category + "/" + url;
" " " " window["df" + $scope.furnitureid].properties.manifest[i].src = url;
" " " }
 
" " " //CreateJSまわりの処理は省略
" " },
" " replace: true
" };
});
.js
ディレクティブ化した後
<df furnitureid=”100522” category=”deco” posX=”0” posY=”0” scale=”0.55”></df>
.html
以後これだけでOK(でもやっぱ黒魔術的な匂いがする)
- ディレクティブの使いどころについては
@konpyuさんのスライドがわかりやすかった
http://www.slideshare.net/KonYuichi/0601-angular-note
- mozaic.fm #3 でもゲストでお話されています!
- 中規模程度の機能をSPAっぽくつくりたい
SPAっぽくつくりたい
イベントページのタブボタン押下時の画面の切り替えを
今はページ読み込みになっているけど
APIだけ返してもらっての表示の切り替えにしたい
- アプリっぽい挙動にしたい = サクサク感
やりたいこと
- フロントまわりでの処理が多くなるので
機種やOSによっては逆に描画が遅くなるかも?
- htmlをキャッシュさせることでSPAを実現するけど
htmlの更新の仕組みをうまくつくらないと
- 既存のワークフロー(webコーダー、サーバサイド)
から見直さないといけない
懸念事項
- jQueryも使える(angular.element)
- 動的に表示を更新してく箇所がたくさんあるときは
jQueryではもう書けない、
でも必ずしもAngularでなくてもよい?
まとめや補足
- 改めて振り返ると、、初期学習コストは高いかも
- html自体がテンプレートになっているので
webコーダーもみやすい
- でも急に見知らぬカスタムタグつくるとビックリ
されて迷惑がられる
まとめや補足
- Angularっぽくないこのスライドの色合いは、
当初はW杯直前に発表する予定だったため
ブラジルカラーっぽくしてるだけ
まとめや補足
ご清聴ありがとうございます!

サーバサイドで動的にhtml生成していたりjQueryをガッツし使っている既存プロジェクトにAngularJSを部分的につっこんでみた @jscafe21