AngularJS についての所感

AgularJS に対する気持ち

所感といいつつ、主に自分がつらさとして感じていることを書く。所感シリーズとしては jQueryについての所感 も併せて読みたい。


Learning Curve

AngularJS: The Best Parts - Anand Mani Sankar から引用


この学習曲線の中でいうと、たぶん今の自分は Very Cool! の頂点から降りている最中くらいだと思う。そして、マサカリをふりかぶった諸兄にひとつだけ言いたいのは、共感脳を養った方がモテるということだ。

チキンハート的弁解: 以下はAngularJSに関するつらさを述べることに専念するために、美点を挙げていないだけであってAngularJSを全方位的に貶めたり、何かと比べて明確にクソだというような意図はない。

画像は AngularJS: The Best Parts · Anand Mani Sankar からの引用。X軸にある www.bennadel.com は AngularJS 大好きさん。

辛1. $scope が使えるようで使えない

$scope のおかげで AngularJS が初見でやさしく便利なライブラリに見えている。Controller や Directive、さらにHTMLからも参照できるデータ空間が、自由自在にデータを共有できるかのような「錯覚」をもたらすことこそ $scope の功罪だろう。

<body ng-app="app">
<div ng-controller="ParentCtrl">
  <div ng-controller="ChildCtrl">
    <div loop-item ng-repeat="item in items"></div>
  </div>
</div>
</body>
angular.module('app', [])
.controller('ParentCtrl', function($scope) {
  $scope.items = [1, 2, 3];
  $scope.foo = 'bar';

  console.log($scope.foo); // 'bar'  
})
.controller('ChildCtrl', function($scope, $location) {
  console.log($scope.foo); // 'bar'  
})
.directive('loopItem', function() {
  return {
    restrict: 'A',
    link: function(scope, element, attr) {
      console.log(scope.foo); // 'bar'  
    }
  };
});

どこまでいっても foo = 'bar' だ。このグローバルっぷりは「window 汚染すんな」が「$scope 汚染すんな」という問題にすり替わってしまっただけのような印象すらある。

ここで $scope というオブジェクトが共有されてるかと思いきやそれは誤りである。あくまで子が親のスコープを継承しているだけである。

<div ng-controller="ParentCtrl">
  {{baz}}
  <div ng-controller="ChildCtrl">{{baz}}</div>
</div>
angular.module('app', [])
.controller('ParentCtrl', function($scope) {
  $scope.baz = 'qux';
})
.controller('ChildCtrl', function($scope, $location) {
  $scope.baz = '✌(՞ټ՞✌)';
});

これの結果は次のようになる。

<div ng-controller="ParentCtrl">
  qux
  <div ng-controller="ChildCtrl">✌(՞ټ՞✌)</div>
</div>

同じオブジェクトを扱っているように思えるが、子のスコープに加えられた変更は親に伝播しない。ここでうっかり $parent を使い始めようとするとアンチパターン一直線になる。

Controller は常に親を継承して新しいスコープを生成する。Directive は scope の設定で Controller のスコープをそのまま使うか、継承して新しいスコープを生成するか、独立した新しいスコープを生成するか選べる。

一見して簡単そうに見える $scope を使いこなすためには、これらの法則を理解した使い分けが必須だ。つらい。

辛2. 飽和する DI と Provider

万能かと思われた $scope と距離を置き始めるのは、AgularJS を使い始めてからさほど時間のかかることではない。Directive は内外のスコープを保護するために Isolated Scope にするだろう。attribute を通したバインディングだってする。

そうすると $scope で雑な共有ができなくなりデータストアや処理の共通化に困るようになる。それを解決するのが Dependency Injection(依存性注入)だ。

DIは強力だ。window$scope も使わずに Provider を注入して色々なオブジェクトを共有できる。DI を徹底するために $window$location$document (なんでjQueryライクオブジェクトなんだ!)もある。

myApp.controller('AcmeCtrl', function($scope, $location, someService, someResource) {

DIを使いこなしている感じがして良い。

myApp.controller('AcmeCtrl', function($scope, $location, someService, someResource, dogeService, dogeResource) {

うっ...

myApp.controller('AcmeCtrl', function($scope, $location, someService, someResource, dogeService, dogeResource, SOME_CONSTANTS, $window, $compile, popupService) {

アアッ。

(極端な例だけど)つらい。

辛3. $scope アクセスの記法が一貫性ない & expression 強力すぎ

binding と interpolate の違いということになるのだろうが、{{ }} の有無のせいでHTMLから $scope の世界にアクセスするときのルールが増えてしまっている。

分かってはいる。分かってはいるんだ。しかし、やはり気になってしまう。

<!-- ng-bind -->
<span ng-bind="item.text"></span>

<!-- interpolate -->
<span>{{item.text}}</span>

{{ }} 繋がりで八つ当たりをすると、テンプレートで利用できる expression が高度すぎるのもどうかと思うことがある。HTMLの中にロジックを散らかすのは好きではない。

ng-show などのディレクティブに渡せるのもパラメータではなく expression と言える。ng-show="(!foo && bar) || baz" のような呪文を、紳士協定で禁じなければならないのは面白くない。 つらい。

辛4. digest loop の後ろをとるための $timeout と敗北感

たとえばある配列を <li> として ng-repeat したとして、そのリストの height を取得するには AngularJS の dirty check が終わったあと、明示的には訪れないいつかを待たなければならない。そのとき手っ取り早いのは次のような方法だろう。

$scope.$watch('items', function() {
  $timeout(function() {
    var height = element.height();
  });
});

なんでやねん

構造上、これが仕方無いのも分かるが、いざこういうコードを書かなくてはいけなくなったケースの敗北感たるや凄まじい。つらい。

でもこれ vdom の patch を rAF でアップデートするときも起こりうるし、dom更新後にイベント発行して once するしかないかな

辛5. スマートUI(賢すぎるUI)化する Controller

いわゆる Fat Controller 問題と同種の話が容易に発生する。本当は、Isolated Scope な Directive が疎に協調しあっていれば、少なくとも複数の関心が一箇所に集中することは避けられるはずだ。

であるにも関わらず、Directive がコンポーネント化を奨めてくれているのにも関わらず、Controller は昔みたような冗長なjQuery的なコードの繁殖を促しているという点が、非常にもったいない。どっちやねん。てか Directive にも Controller とかあるしオイオイ。

寡兵に鉄砲を持たせて突撃したいのに、すぐに腔発するような鉄砲を渡されたようなものだ。つらい。

番外1. AngularJS に思いを馳せるのが疲れる

個人の好き嫌いなので番外とする。

チーム内に理性を強制するコストが高いのと、色々ぶっ込みすぎたが故の一貫性の無さが自分にとって耐えがたかったと思われる。何でもできるように見えて、何かと引き換えに何かを失っているように映る。随所において、一貫性や正しさが損なわれてしまっていて、完全でないような印象を受ける。

ググらないと分からないとか、$compile やら require: '^ngModel' やら $digest/$apply のような知らないと使いこなせない機能の深淵さは、まぁ必要な学習コストだと思う。必要なコストは払わなければならない。Directive の restrict の記述がクソなのだって、センスが微妙なだけで実害はなかった。

しかし、つらい。

番外2. 業務的な観点から見たとき

これも運用次第なので番外。

  1. jQuery書けるよ人材では、Seriveなどを使ったオブジェクトの分掌設計ができずに、スマートUI化が進むので開発効率が悪い。
  2. プログラム分かるけどフロントの経験が浅い人材では、AngularJSに慣れても潰しが効かないので教育効率が悪い。

1はコードレビューのコストで解決できるが、2はどうしようもない。端的に言えば「jQuery書ける != JavaScript書ける」と同種の問題が顕在化する恐れがある。

全体感としても、便利な機能に対してコードの自由度が高すぎるので、少人数で人員の回転が速いプロジェクトにおいては安全な文化ができあがるよりも早く汚染が進みがちだ。ましてや常に JavaScript スペシャリストだけがアサインされるとは限らない。規約の遵守etcにマネコスをかけられる環境でないと正直つらい。個人の感想です

逆にどこで活かせるかといえば、少数精鋭でAngularJSを使うことの合意が取れている環境か、素早く簡単なプロダクトを作りたいときだろうか。殴り書きによる短期的な生産性は捨てがたいし、そういうのには向いている。

2.0 に期待しよう

ちなみに AngularJS 2.0 では次のような変更があるそうなので、1.x とは別モノと捉えて期待を寄せている。詳しくは見れていないが、ちょうどこの記事でイチャモンをつけたポイントが改善されているような気がする。

自分が今のAngularJSに対して感じている、ある種の「正しくなさ」がメジャーアップデートによって解消されることを望んでいる。それまでは別のプロダクトに寄り道しつつ待ちたい。

  • Angular 1.x の controllers と templates を包含する統一されたコンポーネントモデルにより、概念 (concepts) と定型 (boilerplate) を減らして再利用性を高める。
  • scope の概念を見直してシンプルでわかりやすくし、コンポーネント間の責任分担を改善。 (日本語訳)ng-europe, Angular 1.3, and beyond - AngularJS Ninja

AngularJS がコンポーネント的な考えやデータバインディングという仕組みの認知浸透に与えた功績は小さくない。なんだかんだで、目の前にあるプロダクトはAngularJSなのでしばらく頑張ります(。•̀ᴗ-)✧

追記

この記事が、あまりにも似たようなことを述べている感じだった。おれもこんなオシャレに書きたかった!