🅰️

AngularJS モダンプラクティス

2021/07/04に公開

こんにちは、@armorik83 です。私の AngularJS 歴は 2 年弱で、これまで AngularJS に関する記事は Qiita にたくさん書いてきました。例えば次のような記事です。

他にもニッチなものやイマイチだったものも含めてけっこうな数となってきました。また、こういった記事の縁で勉強会でも登壇させて頂きました。

たくさん書いてくると、古い記事は粗が目立ち、そして世界の技術からみても鮮度が落ちていくため、どうしても有用性が失われていきます。そこで今回改めてこれまでのけじめとして、そして控えている Angular 2 を捉えながら 2015 年に AngularJS 1.x をどう書いていくべきか、まとめていきます。

おことわり: 客観的に見てベストプラクティスと呼ぶには個人的な好みが含まれすぎているため、モダンプラクティスと称しています。以下の書き方や使い方は、AngularJS が公式に強制または推奨しているものではなく、個人的に Angular 2 を考慮して提案しているものです。ご了承ください。

【追記 2015/6/30】Angular 2 も刻々と成長しており、全容が明らかになっていくにつれ本稿と相容れない解釈も生まれてきます。本稿は筆者による初版公開時点での考えをまとめたものであり、いかなる時も全ての局面で有用だとは限らないことをご了承願います。

【追記 2016/2/8】1,000 ストックありがとうございます!このたび AngularJS 1.5 がリリースされました。モダンプラクティスを守っていたら 1.5 への移行はすぐです。今後リリースされる 2.0 も見越して、AngularJS 1 系も最新を保てるよう心がけましょう。@laco0416 氏のその使い方はもう古いかも?AngularJS 老化チェック(ディレクティブ篇)で、あなたのプロジェクトでの書き方がどれくらい古いか診断できるようです。

執筆時点: 本稿は 2015 年 5 月に執筆しています。

  • AngularJS: 1.3.15、1.4 に言及する時は1.4.0-rc.2
  • Angular 2: 2.0.0-alpha.24
  • Babel: 最新版
  • TypeScript: 1.5.0-beta

§0 イントロダクション

現状の何に問題があるのか

ヒトコトで言うと、Angular 2のリリースが控えている、これが障壁とされます。AngularJS は素敵なライブラリです。ただ出るのが少し早すぎたか、開発陣がこのユーザ規模を想定していなかったか、多くの粗を探され多くの後発と比較されました。Angular 2 はここに挑戦しており改革に大きな痛みを伴っています。

「AngularJS は Angular 2 と互換性がほとんど無いんだろ?」
「今から始めるなら React を…」
「jQuery の方が知ってるし簡単にできる」

有り得る意見です。でもちょっとまって、AngularJS に対して誤解がありませんか。

対象読者

この記事は、既に AngularJS をたくさん書いている方々、ちょっと始めてみたけど今のうちに止めた方がいい?と不安になってる方々など、幅広く現在の AngularJS ユーザに向けて書いています。ただ、申し訳ありませんが全く使ったことのない方に対する第一歩の手引きとはなっておりません、ご了承ください。

以下の章からは、AngularJS をどう書いていくべきかを具体的に述べます。幅広い力量に向けて対象にしているため、どうしても長めになっております。序文で自分は知っている話題だと感じたら次々と読み飛ばしてもらって構いません。

オススメ度

各節には、習熟度や利便性などから筆者が独断で表現したオススメ度を表示していますので、ご参考にしてください。

  • ★★★: AngularJS 習熟度に関わらずお勧めしたい内容
  • ★★☆: AngularJS に対して多少の経験があり、困難とも遭遇している方向け
  • ★☆☆: AngularJS をより使いこなし、最新の JavaScript の潮流も勘案しておきたい方向け
  • ☆☆☆: 筆者による挑戦的で実験的な内容、お勧めはしませんが参考までに

§1 Class 構文

ES6 が近づいている

ES6 をご存知でしょうか。ES6 の ES とはECMAScriptの略称で、これは我々が JavaScript と呼んでいる言語の正式な標準規格のことです。JavaScript といっても実際にはブラウザ毎に差異があるため、標準化が必要となりました。

ES6 は簡単に言うと JavaScript の次世代規格ということです。

現在の最新ブラウザは全て ES のバージョン 5 をサポートしていますが、ES6 の仕様を全て満たすブラウザはまだ現れていません

ES6 は、ES5 と比べて簡便に記述するための新構文が多く含まれているほか、新しい API も複数追加されています。Angular 2 は TypeScript での使用を前提に開発されており(従わずに ES5 や Coffee で書くこともできます)ES6 についての知識もぜひ知っておくべきです。

オススメ度: ★★★

TypeScript と ES6 の関係

TypeScriptとはプログラミング言語のひとつで、バージョンは執筆時点で 1.5.0-beta[1]です。TypeScript は JavaScript のスーパーセット言語として策定されており、早くから「ES6 の構文で書いてブラウザで動作する JavaScript を生成する」というアプローチを取っていました。Angular 2 はこのTypeScript で開発され、ユーザが Angular 2 を用いる際にも TypeScript で書けるよう設計されています。

TypeScript は ES6 の構文を積極的に採用しているため、ES6 の知識と TypeScript の知識は重複する部分が多いです。そしてもちろん最大の特徴はコンパイラによる静的型検証です。

オススメ度: ★★★

Babel

TypeScript を使う方法以外に、ES6 構文を含む JavaScript を ES5 で動くように変換するトランスパイラというものがあります。そのうちのひとつのBabelは開発がとても盛んで、公式サイト内で気軽に試すこともできる筆者オススメのトランスパイラです。実際の開発ではgulpなどのタスクランナーで Babel の変換処理を自動化して利用します。

このほかに、traceur-compilerというものもあります。

オススメ度: ★★☆

Class 構文の例

AngularJS + Class 構文

次の例は、実際に業務で AngularJS を用いて書いたコードを見本用に整形したものです。Class 構文は ES6 からの大きな特徴で、不慣れな方には別言語に見えるかもしれません。

controller.js
export class Controller {
  constructor($rootScope, $scope, $timeout) {
    this.subscribe();
  }

  subscribe() {
    this.disposer = this.disposer || {};
    this.disposer.store    = Store   .addListener(this.storeChangeHandler.bind(this));
    this.disposer.renderer = Renderer.addListener(this.rendererChangeHandler.bind(this));
  }

  // snip
}

Angular 2 での Class 構文の使い方

次の例は Angular 2 チュートリアルの断片です。公式のチュートリアルが最初から Class 構文の使用を想定して紹介しているのです。

angular2-example.js
// snip
class MyAppComponent {
  constructor() {
    this.name = 'Alice';
  }
}

bootstrap(MyAppComponent);

@を使ったDecorators 構文を省略していることに気付いた読者もおられるでしょうが、今回は触れません。Angular 2 Annotationsは alpha 版で絶賛開発中なため、また日を改めて紹介します。

AngularJS で Class 構文を導入する

一番ダメな例

var mainCtrl = function ($scope) {
  $scope.user = "John";
};
<div ng-controller="mainCtrl">
  <p>Hello, {{user}}!</p>
</div>

変数mainCtrlに対して無名関数を与えて Controller を定義する例です。最もやってはいけない書き方。AngularJS 1.3 以降はそもそも禁止されたので、さすがにこのコードで運用することはありません。しかしこう書くチュートリアルを見て AngularJS を始められる方が今日も残っている以上、ここに記しておきます。当時このようなチュートリアルを許してしまった開発陣はやや残念です。

$scope は使わない

前項を改善したのが次の例です。よく見る感じになりました。

angular.module("myApp", []);
angular.module("myApp").controller("mainCtrl", function ($scope) {
  $scope.user = "John";
});
<div ng-controller="mainCtrl">
  <p>Hello, {{user}}!</p>
</div>

まだ改善箇所があります。minify 対策として['$scope', function($scope){...}]にしたほうがいい…それも正しいのですが、ここでは『$scope自体を使わないこと』がより良い実践でしょう。

理由は 2 つあります。ひとつは AngularJS 1.x で Class 構文を取り入れていくならば、$scopeを使わないほうが身軽だから。もうひとつは、Angular 2 では$scopeが廃止されるからです。AngularJS 1.3 以降を使うならば$scopeを使わない選択は十分にメジャー、Angular 2 も視野に入れるとそろそろ不安材料ですよ。

ご心配なく、$scope.$watch$scope.$broadcastについては§5 で扱います。

function を独立させる

前項から$scopeを外したものが次の例です。

angular.module("myApp", []);
angular.module("myApp").controller("mainCtrl", function () {
  this.user = "John";
});
<div ng-controller="mainCtrl as main">
  <p>Hello, {{main.user}}!</p>
</div>

$scopethisになりました。JavaScript のthisはややこしい…そんなことはない、この先には Class 構文が待っています!

HTML 側では、ng-controller"mainCtrl as main"となりました。asコントローラ名 as コントローラを格納する変数名という AngularJS の独自構文です。controllerAsという API も今後出てくるので、あわせて覚えておきましょう。

ここから次の改善として、Class 構文を導入しやすくするためfunction(){}を外に出します。

Class 構文を導入する準備が整った

function mainCtrl()として外に切り出しました。

function mainCtrl() {
  this.user = "John";
}

angular.module("myApp", []);
angular.module("myApp").controller("mainCtrl", mainCtrl);
<div ng-controller="mainCtrl as main">
  <p>Hello, {{main.user}}!</p>
</div>

angularへの登録では.controller('mainCtrl', mainCtrl)と指定します。

Class 化

あとはclass構文を用いて一気にいきましょう。Class 名は慣習として大文字で始まるので、Controller 名を小文字から始めているならば、この際直してもよいでしょう。Ctrlは最近だとControllerと書く例をよく見かけます。Angular 2 の例だとFooComponentFooCmpと安定していません。(追記: 先日 Angular 2 開発者から直々に貰ったサンプルではComponentとなっていました)

非 Controller・非 Component と混同したくないため、個人的にはMainとせずMainControllerとします。ここは好みですので、チーム内規約などに沿って進めてください。

class MainController {
  constructor() {
    this.user = "John";
  }
}

angular.module("myApp", []);
angular.module("myApp").controller("MainController", MainController);
<div ng-controller="MainController as main">
  <p>Hello, {{main.user}}!</p>
</div>

実際に動作するサンプルです。

as以下は変数名なので、そのままにしています。DI アノテーション(minify 対策)は、次の節にて説明します。

いくつかのメソッドがある場合

複数のメソッドがあった場合にどう Class 構文を導入していくか、補足しておきます。

$scope にいくつかのメソッドがある例

todoCtrl.js
function TodoCtrl($scope, $routeParams, $filter, store) {
  $scope.newTodo = '';
  $scope.editedTodo = null;
  $scope.addTodo = function () {};
  $scope.editTodo = function (todo) {};
  $scope.saveEdits = function (todo, event) {};
}

TodoMVCを例に取り上げます。$scopefunctionがたくさん生えたよくある構造です。これを ES6 Class 化していきましょう。

メソッドとプロパティで分ける

まず$scopeのプロパティに格納されるものが関数型かそれ以外かで分けます。

todoCtrl.js
function TodoCtrl($scope, $routeParams, $filter, store) {
  $scope.newTodo = '';
  $scope.editedTodo = null;

  // 以下は関数型、Classのメソッドとなる
  $scope.addTodo = function () {};
  $scope.editTodo = function (todo) {};
  $scope.saveEdits = function (todo, event) {};
}

Class を用意する

次にclassを用意します。

todoCtrl.js
class TodoController {
  constructor($scope, $routeParams, $filter, store) { // <- ここに同じものを
    // それぞれ保持
    this.$scope = $scope;
    this.$routeParams = $routeParams;
    this.$filter = $filter;
    this.store = store;
  }
}

function TodoCtrl($scope, $routeParams, $filter, store) { // <- 使うService名はconstructorへ
  $scope.newTodo = '';
  $scope.editedTodo = null;

  $scope.addTodo = function () {};
  $scope.editTodo = function (todo) {};
  $scope.saveEdits = function (todo, event) {};
}

constructorの引数は DI する Service 名です。TypeScript の場合だと constructor の引数名の前にpublicなど[2]と書くことで this への格納を省略できます。ES6 ならば律儀に書く必要があります。

プロパティを constructor へ

todoCtrl.js
class TodoController {
  constructor($scope, $routeParams, $filter, store) {
    this.$scope = $scope;
    this.$routeParams = $routeParams;
    this.$filter = $filter;
    this.store = store;

    this.newTodo = '';      // +
    this.editedTodo = null; // +
  }
}

function TodoCtrl($scope, $routeParams, $filter, store) {
  $scope.addTodo = function () {};
  $scope.editTodo = function (todo) {};
  $scope.saveEdits = function (todo, event) {};
}

関数以外の$scopeプロパティは全てconstructorに移します。$scopethisに書き換えます。

TypeScript だと次のように書くことができます。型情報は省略していますので、本来は型も記述します。

todoCtrl.ts
class TodoController {
  newTodo = '';      // この辺がちょっと違う
  editedTodo = null;

  constructor(
    public $scope,
    public $routeParams, // publicと付けるとthis代入を省略できる
    public $filter,
    public store
  ) {
    // noop
  }
}

function TodoCtrl($scope, $routeParams, $filter, store) {
  $scope.addTodo = function () {};
  $scope.editTodo = function (todo) {};
  $scope.saveEdits = function (todo, event) {};
}

$scope 関数をメソッドへ

最後に$scopeの各関数を Class のメソッドとして書き換えます。この時、出てくる$scopethisに置き換えることを忘れずに。

todoCtrl.js
class TodoController {
  constructor($scope, $routeParams, $filter, store) {
    this.$scope = $scope;
    this.$routeParams = $routeParams;
    this.$filter = $filter;
    this.store = store;

    this.newTodo = '';
    this.editedTodo = null;
  }

  addTodo() {
    //
  }

  editTodo(todo) {
    //
  }

  saveEdits(todo, event) {
    //
  }
}

ひとつだけ例としてaddTodo()の中身を書いてみます。元ソースはこちら

todoCtrl.js
addTodo() {
  const newTodo = {
    title: this.newTodo.trim(),
    completed: false
  };

  if (!newTodo.title) {
    return;
  }

  this.saving = true;
  store
    .insert(newTodo)
    .then(() => {
      this.newTodo = '';
    })
    .finally(() => {
      this.saving = false;
    });
};

要注意ポイントとして、$scopethisに置き換えるならば、thisの束縛を揃えるためにアロー関数を使う必要があります。ES6 の新構文であるアロー関数は単なる糖衣構文ではなくthisの束縛を上のブロックと揃える大事な性質があります。ES6 でも TypeScript でも使え、なにより短くて済むので積極的に採用したい構文です。

変数宣言varは、筆者はlet#const#に積極的に置き換えています。この 2 つも ES6 から導入された新たな仕様です。

DI アノテーションを記述

よく言及される話題で、ユーザ間ではminify 対策とも呼ばれます。AngularJS では各 Service, Controller で使う別の Service を列挙する必要があり、これを欠かすと minify 変換時に正常に動作しなくなる問題が存在します。

DI アノテーションを自動で付与してくれるツールng-annotateを使ってもよいです。ただし Angular 2 では DI の仕組みが変わり機械的な記述がしにくい構造となったため、AngularJS 1.x でも手書きでよいと考えています。Angular 2 に関しては@Injectアノテーションなどを参照してください。

AngularJS における DI アノテーションは、自身のClass名.$injectで記述できます。

todoCtrl.js
class TodoController {
  constructor($scope, $routeParams, $filter, store) {
    this.$scope = $scope;
    this.$routeParams = $routeParams;
    this.$filter = $filter;
    this.store = store;

    this.newTodo = '';
    this.editedTodo = null;

    TodoController.$inject = ['$scope', '$routeParams', '$filter', 'store'];
  }

  addTodo() {
    //
  }
}

これで$scopefunctionがたくさん生えた Controller を Class 化できました。

§2 Directive 指向

本章では、前章で Class 構文を導入した Controller から Directive に切り替えていく方法について解説します。

Directive というと「AngularJS の中でも複雑で難しいやつ」とか「入れ子にすると混乱する」などの意見をよく目にしますが、恐れることはありません。これから挙げる API 以外は無視しても大丈夫です

そして重要なのは、Angular 2 は Component 指向で、Angular 2 Component ≒ AngularJS 1 Directive だということです。ng-controllerは Angular 2 では廃止されます

使う API

ここでいう API とは DDO - Directive Definition Objectのプロパティのことを指します。使わない API の方が多いです。

  • restrict: 基本的にEのみ、稀にA、あとは不要
  • templateUrl: HTML が 1 行程度と短ければtemplateプロパティを使ってもよい
  • scope: Isolate scopeを生成させるため常に{...}で記述する
  • controller: controllerAsも併記すること
  • bindToController: 1.4.0 以降ならばscopeより扱いの楽なこちらが便利

あとは覚える必要がないです。ここで、AngularJS の Directive をフル活用している方からは疑問が上がるかもしれません。

オススメ度: ★★★

Q. compile, linkを多用していますが不要ですか?

A. Angular 2 ではこのフック方法が変更されるので、依存しすぎないよう注意してください。compile, prelink, postlinkの移行方法は現時点(2015/5)では明示されていませんが、ソースを読む限り AngularJS と同等の API は提供されないことが分かっています。Angular 2 は DOM の直接書き換えを推奨していないので、こうなっているのかもしれません。

もしcompile内で jQuery を多用している場合、慎重に置き換える必要があります。jQuery との付き合い方は§4 にて後述します。

オススメ度: ★★☆

require はどうするの?

Q. requireで親 Directive Controller に依存しています。linkが廃止されると困りますが、どうすればいいですか?

A. Angular 2 では@Parent#@Ancestor#といった新しいアノテーションが採用されます。この API を活用することを念頭において、link内ではあまり複雑なことをせず、さっさと Controller に渡すのが賢明です。

筆者の例を掲載します。(TypeScript で実装していたので、例も TypeScript です)

https://github.com/likr/interactive-sem/blob/master/app/src/views/dialogs/add-latent-variable.ts#L83-L89

static link($scope: Scope, _: any, __: any, controllers: any) {
  // controllers引数にはrequire配列と同じ順序で格納されている
  var cwModal = controllers[0];
  var self    = controllers[1];

  $scope.dialog = cwModal.dialog;
  self.init();
}

このときはダイアログのプラグインを使っていたので、そのインスタンスを取得する必要がありました。

{
  controller: Controller,
  controllerAs: 'Controller',
  link: link,
  require: ['^cwModal', directiveName], // <- requireを配列にする
  restrict: 'E',
  scope: {
    locale: '&isemIoLocale'
  },
  templateUrl: app.viewsDir.dialogs + 'add-latent-variable.html'
}

このようにrequireArray<string>型の配列にすることで、複数の Controller を指定できます。この時自身の Directive 名(ここではdirectiveName変数に格納済)を与えると自身の Controller が取得できるのです。

Directive Controller の生成タイミングはlink実行なので、別途linkからinit()を呼んで初期化処理のタイミングをlink実行後にさせています。link関数内と Controller を繋ぐ手段が$scope (Isolate scope)なので、$scope.dialog = cwModal.dialog;として渡します。

オススメ度: ★☆☆

restrict は E のみ?

Q. restrict はA, C, E, Mの 4 種とそれらの組み合わせですが、E以外は使いませんか?

A. はい、基本的にはEしか使わないと覚えてください。restrict API は不必要に混乱を招いたと考えています。

そもそも各所で喧伝されている 4 種のうちMはドキュメントから省かれています(API としては現存)。そしてCの用途ですが、これは IE8 以前の非 HTML5 に対応するためのもので、現代では不必要となっています。

残るA, Eについては、基本的にはタグ名を Directive とするEを使えば間違いなく、より応用的に Directive を利用する場合のみ属性名を Directive とするAを考慮してもよいでしょう。AEという同時指定はプロジェクト内検索を困難にしチーム内にも混乱をもたらすため、使うべきではありません。

Angular 2 ではrestrictは廃止され@Component#@Directive#に分かれselectorプロパティで指定するため、明確となっています。

  • E: タグ名 -> Angular 2 では@Component
  • A: 属性名 -> Angular 2 では@Directive
  • AE: 使わない!
  • C: 使わない!

オススメ度: ★★★

replace は使ってもいいですか?

Q. Directive 自身をtemplateと置換してレンダリングするreplaceは使ってもいいですか?

A. DEPRECATEDです、使うべきではありません。

svg内では独自タグ名が使えないためreplaceを採用されることがあるかもしれません。廃止は次期メジャーバージョンとされていますが、今のうちから DEPRECATED だと意識しておくとよいでしょう。svg内で自作の Directive を適用したい際には、restrict: Aに設定した上で工夫してください。

オススメ度: ★☆☆

Isolate scope とは?

Q. Isolate scope とは何ですか? scopeの指定は@=&などの指示子があってややこしいです。

A. Isolate scope とは親子で Scope を共有しないDirective の独立した Scopeのことです。AngularJS 公式の提供が複雑でいまいち理解されていませんが、Isolate scope 以外を使うべきではありません。

scopeプロパティには、自身に無ければ親を参照しにいくという複雑極まりない仕様があり、これはグローバル変数が起こす副作用と混乱に等しい問題でした。筆者は「親子共有問題」と呼んでいます。これはcontrollerAsが普及しプロパティ元が明記されるにつれ緩和しましたが、根本的解決にはなりません。

この問題を解決するには Isolate scope(分離スコープ)を導入します。これはscope: {}のように Object リテラルを与えて使います。この時、値の継承方法として@=&という 3 種の指示子が提供されており、これについては以前まとめました。結論として筆者は&以外不要と考えています。

&は function として結び付ける」という解説があり、これではあまりピンときません。そこで「&は Readonly だ」と置き換えれば解釈しやすいでしょう。&の対比としてバインド可能な=がありますが、これは隠蔽せず共有してしまうので、なるべく使わない方針です。React 経験者ならばpropsに近い一方向のフローと言えばイメージしやすいでしょうか。

プロジェクト内の Directive を Isolate scope にするならば、全て統一して用いるのがベターです。レガシーなソースに段階的に導入するならば仕方ないのですが、スクラッチで実装する場合は一律で Isolate scope にした方が、混乱も少なく、後々の拡張性や差し替えを容易にします。ひとつ注意としてプロパティが一つもない Directive の場合はscope: {}と空の Object を与える必要があります。こうしなかった場合、親子共有されるただの Scope が生成されます。

オススメ度: ★★☆

Scope の命名は?

Q. scopeは HTML 属性名もプロパティ名も同名でかまいませんか?

A. これは注意してください。よく迷うのがscopeの命名です。{prop: '&'}と書けば<mydir prop="">のように同名の HTML 属性として扱えます。ただし筆者の経験では、プロジェクト内検索の利便性と視認性の両側面から、多少冗長でもプロジェクト毎の接頭辞を付けることをおすすめします。別途、独自プラグインを作成しその Directivere のstrictAだった場合に、その属性名がどのプロジェクト or プラグインに属すのか、混乱が起きやすいのです。

接頭辞を扱うためには、{prop: '&myProp'}といったキャメルケース形式で記述し、使用時は<mydir my-prop="">と記述します。AngularJS ではngが用いられていますね。この話題も以前の記事で触れています。

Angular 2 では HTML 内に Angular 独自の構文が導入されるため、判別がつきやすいようになっています。

オススメ度: ★★☆

bindToController は使ってもいいですか?

Q. bindToControllerを最近よく見かけますが、使ってもいいですか?

A. はい、積極的に使っていきましょう。ただ個人的には 1.3 以前でscopeを使っているならbindToControllerに急いで置き換える必要はないと考えています。余裕があったらついでに、くらいで。

bindToControllerscopeとよく似た API ですが、Controller の$scopeに格納されず、Controller のthisに直接格納されるという仕組みです。筆者はこの新 API を Angular 2 移行への段階的な導入と解釈しています。HTML 側で{{controller.$scope.prop}}と冗長に書く必要があったところを{{controller.prop}}と書ける点がメリットです。

AngularJS 1.3.0 から導入されていたこの API は、初期はあまり話題になったと感じていません。1.4.0 からは直接bindToControllerにプロパティ{}を記述できるようになったため、利便性が向上しました。この API の紹介は本稿では煩雑になるため、別の機会にします。

オススメ度: ★★☆

ng-controller ベースから Directive ベースへ

API への理解が整ったところで、ng-controllerから脱却する手順について解説します。

class MainController {
  constructor() {
    this.user = "John";
  }
}

angular.module("myApp", []);
angular.module("myApp").controller("MainController", MainController);
<div ng-controller="MainController as main">
  <p>Hello, {{main.user}}!</p>
</div>

§1 の例をベースに進めていきましょう。

Directive Definition Object の定義

DDO - Directive Definition Object を返す関数mainDDO()を定義します。

class MainController {
  constructor() {
    this.user = "John";
  }
}

function mainDDO() {
  return {
    restrict: "E",
    controller: MainController, // Classを与える
    controllerAs: "main",
    scope: {}, // 使わない場合も空ObjectでIsolate scope化
  };
}

angular.module("myApp", []);
angular.module("myApp").directive("main", mainDDO);

次に<main>タグの中身を定義するtemplateを記述しましょう。

Template の定義

class MainController {
  constructor() {
    this.user = "John";
  }
}

function mainDDO() {
  return {
    restrict: "E",
    controller: MainController,
    controllerAs: "main",
    scope: {},
    template: "<p>Hello, {{main.user}}!</p>", // 追加
  };
}

angular.module("myApp", []);
angular.module("myApp").directive("main", mainDDO);
<div ng-controller="MainController as main">
  <!-- この<p>をtemplateにする -->
  <p>Hello, {{main.user}}!</p>
</div>

Isolate scope を使うので、templateまたはtemplateUrl必ず導入する必要があります。Isolate scope 利用時は、その Directive 内に HTML を記述することはできません。つまり、<main>...いろいろ...</main>とならず、常に<main></main>のみとなります。これによってtranscludeオプションも使用できなくなりますが、transcludeは挙動がとにかく取っ付きづらく、筆者は一度も活用できたことがないので、気にすることはないでしょう。自作 Directive はタグ内に何も書かず、属性と Isolate scope をインタフェースとして使うものと覚えるとシンプルです。

筆者は個人的好みでtemplateUrlを使い、2 行以上ならば全て HTML ファイルに記述しています。最近は ES6 の新構文Template stringsの有用性も認められてきたので、望むならばtemplateに全ての HTML を記述するスタイルでも構わないです。

作成した Directive を HTML に導入

class MainController {
  constructor() {
    this.user = "John";
  }
}

function mainDDO() {
  return {
    restrict: "E",
    controller: MainController,
    controllerAs: "main",
    scope: {},
    template: "<p>Hello, {{main.user}}!</p>",
  };
}

angular.module("myApp", []);
angular.module("myApp").directive("main", mainDDO);
<main></main>

HTML 側を<main></main>にして完成! お疲れ様でした。ng-controllerから移行するとき、ng-controller="as"が未導入で$scopeが入り混じってる状態だと<main>が持つ Isolate Scope とごちゃ混ぜになりとてもつらいので、先にasの導入、次に Class への移行と、順次済ますのが得策です。

compile, linkは Angular 2 への移行を阻害するため縮小方向が望ましいですが、いきなり無くすわけにもいきません。筆者はこのケースを次のようにまとめています。

export class MainController {
  constructor() {
    this.user = "John";
  }
}

export class MainDefinition {
  static postLink(scope, iElement, iAttrs) {
    // ...
  }

  static ddo() {
    return {
      restrict: "E",
      controller: MainController,
      controllerAs: "main",
      link: MainDefinition.postLink,
      scope: {},
    };
  }
}

angular.module("myApp").directive("main", MainDefinition.ddo);

function を並べずに export class で括った理由は、まとまった単位のほうがテストで扱いやすかったためです。

ところでなんで Directive がいいの

ng-controllerは Angular 2 で廃止されると述べましたが、なぜ筆者が現段階で Directive 化を勧めているか補足しておきます。

Directive 化促進の理由はいくつかあります。

1. テンプレートを小さく保ちたいという動機が大きいです。筆者は長らく JavaScript より HTML + PHP を書いてきた背景があり、HTML 内のリファクタリングの困難さを痛感していたため、とにかく HTML の部品は小さく、汎用性を高く、という願いがありました。HTML の見通しがよくないことは、肥大した HTML を扱うほど実感されると思います。Bootstrap などを扱っていて<div>が増えすぎることを Directive でセマンティックに隠蔽できる利点も大きい。可読性を引き上げ事故を防げる Directive、これを用いない理由はないというのが持論です。

2. 次に、Isolate scope を積極的に使いたい動機です。前述したように Scope の親子共有問題というのが厄介で、Child scope の生成されない要素は、親 Scope ではなく兄弟の Child scope を引き継ぐという不可解な仕様に悩まされるくらいなら、もう単純明快な Isolate scope だけ使っていれば混乱しないのでは。

3. そして、やはり Angular 2 への移行を段階的にしたい思いがあります。Angular 2 は Component 指向で、独自タグとtemplateを中心にした実装となっていきます。AngularJS と Angular 2 の距離を縮めることを考える時、AngularJS の Directive 化が整っている方が大幅な設計見直しをせず Angular 2 に向けてステップアップできると睨んでいます。

分割は不便、面倒だと感じる方もおられるでしょうが、モダンな Component 指向のフレームワークならば全てこうなっており、React の JSX も同様です。汎用性が上がり、差分管理が容易になることで可読性、保守性も高まる。メリットは大きいです。

オススメ度: ★★☆

§3 ES6 module と AngularJS DI

本章では AngularJS の特徴として挙げられる DI を考察し、Angular 2 で採用される ES6 module について触れながら、現段階の付き合い方や今後の実践について紹介します。

AngularJS の DI とは?

DI とは Dependency Injection の略語で、日本語では依存性注入です。かのMartin Fowlerが提唱しました。ディペンデンシー・インジェクションというと大層な感じですが、積極的に実践すべき設計で、特殊なものだと思わないでください。以下は Java の事情などは含めず「AngularJS における」DI の話です。

AngularJS DI の例

AngularJS DI の解説によくあるconstructor内でnewする例です。

class RootController {
  constructor() {
    this.store = new Store();
    this.renderer = new Renderer();
  }
}

このコードの問題は、テストの際も本物のStoreRendererを必要とする点です。Storeがもし通信を必要とするモジュールならば、テスト毎に待たなければなりません。ここでモック・テストを行うためには、RootControllerに向かってMockStoreを「注入」する必要があります。

DI を導入すると

class RootController {
  constructor(store, renderer) {
    this.store = store;
    this.renderer = renderer;
  }
}
var rootController = new RootController(new Store(), new Renderer());

var mockRootController = new RootController(
  new MockStore(),
  new MockRenderer()
);

constructorに引数を設けるとモックへ差し替える余地が生まれました。DI 自体はこれだけのことで簡単に実践できます。AngularJS ではvar rootController = new RootController(...)に相当する処理を内部でうまいことやってくれるので、そのために必要な定義が DI アノテーション、ということになります。自力で準備、実装することを「オレオレ DI」と呼ぶこともあります[3]

AngularJS はこの DI を当初から提供したことで疎なテストを行いやすくし、KarmaProtractorの提供をもってテストの重要性を説きました。

オススメ度: ★★★

AngularJS の CommonJS 採用、そして Angular 2 へ

CommonJS, Browserify

Node.jsでは CommonJS という、1 ファイル 1 モジュールとしてインタフェースや依存性を記述するモジュール管理を採用しています。一方、ブラウザ上では HTML に<script src=""></script>をいくつも並べるローディングが長らく主流で、CommonJSはサーバサイドのものという印象が強めでした。

それが 2014 年頃からBrowserifywebpackを用いてブラウザ上でもCommonJSスタイルで記述し、HTML には生成物である<script src="bundle.js"></script>を 1 行記述するだけというスタイルも現れ、少しずつ浸透しています。複数回の<script>ローディングによる通信の遅れを減らせ、npm, Bowerとパッケージ管理が分散せずに簡潔に済むことが利点だと筆者は認識しています。@teppeis 氏のこちらの記事も参考になります。

AngularJS はこれまで CommonJS に対応せず、<script src="angular.js">での読み込みとグローバル変数angularを使って実装する従来のスタイルでした。それが 1.3.14 からは CommonJS に正式に対応し、次のようなコードで参照できるよう改良されています。

var angular = require("angular");

AngularJS にはangular.module('moduleName', ['requires'])という AngularJS 独自のモジュール機構があります。この機構と CommonJS、そして Angular 2 に採用される ES6 module とはどのように付き合っていくべきでしょうか、次の節で解説します。

ES6 module

ES6 module とは CommonJS module (require, module.exports)の仕組みに近いモジュール機構で、ES6 の言語レベルで備える点が最大の特徴です。

commonjs.js
var otherModule = require('other-module');

module.exports = function mymodule() {
  // ...
};
es6.js
import otherModule from 'other-module';

export function mymodule() {
  // ...
};

比較すると概ねこのようになります。厳密には ES6 にはdefaultなどの仕様が加わるため、全て CommonJS の仕様と一致するわけではありません。TypeScript では 1.4 から ES6 module の構文で記述が可能となりました。

AngularJS module

AngularJS にはmoduleservice, directive, controller…とても用語が多く用途も似ているので大変混乱しやすいです。

筆者は次のように考えています。

  • angular.module(): アプリケーション名、または提供するプラグイン名(まとめてプロジェクト名と称する)
  • angular.module().service(): そのプロジェクト内の肥大化を避けるために適宜分割する単位

AngularJS module は粒度がだいぶ大きく、これまでに述べた CommonJS/ES6 module とは性質が違うので、混同しないよう注意してください。CommonJS/ES6 module に相当するものとしては AngularJS Service が近いです。

移行を見据えた AngularJS での考え方

ここまで、AngularJS を取り巻くいくつかのモジュール機構(AngularJS module, CommonJS module, ES6 module)と AngularJS の特徴である DI を紹介しました。AngularJS の module が先行していたため、後発の仕組みと食い違い、複雑なものとなってしまいました。

一旦整理し、何をどう扱うか説明しましょう。

AngularJS module はアプリケーション内にひとつ

前述のとおり、AngularJS module は粒度が大きい単位なので、アプリケーション内でいくつも用いることはアンチパターンだと捉えています。可搬性を考えると、ひとつのアプリケーションにはそのアプリ名を与えた単独の AngularJS module で運用すべきです。

他のユーザに AngularJS プラグインを配布する場合は、ここにはプラグイン名が入ります。

オススメ度: ★★★

移行に向けての分割

Angular 2 は ES6 module を積極的に活用する設計となっており、今から ES6 module に関心を持つのは良いことです。ただ、今すぐ AngularJS を ES6 module に書き換えるべきかといえばそうとも限りません。いずれ Angular 2 で適合するように再度修正する作業は発生します。

ただし、各種 Service, Controller, Direcitive を 1 ファイル内にひとつにしていくリファクタリングは、早めに意識しておくべきです。1 ファイル内にいくつも定義している方は Angular 2 での移行コストに ES6 module 導入の手間も加わるので、早めに段階的に分けて備えましょう。

なお、angular.module('myApp')はチェーンにせず毎回書くべきという方針です。筆者の場合、1 ファイルに 1angular.module('myApp').*という方針にしており、1 ファイル内にあれこれ定義しません。例外的に複数書くのはangular.module().run()angular.module().config()のみです。

オススメ度: ★★☆

CommonJS + Browserify スタイルを好む方は

AngularJS 1.3.14 から CommonJS が採用されたことで、Browserify[4]の導入が容易になりました。Browserify に詳しくない方は無理して導入する必要はありません。そして知識や経験がある方ならば、現時点から CommonJS 化、Browserify 対応を始めることは間違っていません。

筆者は Browserify に対応したスタイルで AngularJS を書いています。そして「1 ファイル 1Service」はそのまま「1 ファイル 1module[5]」と置き換えられるので、Service 自体を一切使わないようになりました。angular.module().service().factory()を使うことで AngularJS DI を利用できるというメリットは、proxyquireを使うことで保っています。

オススメ度: ★☆☆

proxyquire の実験

【追記 2015/6/28】更に検証を進めましたが AngularJS と proxyquire は全く相性が良くないのでお勧めしないどころか、止めておいたほうがいいです。素直に Karma を使ってください。


AngularJS の DI はテスト利便性のためという性質が強いので、ここが維持できるならば AngularJS が提供する DI にこだわる必要は無いと筆者は考えました。proxyquire を併用すると、$injectの記述を少なくでき、ブラウザのローディングでどうしても実行が遅い Karma から、高速な Node.js + Mochaに切り替えられる点をメリットと捉え実践してみました。

筆者の実際に運用したテストです。実はこのときはまだ proxyquire に気付いておらず、自力で似たような機構を実装していたためコード量が増えてしまっています。方向性としては同じです。

この proxyquire で複数のモジュールをモック化しているだけでなく、AngularJS DI を用いる$rootscope$timeoutも L21-L30 でモック化して与えています。例にはありませんが、$resourceなどのモック化も同様に行えます。

ただしこれは尖ったアプローチで、一般的ではないと自覚して試みています。過度にやりすぎると今度はテスト自体が負債となる恐れもあり、筆者自身もまだバランスを確かめている段階です。

オススメ度: ☆☆☆

Angular 2 の DI

Angular 2 では、Service と Factory の違いに悩まずに済む DI 機構や、ES6 module と親和性の高いSystemJSというモジュール管理機構を推奨しています。SystemJS はあまり馴染みがないですが「郷に入っては郷に従え」となるのかもしれません。

§4 jQuery

事を荒立てる前に言うならば、jQuery から完全脱却できない話は止むを得ないことだと感じています。そして意見に流されすぎるのもよくありません。主観的には様々な感情論があることでしょうが、それは置いておきます。

DOM 操作に対する時代の変化

仮想 DOM の登場は jQuery 主流だった Web 制作からすると新たな潮流です。仮想 DOM の詳説は別の記事に譲ります。仮想 DOM は差分検出を主としているため、jQuery を通じて ── もちろん他の手段でもですが ──DOM を直接操作すると仮想 DOM の情報と実際の DOM に食い違いが発生します。そのため仮想 DOM を用いる View ライブラリでは jQuery 等による手動 DOM 操作はご法度とされるようになりました。

また jQuery は、もともとはゼロ年代のブラウザ間差異を吸収する目的で普及が進み、jQuery 2 系の方針からも分かるように、現代ではレガシーブラウザはサポートされない方向に進んでいます。どうしても意図的に直接 HTML 要素を操作する場合も、現在はDOM API が提供する範囲で十分賄えると感じます。ここでどうしても jQuery の力が必要というときに使えばよいでしょう。

負の遺産と嗅覚

身元の怪しいjQuery プラグインから脱却できない事情は、負の遺産にしかなりえないと考えており、筆者はまったく賛同しません。これは jQuery に限らず何のライブラリ、フレームワークにおいても同様で、AngularJS 向けのプラグインについても言えます。

代表的な AngularJS プラグインにAngularUIがあります。これは AngularJS 1.4 が控えてる今になってようやく 1.3 に対応したので、決して早い対応ではありませんでした。このようにプラグインを採り入れることは自らに足枷をはめることと同義になりかねないため、開発コミュニティの体力や活性度、採用することで得られるメリット、負の遺産としないための疎な設計、これらを十分に考慮すべきです。

低品質なプラグインに対する嗅覚は常日頃養いたいものです。

Angular 2 は脱 jQuery

誤解をされている方がいるようですが、AngularJS 1.x はむしろ jQuery との親和性を意識して設計されました。それは今日になっても変わっていません。AngularJS のロードより前に$グローバル変数が存在した場合、自動的に mixin してangular.element()で使えるようになっています。

ただし、親和性が高いからといって無頓着に増やし続けると、それは Angular 2 への移行を困難にします。Angular 2 では逆に jQuery を推奨していません。これは前述した仮想 DOM の事情に近く、Change Detection(開発者の記事)を正しく動作させるためには直接 DOM を操作すべきでないからです。Change Detection は Angular 2 の高速化に寄与しており、これは 1 系から 2 に移行する最大のメリットとなるでしょう。

§2 でcompile, linkには依存しすぎないよう注意せよと述べました。これは jQuery を極力控えるべき事情と通じています。

AngularJS と jQuery の付き合い方

では jQuery を多用している AngularJS プロジェクトはどのように乗りこなすべきでしょうか。答えはngIf#ngShow#, ngHide#の駆使にあります。

多くの場合、ダイナミックな HTML 要素の追加にはngIfを使用してください。ngIfbooleanに従い DOM をその都度生成・削除します。どうしてもこの生成パフォーマンスが気になった場合にはngShow/ngHideを使えます。これはdisplay: noneによって要素は残しながら非表示とします。

リッチな UI を実現するとき、しばしばアニメーション効果が望まれます。これはngAnimate#CSS Transitionsにて表現可能です。フェードイン・アウトは CSS Transitions で制御する都合上ngShow/ngHideの方が扱いやすいです。ダイアログ、プルダウンメニュー、リストや通知の挿入、削除はngAnimateで十分対応可能です。

申し訳ないのですが筆者はあまり過度のアニメーション装飾を好まないため、これ以上複雑な動きには必然性を感じていません。どうしてもそういった動きを実現するのに jQuery が必要と考えるならば、同時に AngularJS が本当に必要かどうかについても検討できるでしょう。そこで AngularJS が必要ならば、これは CSS Transitions の可能性を拡げるきっかけにも繋がります。

適材適所と言ってしまえばそれまでですが、世の流れを窺いながら適切に付き合っていきましょう。

オススメ度: ★★☆

§5 AngularJS の気になるところ

本章では、まとまり切らないながら重要である疑問について扱います。

scopeをcontrollerAsに置き換えるとwatch はどうすれば?

A. 問題なく使用できます。

§1 で$scopeは Controller のthisに置き換える手法を紹介しました。そうなると$scope.$watchはどう扱えばいいのか疑問が起こります。

これは全く問題がなく、$scopeを、Controller のプロパティを生やすための Service として捉えずに「$scope$watchを提供するための Service である」と認識を改めるとよいでしょう。なので他の Service、例えば$resourceを DI するのと同じ感覚で$scopeを DI してください。

class MainController {
  constructor($scope) {
    this.$scope = $scope;
    this.user = "John";

    this.$scope.$watch("main.user", (newVal) => {
      console.log(newVal);
    });

    MainController.$inject = ["$scope"];
  }
}

function mainDDO() {
  return {
    restrict: "E",
    controller: MainController,
    controllerAs: "main",
    scope: {},
    template:
      '<input type="text" ng-model="main.user"><p>Hello, {{main.user}}!</p>',
  };
}

angular.module("myApp", []);
angular.module("myApp").directive("main", mainDDO);

動作サンプルです。意外かもしれませんが、$watch対象は$scopeに格納されたプロパティに限っておらず$watch('ここに書かれた式')によって解決されています。式は HTML の{{main.user}}と同等のものです。

オススメ度: ★★★

$broadcast, $emit, $on は使ってもいいですか?

A. 答えに詰まりますが、Angular 2 では別の機構に置き換えられ互換性のある API は用意されないため、これを理解した上で用いるなら可です。

StackOverflow を眺めていても気付くところで、$broadcastには誤解があるようです。筆者もその一人で、どうやら$broadcastEventEmitterと同じように扱うことは開発陣は想定していないようでした。AngularJS としては、むしろこの API をMouse Eventといった Web API に対して使うものと想定していたようで、その証拠に Angular 2 に$broadcastに替わるものとして用意されたhostListenersの機構は、マウスやウインドウのイベント取得に向けて設計されています。

では AngularJS 内で$broadcastを EventEmitter 的に使ってはダメかというと、そうではありませんが、推奨されていないと認識すべきです。Angular 2 で同等の機構を実現するならば、React と同様に EventEmitter を継承して使えばよいようです。

オススメ度: ★★☆

Filter はどのように扱っていますか?

A. 内蔵されているもの以外は使っていません。

筆者はあまり自作 Filter を量産しようとは考えておらず、極力汎用ライブラリの処理結果を bind させる手段をとっています。これは AngularJS から他社フレームワークに乗り換える必要が生じた際への対策といえます。汎用ライブラリの例は§6 にて扱います。

ところで、Angular 2 にはPipesという API が備わっており、これが AngularJS でいう Filter に相当します。Angular 2 の Pipes に関する情報は@laco0416 氏のこちらの記事をお勧めします。

オススメ度: ★☆☆

ルータの最適解は Component Router ですか?

A. 残念ながらComponent Routerを使う時期はまだ来ていません。

Component Router とは、2015 年 3 月に New Router として発表された Angular チームが開発している新しい Router です。New Router の名称は陳腐化すると指摘があり、現在は(通称)Component Router と呼ばれています。これは API がまだ安定しておらず、AngularJS 1.4.0 にも間に合わないとアナウンスされている[6]ため、今急いで導入する必要は無いでしょう。

Component Router を実際に使う例が 2015 年 4 月の GDG 神戸 Angular 勉強会#3 でありましたので、紹介しておきます。

筆者は Directive 化を強く進めた結果、ui-routerを必要とすることなくngRouteで落ち着いてしまいました。ui-router の有用性は認めているので、これは純粋に好みによるものです。ヒントを期待されていたならば、ごめんなさい。

オススメ度: ★☆☆

§6 armorik83 流モダン開発環境

最後の章では、筆者がモダンだと考えている AngularJS の開発環境について記しておきます。Angular 2 を意識してはいますが、入門者から熟練者まで幅広く受け容れられる構成とは思っておらず、参考にされる程度で構いません。

汎用ライブラリ

ライブラリはできるだけ少なめにしたいという指針をもって選んでいます。

オススメ度: ★★☆〜★★★

言語

TypeScript
まずはTypeScriptです。TypeScript との付き合いも AngularJS 歴と同じ 2 年弱で、強固な相棒でした。

かつて JavaScript で Class 構文を使いたいならば TypeScript しか選択肢がありませんでした。これも TypeScript を選択した大きな理由です。最近は次に挙げる Babel も使います。

オススメ度: ★★★

Babel
Babelはもう手放せない存在です。AngularJS アプリケーションの場合、型を重視したいため TypeScript を用いますが、シンプルに 1000 行程度で収まるライブラリは Babel で書くようになりました。2015 年は Babel が急成長しており、TypeScript でなくとも Class 構文を採り入れやすくなっています。Babel で書く場合でも、TypeScript の.d.tsは用意しており、これは下手なドキュメントより優秀です。

そのほか、テストではモックへの型検証がかえって邪魔になることがあるので、TypeScript を使わず、すべて ES6 + Babel で書いています。

オススメ度: ★★☆

dtsm
型定義.d.tsのインストールと管理は@vvakame 氏のdtsmで行います。tsdと違い、コマンドがシンプルで複数のリポジトリに分散した型定義も管理しやすいだけでなく、やはり国産でバグ報告と修正が円滑という強みがあります。

オススメ度: ★★★

ESLint
ESLint最近使い始めたらとても気に入ったので、今後も Babel で書く際は併用したいと考えています。一方、TSLintは残念ながら処理待ちが長いため使っていません。これはコーディング・スタイル以外は tsc が警告してくれるものと考え妥協しています。

オススメ度: ★★☆

Less
CSS 自体あまり書きたくないのですが、そうも言えないためLESSを使っています。以前流行りだしてすぐの頃はSassCompassでした。Ruby ってのがどうにも馴染まず。

オススメ度: ★☆☆

タスクランナーとビルドツール

gulp
タスクランナーは、今は専らgulpを使っています。単純なものはnpm scriptsに記述し、残りの大半はgulpfile.jsに記述しています。以前記事を書きましたので、gulp についてはそちらを。

オススメ度: ★★☆

Browserify
Browserifyは§3 の CommonJS module の節でも触れました。AngularJS が CommonJS に対応したことで Browserify 導入の契機となりました。

オススメ度: ★★☆

テスト

Mocha
AngularJS はテストの実行にJasmineを用いて解説していますが、筆者は Jasmine からMochaに乗り換え現在も使い続けています。describe, itスタイルで Jasmine とよく似ています。

オススメ度: ★★★

Sinon.JS
モックの強い味方Sinon.JSです。AngularJS の Directive や Controller のテストは大半を Sinon.JS 併用で行い、最後の網掛けとして本物を使った e2e で埋めています。

オススメ度: ★★☆

Nightmare
その e2e は紆余曲折があり、Angular チームが開発しているProtractorや Groupon のTestiumなど、いろいろ試してきました。

世の中がまだ PhantomJS 2 に向かいきってないのが歯痒いですが、現在はNightmareになんとか落ち着いています。なお、残念ながら Angular 2 alpha では e2e 周りがとても弱く、今後改善されることを期待しています。

オススメ度: ★★☆

proxyquire
§3 の DI の節にて紹介したproxyquire、AngularJS DI をあえて用いない、かなり尖った構成です。詳細はこちら

オススメ度: ★☆☆

power-assert
@t_wada 氏を中心に開発されているpower-assertも最前線。コンソールデバッグの手間を大幅に減らせて心強い味方です。余談ですがこのプロダクトのロゴマークをデザインさせていただきました。

オススメ度: ★★★

cw-log
手前味噌でcw-logも多用しました。AngularJS で複数の Class を組み合わせると処理順を確かめたい場合が多く、本番環境でもログの出力をコントロールでき重宝しています。

RIP

最後に、使っていたものの今は離れたライブラリを供養して締めくくります。

  • Yeoman Generator: 保守が面倒すぎて、どんどん陳腐化した
  • Grunt: この JS の速さについてきていない、各種プラグインが負債化
  • Bower: npm と Browserify ですべて管理するので不要に
  • Jasmine: そこまで悪いわけではないが、Mocha + SinonJS + power-assert を考えるともう戻れない
  • Protractor: Jasmine に依存しているため同上の理由
  • Underscore.js: lodash を使っている
  • jQuery: §4 参照

謝辞

本稿のためにたくさんの方々のご協力をいただきました。

私が AngularJS に真摯に向き合う機会をくださり、長時間の相談と推敲にも付き合っていただいた@_likr 氏、AngularJS を使い始め入門者の視点から沢山の指摘を寄せてくださった@shinsukeimai 氏に感謝いたします。

本稿のレビューを引き受けていただき、貴重な視点や指摘、理解の深まる質問を寄せてくださった@Quramy 氏、@izumin5210 氏、Angular 2 の情報収集の際にとても支えとなっている@shuhei 氏、@laco0416 氏に感謝いたします。それぞれの Angular への熱意がこの記事を押し上げました。@vvakame 氏には私が AngularJS と TypeScript を使い始めて間もない頃から、数々のアドバイスをいただけましたこと感謝いたします。

最後に、本稿を最後までお読みいただいた皆様へ心より御礼申し上げます。本稿が皆様の役に立ち、Angular ユーザがより豊かになることを願っています。

2015 年 5 月 Crescware 奥野賢太郎 @armorik83


脚注
  1. stable は 1.4.1 ですが、標準でインストールされる最新バージョンが 1.5.0-beta となっています。 ↩︎

  2. privateでも可。 ↩︎

  3. オレオレ DI はテスト困難なモジュールに対して暫定的に強引に導入する性質が強く、毎回やるべきではないと考えます。 ↩︎

  4. webpack 派は webpack と読み替えて結構です。 ↩︎

  5. CommonJS の module を指します。 ↩︎

  6. Angular Weekly Meeting議事録、開発者向けの情報です。 ↩︎

Discussion