ygoto3.com

Front-end engineer at CyberAgent

最近よくクリエイターが移住するカナダで Atomic Design を学ぶ

こちらは Frontrend Advent Calendar 2014 19日目の記事です。

2014年 12月8日〜12月14日の間、カナダに滞在してきました。目的は12月9日〜12日に開催される Smashing Conference 2014 へ参加するためです。

海外のカンファレンスに参加するのはおろか、海外旅行もしたことがないので、今回1人でカナダに行くということで終始緊張の連続でした。(Frontrend の @hiloki さんが「僕も行こうかな」と言っていたので、心強く思っていたのですが、残念ながら叶わず。)

本記事は、カンファレンスが開催されたカナダという国にまつわるお話を Frontrend の話題を交えながら書きつつ、このカンファレンスで一番楽しみにしていたトピックである「Atomic Design」について書いていきます。

参加の理由

今まで海外カンファレンスなど参加したことがない自分が今回参加を決意した理由は、興味があるスピーカー陣が多くいたことがもちろん1番なのですが、2番目の理由として、今年に入ってからカナダという国そのものに興味を持つようになったからです。

興味を持ったキッカケは、カンファレンス募集期間の前後、立て続けに日本からカナダに移住する Web クリエイター・エンジニアたちに会ったことです。そのうちの一人が、Mr. Frontrend である @t32k さんでした。彼のブログ記事「Webエンジニアからみたフィリピン語学留学」で書いている通り、彼はフィリピンで語学留学した後、現在カナダでエンジニアとして働こうとしています。

アメリカに近いという点でのカナダの魅力

「偶然なのか、なんだか最近多くの人がカナダに行っている気がするな。」となんとなく思っていました。IT 業界でエンジニアとして働いている者であれば、一度はシリコンバレーで働きたいと願う人も少なくないでしょう。だから、アメリカ合衆国で働きたい、というのは分かります。しかし、なぜカナダなのでしょうか。

そんな疑問に答えるかのように、Frog というクリエイターのためのカナダ留学支援団体のサイトにはカナダのバンクーバーについてこんなことが書かれています。

  • バンクーバーはシリコンバレーと時差が同じ
  • Facebook やアマゾンなどの企業が進出
  • カナダで就労して、アメリカの就労ビザを取得したらシリコンバレー就職

なんともアメリカンドリームに近づけそうなカナダの魅力がそこにはありました。もし普通の日本人がアメリカで働きたいと思ったとしても、アメリカの就労ビザ取得は難しく、働きたくても外国人にあたる日本人はなかなか働く許可を得ることが難しいでしょう。それと比較すると、カナダは就労ビザの制度に融通が利きやすいということのようです。

実際、現地で出会った人の中にもいずれはシリコンバレーで働くことを目標にしている人もいました。

カナダ自身の魅力

vancouver

上記のような理由は、カナダ自身というよりはアメリカへのアクセスに対してのカナダの魅力しか示していませんが、実際カナダのバンクーバーに行ってみると、人は優しく、街は綺麗でとても魅力的な国でした。

私は学生時代に約6年アメリカに住んでいた記憶もあって、カナダに着いたとき、街中の標識や店頭の商品を見てアメリカととても似ているな、と感じたのですが、実際に人と触れ合うと(言い方は悪いですが)アメリカのようなドライさはあまり感じられません。むしろ優しさに溢れていました。

たとえばロックが開かなくなった荷物について私が空港の人に助けを求めると「私には開け方が分からない」と言いながらもあの手この手で開けようと頑張ってくれたり、カナダに来たばかりで電車の乗車券の買い方1つ分からない私に通りすがりの人がとても丁寧に買い方を説明してくれたり、気候とは裏腹になんだかとても暖かい国だなと感じました。

そして、移民も多いためか外国人に対して慣れている感じも見受けられます。物価が若干高いところと、気候が寒いところを除けば、日本人にとっても住みやすそうな街に感じました。もちろん1週間しか滞在していないので実際のところは分かりませんが、The Japanese Community in Canada が示すようにバンクーバーの人口の 1.3% が日系であることからも日本人にとっての住みやすさが想像できます。

英語の重要性

カナダで開催されるカンファレンスということもあり、やはりコミュニケーションは英語になります。長く英語を話す機会から離れているので、この点も今回の旅において心配する要因の1つでした。

少し前に、エンジニアミーティング vol.6-4 エンジニアの英語戦略 というポッドキャスト番組でも話があったように、特にここ1、2年はまわりにいるエンジニアが英語を話せるようになる必要性を強く感じてきています。

実際、職場の友人も、English Lunch という呼ばれる英語オンリーで会話するランチを定期的に開いたり、Frontrend の @1000ch が「2014年の振り返りと人気記事まとめ」で触れているように レアジョブ英会話 で毎日レッスンを受けたりしている人がたくさんいます。

私も同様に、生の声を聞ける機会に英語で恐れず話ができることは大事だと今回の旅で思いました。(恐れず、を強調したのは、別に流暢でなくてもコミュニケーションを取ることができれば良いと思っているからです。)私は漠然と開発における技術やワークフロー、考え方など欧米は日本より進んでいるように思っていましたが、実際にカンファレンス参加者と話すと日本で私たちが課題に持っているのと同じような課題を抱えていて、相手の話が身近に感じたり私の話に共感してくれたりするのがとても新鮮でした。

Smashing Conference Whistler

さて、今回参加した Smashing Conference ですが、ウィスラーというバンクーバーから車で2時間程離れたリゾート地で開催されました。(ウィスラーは2010年冬季オリンピック競技が行われたくらいのスキーリゾートなので、正直スキーやスノボというオマケにつられてカンファレンスに参加した、という人もいました。)

参加者は、やはりカナダ人、とりわけバンクーバーから来ている人が多かったのですが、アメリカ人も多くいました。逆に考えると、カナダからアメリカのカンファレンスなどのイベントに参加することもそのくらい気軽なのだろうと感じます。(日本からアメリカのイベントに参加しようと思うとウン十万円と費用がかかるので、この点においては羨ましい限りです。)

Mr. Brad Frost と Atomic Design

今回のカンファレンスで一番楽しみにしていたのは、Brad Frost 氏の Full-day ワークショップを受講することでした。Brad Frost 氏は Responsive Design のパターンやニュースなどを集めたサイト This Is Responsive を公開し、Adaptive Design に関する実用的で深い見識を持っています。

私がこのワークショップを楽しみにしていた理由は、彼がレスポンシブな Web をコンポーネントベースでデザインする方法論として、Atomic Design というものを提唱していて、このワークショップはその考えを学ぶことができる場だったからです。

ここ1年、私は2つのプロジェクトでフロントエンドの開発をしました。どちらのプロジェクトでもコンポーネントベースでの UI 開発を意識してきましたが、コンポーネントをどんな粒度で作るのが1番再利用性が高くなるのか、なかなか明確な基準をチームの中で共有することができないでいました。彼のワークショップを通して、コンポーネント開発に対する考え方を磨きたいと思いました。

ここからは、そのワークショップおよび、カンファレンスの1セッションでもあった Atomic Design について書きたいと思います。

Atomic Design とは

Atomic Design はデザインシステムを作るための1つの手段です。ざっくり言うと UI コンポーネントを粒度に応じたカテゴリーに明確に分ける手法です。Atomic Design では下記のようなカテゴライズのレベルが示されています。

  1. Atoms - 原子
  2. Molecules - 分子
  3. Organisms - 有機体
  4. Templates - テンプレート
  5. Pages - ページ

これらのカテゴリーは上から下に行くにつれて、粒度は大きくなり、抽象度は下がっていきます。下位カテゴリーのコンポーネント(例えば原子)を組み合わせて上位のコンポーネント(分子)を構成するようにデザインを考えていきます。

We’re not designing pages, we’re designing systems of components.

まさに Stephen Hay 氏の上記の言葉にあるように、ページをデザインするのではなく、コンポーネントで構成するデザインシステムです。

特徴的な名称

私が Atomic Design が手段としてとても優れていると思う点は、カテゴリーの名称がコミュニケーション・ツールとしても柔軟なところです。

5つのカテゴリーのうち、原子、分子、有機体に関しては、開発サイドにメリットがあるカテゴリー名です。この3つの名称は、およそ Web デザインには関係が無さそうな名前です。しかし、原子と原子が結合して分子になることから連想すると、コンポーネントを組み合わせて新しいコンポーネントを作り出すという特性をとても正確に表しています。

それに対してテンプレートとページという2つの名称はとても一般的です。前者3つのコンポーネントより具体性が増して、このレベルではクライアントやプロデューサーに見せるアウトプットになるので、彼らと会話するときに余計な違和感を与えることなくコミュニケーションできる名称になっています。

Pattern Lab

5つのカテゴリーを1つずつ説明する前に、Atomic Design を実践するために便利なツールである Pattern Lab を紹介します。Pattern Lab は Brad Frost 氏と Dave Olsen 氏の両名が作ったパターンライブラリかつ静的サイト・ジェネレータです。

パターンライブラリとしては、原子や分子、有機体といったコンポーネントパターンをコードベースで管理することができます。また静的サイト・ジェネレータとしては、ページやテンプレートと言ったサイトレベルのデザインを原子、分子、有機体から生成します。

Pattern Lab は Atomic Design のパターンを作るスターターキットとしてとても優れているので、ここからの5つのカテゴリの紹介は Pattern Lab で説明していきたいと思います。

Atoms - 原子

原子という名前の通り、これ以上分割することができない基本的な要素がこのカテゴリに含まれます。これ以上分割することができない要素なので、ラベルやインプット要素、ボタンなどの HTML タグは原子としてカテゴライズされます。カラーパレットやフォント、アニメーションなどもこれ以上分割することができない要素として原子に含まれます。

1
2
<!-- 見出し -->
<h1>Heading Level 1</h1>

1
2
<!-- ボタン -->
<p><a href="#" class="btn">Button</a></p>

button

原子にカテゴライズされる要素は、基本的に単体では UI として意味を成さないものばかりになりますが、この後に紹介していくカテゴリのコンポーネントも、ここの要素にあてられたスタイルがベースとなっていきます。この要素たちを集めて一覧化すれば、Dan Mall 氏の ELEMENT COLLAGES のような感じで、サイト全体のトンマナを要素レベルで俯瞰できるはずです。

Molecules - 分子

分子もその名の通り、原子がくっついてできる最小の単位です。ラベルやインプット要素、ボタンなどのコンポーネントを合わせて、例えば検索フォームという形にすることができます。原子は単体ではあまり意味をなさないものでしたが、組み合わせることで目的を持ったコンポーネントになることができます。

ただ、このコンポーネントが持っている目的はまだ汎用的なもので、コンポーネントとしての再利用性が5つのカテゴリの中で最も高くなるようにデザインしていく必要があります。これが Atomic Design システムの根幹を担います。この分子をいろいろな UI パーツに上手に組み込むことが Atomic Design のコツとなるように思います。

Pattern Lab では、このように既存で作った原子を組み合わせて分子としてのコンポーネントを作れるように、別のコンポーネントをインクルードすることができるようになっています。

1
2
3
4
5
6
7
8
9
10
11
<div class="block block-thumb">
<a href="{{ url }}" class="b-inner">
<div class="b-thumb">
{{> atoms-square }}
</div>
<div class="b-text">
<h2 class="b-title">{{ headline.short }}</h2>
<p>{{ excerpt.medium }}</p>
</div>
</a>
</div>

molecule

Pattern Lab はテンプレートエンジンとして Mustache を使っており、{{> そのコンポーネントの名前}} という記述でほかのコンポーネントをインクルードすることができます。上記のコードでは、原子のコンポーネントである atoms-square をインクルードしています。原子から分子というように小さいコンポーネントを使って大きいコンポーネントを作っていくことが簡単にできます。

Organisms - 有機体

有機体は原子や分子を組み合わせて、さらに複雑なコンポーネントを構成します。このレベルでは、インターフェースのパーツとして人が意味のある名前を付けるコンポーネントとなります。例えば、ヘッダーやフッター、記事一覧などのパーツが有機体に含まれます。

1
2
3
4
5
6
7
<header class="header cf" role="banner">
{{> atoms-logo }}
<a href="#search-form" class="nav-toggle nav-toggle-search icon-search"><span class="is-vishidden">Search</span></a>
<a href="#nav" class="nav-toggle nav-toggle-menu icon-menu"><span class="is-vishidden">Menu</span></a>
{{> molecules-primary-nav }}
{{> molecules-search }}
</header>

header

Templates - テンプレート

テンプレートはその名前の通りで、具体的なコンテンツを持っていないページのテンプレートです。ヘッダーや記事一覧などの有機体、ページネーションなどの分子をレイアウトして構成します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div class="page" id="page">
{{> organisms-header }}
<div role="main">
<h1 class="section-title">Our Outdoor Blog</h1>
<div class="l-two-col">
<div class="l-main">
{{> organisms-latest-posts }}
{{> molecules-pagination }}
</div><!--end l-main-->
<div class="l-sidebar">
{{> organisms-recent-tweets }}
</div><!--end l-sidebar-->
</div><!--end two-col-->
</div><!--End role=main-->
{{> organisms-footer }}
</div>

Pages - ページ

最後がページです。テンプレートに画像やテキストなどの具体的なコンテンツが流し込まれて完成します。

1
{{> templates-homepage }}

Pattern Lab では、コンテンツは JSON によってテンプレートに適用され、ページとなります。ここではここまで作ってきたコンポーネント現実のコンテンツに耐え得るかを確認します。サンプルテキストやアテの画像ではないコンテンツを流し込んだときにレイアウトが崩れることなく、コンポーネントとして成り立つことができているかをテストするカテゴリーでもあります。

デザインシステムの運用におけるパターンライブラリの重要性

私も過去のプロジェクトで、デザインシステムらしきものを自分で考えて、そのシステムに従ってコンポーネントを管理していました。いわゆるオレオレデザインシステムです。そのデザインシステムで作ったコンポーネントの粒度が適切だったかどうかはさておき、ディベロッパー間で共通言語として使うところまでは上手くいったように思いましたが、コンポーネントを組み合わせて新しいコンポーネントを作成していくため、粒度の小さいコンポーネントに修正が入ったときの影響範囲が分かりづらくメンテナンス性にずっと課題を感じていました。

コンポーネントベースのデザインシステムを運用で実践していくにあたっては、コードのメンテナンス性を保つために最低限下記の機能を持ったパターンライブラリが必要だと思います。

  • 粒度の小さいコンポーネントをインクルードしてより大きなコンポーネントを構成する機能
  • インクルードされる側の粒度が小さいコンポーネントがどこで使用されているかを把握できる機能

「粒度の小さいコンポーネントをインクルードしてより大きなコンポーネントを構成する機能」に関しては、細かいコンポーネントたちを1ソースにまとめるために必要です。やはりコンポーネントに修正を入れるときは1ヶ所だけ修正すれば良い状態が理想です。

「インクルードされる側の粒度が小さいコンポーネントがどこで使用されているかを把握できる機能」に関しては、コンポーネントを削除する時に便利です。長く開発・運用を続けていると、定期的にコードの大掃除する必要に迫られると思いますが、そのコンポーネントを削除してもほかのコンポーネントに影響が出ないか確認できれば不必要なコンポーネントの掃除が容易になります。

Atomic Design 実践用のサポートツールである Pattern Lab は、「粒度の小さいコンポーネントをインクルードしてより大きなコンポーネントを構成する機能」を Mastache を採用することにより実現し、「インクルードされる側の粒度が小さいコンポーネントがどこで使用されているかを把握できる機能」を Lineage という機能で実現しています。

コンポーネントベースでのデザインを実践するときは、どうしてもそのデザインシステムに合ったコンポーネント管理機能を持つツールが必要になります。デザイナー・ディベロッパーは自分のプロジェクトに合ったデザインシステムを採用する必要がありますが、そのデザインシステムをサポートするツールがあるとは限りません。むしろ、ないケースの方が多いでしょう。Atomic Design の場合は、そのデザインシステムに特化した Pattern Lab というツールがすでに用意されていることが、これを採用する強い理由になるように思います。

AngularJS の Controller / Service / Directive / Filter 役割のポイント

会社内で AngularJS の Working Group を作り活動している中で、よく上がる質問の1つが AngularJS における Controller / Service / Directive / Filter に書く処理をどう分けたらいいのか、でした。

本記事では、Controller / Service / Directive / Filter の役割のポイントを整理したいと思います。

基本ポイント

他の MVC デザインパターンと同様、ビジネスロジックとプレゼンテーションロジックを分離することが基本です。

ビジネスロジックを Service が担当し、Controller でそれを紐付けてテンプレートに共有します。プレゼンテーションロジックは Directive と Filter が担当し、DOM 操作処理やデータ整形処理をテンプレートに共有します。

Controller

Controller ではビューで表示するデータとユーザーアクションに対するメソッドを定義します。

AngularJS では $scope オブジェクトを介してデータやメソッドをテンプレートで共有することができます。Controller には、ビジネスロジックを $scope オブジェクトに書き込んでいき、テンプレートでは $scope オブジェクトで共有されたデータとメソッドを参照します。

共有される $scope オブジェクトがカオスな状態になるのを避けるために、Controller では $scope オブジェクトを書き込み専用として、テンプレートでは読み取り専用として扱うと良いです。

また、直接 DOM を参照することなどは行わなないようにします。DOM 操作処理が Controller に入ってしまうと、デザイン変更などでテンプレートの HTML に変更を行わなければいけない場合に、Controller も書き変える必要が出てくる可能性があります。

そのため、DOM 操作処理は Controller 内では極力行わなず、Directive にその役目を渡しましょう。

ビジネスロジックとプレゼンテーションロジックを Service、Directive、Filter に分離して Controller を簡潔に保つことができると理想的です。

Service

ビジネスロジック担当です。ビューに依存しない処理を記述します。

また、各 Service はシングルトンとして存在するため、異なる Controller や Directive 間で共有するモデルとして使用できます。

Directive

HTML を拡張する機能です。前述の DOM 操作が必要になる場合を含み、プレゼンテーションロジックを記述します。

自身で Controller を持ち、単一で完結するコンポーネントを作ることもできますし、属する scope で公開されているデータと振舞いをテンプレートに紐付ける役割を担います。

Filter

データを整形する処理を記述します。

Directive と同様にプレゼンテーションロジックを記述しますが、Directive と違い、直接 Scope にアクセスすることはできません。モデルを変更することなく表示フォーマットのみを変更します。

サンプル

ここでは、フォームから ユーザーデータを追加する処理を例に説明します。

この例では、FormCtrl という Controller、noHyphen という Directive、User という Service を組み合わせて実装しています。

まずモジュールを宣言します。

1
var app = angular.module('app', []);

テンプレート

1
2
3
4
5
6
7
<body ng-app="app">
<form name="registrationForm" ng-controller="FormCtrl" novalidate>
<input type="text" ng-model="user.nickname" required no-hyphen />
<button type="submit" name="nickname" ng-click="submit()"
ng-disabled="registrationForm.$invalid">送信</button>
</form>
</body>

このようなテンプレート用意した場合、各々の役割は以降のようになります。

Controller に書く実装

1
2
3
4
5
6
7
8
9
10
11
12
13
app.controller('FormCtrl', function ($scope, $log, User) {
$scope.submit = function () {
User.addUser($scope.user)
.then(
function (resource) {
$log.log(resource);
},
function (err) {
$log.warn(err);
}
);
};
});

ここでのポイントは $scope オブジェクトの設定だけを記述している点です。DOM にイベントハンドラを紐付ける $('button').on('click', function () { ... }) などの処理はビルトインの Directive である ngClick に任せてあります。

また、バリデーション機能は、特定の DOM の値を取得する必要があるため、Controller 内には記述しません。後述する noHyphen という Directive を実装して機能を実現します。

このフォームは送信ボタンを押された時に Ajax 処理も実行しますが、その処理も後述する User という Service に実装を切り分けています。

Directive に書く実装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
app.directive('noHyphen', function () {
return {
require: 'ngModel',
link: function (iScope, iElem, iAttr, ngModelCtrl) {
ngModelCtrl.$parsers.push(function (viewVal) {
var _isValid = true;
if (~viewVal.indexOf('-')) {
_isValid = false;
}
ngModelCtrl.$setValidity('noHyphen', _isValid)
});
}
};
});

ここでのポイントは、ngModel を介して DOM 操作をしている点です。Controller で必要だった DOM にイベントハンドラを紐付ける処理は Directive に記述します。

処理した結果を ngModel ディレクティブを介して FormCtrl$scope に渡しています。

Service に書く実装

今回は、factory メソッドを使用します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
app.factory('User', function ($http) {
var _onSuccess = function (res) {
return res.data;
},
_onError = function (res) {
return $q.reject('an error occured.');
},
_addUser = function (user) {
var request = $http({
method: 'post',
url: '/api/something',
params: {
action: 'add'
},
data: {
nickname: user.nickname
}
});
return request.then(_onSuccess, _onError);
};
return {
addUser: _addUser
};
});

ここでのポイントは、ビューに依存しない処理のみを記述している点です。

ここでは、新規ユーザーデータの送信に使われる Ajax 処理を実装しているので、FormCtrl はこの User Service をインジェクトすることで自身の $scope オブジェクトに持っているデータを送信することができます。

特有の概念が多いため AngularJS での役割の分担は分かりづらいですが、自分は上記のように処理を分けるようにしています。

2012 jQuery→Early 2013 Backbone→Late 2013 AngularJSな自分がハマった10のこと

こちらはFrontrend Advent Calendar 2013 22日目の記事です。

本記事は、2012年までjQueryだけで開発していたフロントエンドエンジニアの自分が、Frontrendな方々に影響を受けたのをきっかけに、とあるコミュニティ系WebサービスでAngularJSを導入するまでの過程で影響された記事やイベントや人を時系列で紹介すると共に導入後AngularJSの開発でハマった10のことを紹介します。

2012.07:Anatomy of Backbone.jsで学ぶ

AngularJSと言えば、MVC的なアーキテクチャをJavaScript開発に取り入れるためのフレームワークで現在は業務でも使用させていただいています。

しかし、振り返ってみると、2012年はMVなんちゃらなフレームワーク等には、全く無縁の生活を送っていました。

そんな2012年の7月頃に、FrontrendのYuya SaitoさんにCode SchoolAnatomy of Backbone.jsを紹介していただいて、BackboneをはじめとするJS開発におけるMVC的な発想を知ります。

ご存知の方も多いと思いますが、Code Schoolはステップごとに用意されている講義形式の動画で学び、そのまま出題される課題をブラウザ上のエディタでコードを書いて解答して、実践的にプログラミングを学ぶことができるサービスです。

Anatomy of Backbone.jsを受講した印象としては、とても初心者が学習しやすいチュートリアルです。JSライブラリと言えばjQueryしか使ったことがなかった自分はここでBackboneの基本の基本を学べたと思います。

2013.02:Frontrend Vol.4でBackbone導入決意

基本のチュートリアルをこなしたとは言え、実際のプロジェクト(リリース済)で使うことに敷居の高さとリスクを感じ、なかなか導入することもできないまま2013年になってしまいました。

しかし、2月9日に開催されたFrontrend Vol.4でのahomuさんセッション「jQuery to Backbone – アーキテクチャを意識したJavaScript入門」で、まさに「自分みたいにjQueryくらいしかライブラリ触ったことない人でもBackboneとか使えるかもー」と思い始めます。セッションの内容を参考にさっそく自分がフロントエンドを担当しているWebサービスでBackboneの導入を始めました。

このセッションは、当時のJS開発におけるjQueryが解決しない問題とBackboneを導入することで得られるメリットが分かりやすく説明されている上に、jQueryベースで記述されたコードをBackboneの構造に徐々に移していく具体的なコーディング手順まで紹介されています。自分はまさにその手順に従って、プロジェクトにBackboneを導入できたように思います。

2013.09:Backbone Is Not EnoughでEmberJSとAngularJSが気になる

Backboneを使い始めて半年くらい経ち、自分が担当しているサービスでは巡るめくアップデートとプロモーション施策の実装をしていかなければいけませんでした。そしてフロントエンドエンジニアが自分だけ、という状況だったこともあり「何か劇的に開発を高速化できる方法はないかな」と日々模索していました。

そして、Shine Technologiesのブログ記事「Backbone Is Not Enough」を読み、AngularJSに興味を持ち始めます。

記事では、Backboneでの大規模なSPA開発において、ネストされるViewをうまく構成する難しさ、Viewをテストする難しさ、メモリ管理の難しさ、容易に遅くなってしまうレンダリングへの対策やデータバインディングの重要さなどに関してEmberJSやAngularJSと比較して書かれていますが、中でも「Backboneと比較したEmberJSとAngularJSのコード記述量がかなり少ない」という1点に惹かれて、EmberJSとAngularJSが気になり始めます。

2013.10:A comparison of the two-way binding in AngularJS, EmberJS and KnockoutJSでAngularJSが一番良いような気がしてくる

EmberJSとAngularJSが気になり始めましたが、これらのフレームワークはそれぞれ何が違うのかは正直よく分からないでいました。Backbone単体では自分でこつこつ設定するデータバインディングを簡単に設定できる点に関しては、EmberJSもAngularJSも同じです。

その疑問に関しては、2013年10月に公開されたJSConf EU 2013(9月開催)のMarius Gundersen氏セッションの動画「A comparison of the two-way binding in AngularJS, EmberJS and KnockoutJS」で解決されます。

このセッションでは、AngularJS、EmberJS、KnockoutJSの双方向データバインディングにおける挙動の違いが分かりやすくまとめられています。

このセッションにおいてAngularJSは、Dirty Checkingという仕組みと非同期でデータをバインドをしているため、

  • リストの単純なレンダリングに関しては速い
  • Modelが複雑で巨大になってくるとレンダリングが遅い
  • コンピューティング処理が挟まれるプロパティに関しては重くなる

などの特徴が説明されています。

担当サービスにおいて、Modelはそこまで複雑かつ巨大にならないと思った点と単純なレンダリングの速さが気に入り、AngularJSの導入を決めました。

2013.10:A Better Way to Learn AngularJSで学ぶ

導入を決めたら、次はAngularJSについて学習しなければいけません。

Anatomy of Backbone.js」のような効率が良い(そしてできれば無料の)ラーニングリソースを探していたら(残念ながら、Code SchoolにAngularJSのコースはありませんでした。)、「A Better Way to Learn AngularJS」というラーニングリソースを見つけました。

Code Schoolのようにコーディングで課題を問いていくリッチな機能は無いですが、初心者にも分かりやすく説明されたAngularJSのチュートリアルを動画で見ることができます。無料で学べるのですが、これを一通りこなすだけでAngularJSの基本的な使い方に関しては網羅できるように思います。

2013.10:AngularJSで開発を始めていろいろとハマる

もちろん基本的な使い方しか学んでいない自分は、AngularJSでサービスの開発を始めると、いろんなところでハマりました。Backboneを利用していた時と比べ、確かにコード記述量は減りましたが、AngularJSについて調べている時間は増えました。

そんなわけで「Backbone Is Not Enough」に載っていたAngularJSのラーニングカーブをしみじみ実感しましたが、同時にノウハウが溜まった後は劇的に楽になるはず、という期待でいっぱいでした。

AngularJSで開発を始めてハマった10のこと

本記事のタイトルにある通り、ここからはAngularJSでの開発で最初の頃に自分がハマった点を回避策とともにリストしていきたいと思います。

1. JSファイルをminifyしたら動かなくなった

AngularJSでは、Controllerの書き方にパターンがいくつか存在しますが、そのパターンのうちminifyするとJSが正しく動作しなくなるものがあります。

AngularJSにはDI (Dependency Injection)と呼ばれる仕組みがあります。Controllerで使用するserviceを指定する際に、functionの引数に指定された変数名から自動的に必要なserviceを決定できるすごい機能を持っているために、起こってしまうのがこの問題です。

回避策は2点。

  • 引数にわたすserviceの名前を文字列で明示する
  • ngminを使用する

1点目の回避策は、引数にわたすserviceの名前を文字列で明示することです。

minifyすると動かない書き方(functionの引数だけでserviceを指定)

1
2
3
4
angular.module('myApp', [])
.controller('MyCtrl', function ($scope, $http) {
// ...
});

minifyしても動く書き方(functionの引数の前に文字列でserviceを指定)

1
2
3
4
angular.module('myApp', [])
.controller('MyCtrl', ['$scope', '$http', function ($scope, $http) {
// ...
}]);

2点目の回避策は、使用するpre-minifierをngminにすることです。

こちらだと動かない方の書き方をしても、ngminが動く方の書き方に変えてminifyしてくれます。これでキーの打数は減り、楽して開発できるでしょう。

2. RequireJSなどで遅延ロードするとAngularJSが正しく動かない

Backboneを使っているときは、モジュールごとに処理を分けてRequireJSで遅延ロードさせたりしていましたが、AngularJSで同様のことをしようと思うとモジュールのロードが完了する前にAngularの起動が行われて、意図した挙動をしなくなることがあります。

回避策は、手動で起動することです。

1
2
3
4
5
6
require(['require', 'exports', 'app'], function (require, angular) {
// 起動前のいろんな処理...
// 手動で起動
angular.bootstrap(document, ['myApp']);
});

3. AngularJS起動前だとng-hideなどで隠れるはずの要素が一瞬表示されてしまう

SPAで作っている場合には問題ないかもしれませんが、そうでない場合、先述した手動での初期化などを行うとAngularJSの起動が遅くなり、ページロード時にngHideディレクティブなどを指定しているDOMが一瞬表示されてしまうことがあります。

angular.cssを読み込んでおき、対象の要素のclass属性にng-hideを足しておくことで回避できます。

ngHideなどの処理は、内容的には**display:none;**を適用するために、ng-hideというクラスを要素に付けているだけです。

1
2
3
.ng-hide {
display: none !important;
}

angular.cssを読み込まなくても、上記のようなcssが入っていれば大丈夫です。

4. AngularJS起動前にが一瞬表示されてしまう

上記と同様に、AngularJS起動前の評価されていないexpressionも生のテキストとしてページロード時に一瞬表示されてしまいます。

1
<p>{{comment}}</p>

このような場合は、

1
<p ng-bind="comment"></p>

で回避することができます。 もしくは、

1
<p ng-cloak></p>

でも可能ですが、ngCloakを使用する場合はngHide同様angular.cssを読み込んでおく必要があります。

この問題を回避するという用途的には、ngCloakを使う方が正しいようです。

5. textarea要素にデフォルト値が設定できない

textarea要素にデフォルト値を設定したいと思い、

1
<textarea ng-model="comment">コメント</textarea>

上のように設定してもテキストエリア内にもcomment Modelにも反映されません。

回避策は、ng-initで明示的にcomment Modelをそのデフォルト値で初期化することです。

1
<textarea ng-init="comment = ‘コメント’" ng-model="comment"></textarea>

6. 表示テキストが2重エスケープされる

AngularJSでHTMLにバインドすると自動的にエスケープがかかってしまいます。サーバサイドでエスケープ処理を施している場合は、場合によっては2重エスケープ状態が発生してしまいます。

そんなときは、ngSanitizeをインストールして、

1
angular.module('myApp', ['ngSanitize']);

DOMには

1
<span>{{item.content}}</span>

と書く代わりに

1
<span ng-bind-html="item.content"></span>

のようにしてエスケープ処理をしないようにできます。

7. Directiveの命名規則がややこしい

ここからは、AngularJSで一番素敵な機能だと思っているDirectiveについてのネタが続きます。

1
<x-switch></x-switch>

このようなElement DirectiveをJS側で定義したいとき、

1
2
3
4
angular.module('myApp', [])
.directive('xSwitch', function () {
// ...
});

のように、ハイフンつなぎ(x-switch)→キャメルケース(xSwitch)とする必要があります。

8. カスタムDirectiveの内側のコンテンツが消える

当然と言えば当然なのですが、テンプレートを指定したDirective DOMの内側にあらかじめコンテンツを入れておいてもテンプレートに置き変えられてしまいます。最初はこれに気づきませんでした。

Directiveの内側に入れたコンテンツをテンプレート内の特定の場所で利用したい場合は、JS側のDirective定義でtranscludeをtrueに設定し、template側のコンテンツを利用したい要素にng-transclude属性を設定する。

1
2
3
4
5
6
7
8
9
10
angular.module('myApp', [])
.directive('someDirective', function () {
return {
transclude: true,
template: '<div class="well"><p ng-transclude></p></div>',
link: function() {
// …
}
};
})

9. カスタムDirectiveをたくさん作っていたらtemplate用HTMLファイルのリクエストでいっぱいになる

カスタムDirectiveはとても便利で、HTMLを綺麗にしておけるし、処理も独立させられるのでついついたくさん作ってしまいます。そのときに使用するテンプレートHTMLを外部においてtemplateUrlで読み込ませると、そのテンプレートの数分だけリクエストが別途走ってしまいます。

回避策は、外部テンプレートHTMLをtemplateCacheを使用してJSにキャッシュしてくれるgrunt-angular-templatesを使用することです。

こちらに関しては、別記事参照。

10. AngularJS Batarangの存在を知るのが遅かった

これは、ハマったことでも何でもないですが、AngularJSのデバッグをする際にDev ToolsとDOM自体にプロパティ表示用のコードを書いたりしていました。

AngularJSのデバッグ用Chrome Extensionに「Angular Batarang」というものがあります。これの存在をもっと早く知っていたらデバッグがもっと楽だっただろうと思います。

BackboneにもFirebug Extensionの「Backbone-Eye」などがありますし、やはり専用のデバッグツールがあると開発も快適です。

2013.10:ブログを始める(余談)

開発にハマっては調べハマっては調べしている日々の中、FrontrendのHiroki Taniさんに何気なく「ブログとか書いてみたらどうですか」と言われたのをきっかけに、どうせならAngularJSで調べたことでも書いてみようと思い、このブログを始めました。あまり継続して書けていないので、来年はもっと頑張ろうと思います。

終わりに

Frontrend Advent Calendar 2013という場を借りて、とても個人的な振り返りをさせていただきました。今日この記事を書けるのも、先に書いた通りFrontrendの方々にいただいたきっかけが大きく影響しています。

明日は、ysugimoto さんです。