Angular 2 alpha.11, alpha.13 時点の情報です。現時点の最新版とは全く異なる内容です。現在では公式サイトのドキュメントが充実しているので、そちらを参照することをおすすめします。
昨日参加してきた ng-japan の間、Angular 2 の 5 Min Quickstart という Hello World 的なサンプルから始めて、貧弱な TODO アプリを作ってみました。感想と、その過程でいろいろハマったところを書いてみます。
感想
まずは感想から。
別のフレームワーク?
やっぱり Angular 1.x とは別のフレームワークだなあという印象です。概念も書き方もツールも違う。ライブラリもそのまま使えない。Angular とは別の名前にした方がいいのでは、というくらいです。
現時点では、他のコンポーネント志向のフレームワークに乗り換えてもコストは同じな気がします。New Router でちょっとづつ移行できるというというのも、Router でたくさんのページを作っていない人には関係なさそう。今後の展開に期待です。
Web Components
ばっちり Web Components 的なものが使われています。詳しくないので、調べなければ。
Shadow DOM が使われていて、CSS も Component ごとに scope されており、グローバルな CSS は当たりません。書く Component で CSS を import すればいいんですかね。これはけっこううれしい面もあるけど、現在のライブラリ、開発スタイルからの大きな転換が必要かなと。
quickstart 特有(?)の問題
以下は、今後開発が進んだり、quickstart とは別のビルドシステムを使えば解決しそうなことです。
Chrome でしか動きません。Mac の Firefox, Safari は動作せず。
クライアントで ES6 Modules を非同期読みこみしつつ変換かけてるので、とても遅いです。遅いだけならいいのですが、JS を外出しして import するようにすると、ブラウザにアグレッシブにキャッシュされ、ファイルを変更してリロードしても反映されずハマります。 Chrome Developer Tools でキャッシュを off にしましょう。
quickstart に含まれている angular2 は alpha.11 のバージョン。最新の angular/angular
は alpha.13 なので注意が必要です。後述する componentServices
-> services
とか。
export default class Foo {}
はうまく動きません。ES6 modules のコンパイルと annotation のコンパイルの組み合わせがおかしいようです。クラス宣言と export default
を分けるか、export class Foo {}
としましょう。
テンプレート
使う Directive を宣言
Component のテンプレート中で使う directive は、逐一宣言する必要があります。自作 Directive だけでなく If
, Foreach
などもです。
@Component({
// ...
})
@Template({
inline: `
<h1>Parent</h1>
<p *if="!hasChildren()">No children</p>
<ul *if="hasChildren()">
<child-component *foreach="#child in children" [child]="child"></child-component>
</ul>
`,
directives: [Foreach, If, ChildComponent]
})
class ParentComponent {
// ...
}
If, Foreach の使い方
Angular 2 の Viewport Directive 文法の謎 に書いたので、参照してみてください。
属性の書き方
Angular 1.x のテンプレートには以下のような問題がありました。
- expression だったり string そのままだったり。
ng-template="'foo.tpl.html'"
- ng-click とかイベントごとに directive が必要。
Angular 2 ではこれらの問題を解決するため、書き方と動きを一致させ、ひと目でその属性がどういう働きをするかわかるようにしてあります。
-
foo="bar"
普通の属性。Directive で bind すると、属性値を文字列として受け取れます。 -
[foo]="bar"
Directive で bind すると、属性値を expression として評価したものを受け取れます。 -
(click)="bar()"
click イベント発生時に、属性値を statement として評価します。 -
#foo="bar"
foo という変数を定義。 -
#foo
暗黙的な引数を定義。Directive が\$implicit
とという名前で用意した local の値が入る。
自作の Directive に属性を渡す時も同様です。@Component({ bind: { foo: 'foo' } })
としておいて、テンプレートで [foo]="something"
と書くと this.foo
に something
を評価したものが入ってきます。
class 属性も同じような感じで指定できます。ng-class
のかわりですね。試してないけど style
も同じような感じで指定できるようです。
<div [class.foo]="isFoo"></div>
フォーム
Angular 1.x の特徴とも言える双方向データバインディングはありません。
素朴にやると input
の keyup
や change
イベントで $event.target.value
を取得することになります。React の controlled components みたい。
Control
というものがあり、そっちが本命のようです。これはまだ試してません。
Service
1.x の module.service() ではなくて、ざっくり言うと Directive でないものの総称です。Component の constructor に inject されます。
以下のようにして componentServices
で Provider を指定して inject できるようにします。quickstart で使っている alpha.11 では componentServices
ですが、最新の alpha.13 では services
に変更されているので注意!
@Component({
componentServices: [Foo, Bar]
})
class FooComponent {
foo: Foo;
bar: Bar;
constructor(foo: Foo, bar: Bar) {
this.foo = foo;
this.bar = bar;
}
}
親 Component で componentServices
を指定していれば、子で同じものを指定する必要はありません。子でも同じ Service を指定すると別のインスタンスが inject されるので注意!理由がわからずかなりハマりました。局所的な Singleton を作れるので、意図的に使えば便利かもしれませんね。
さらなる疑問
- 変更検知の対象はどこまでなのか?expression に入れているもの?Component instance にぶら下げているもの全部?
-
$scope.$watch
みたいなことをするには?たまに見るonChange
というのでできる?しないのが一番いいけども。 - CSS はどうやって管理するのか?
変更検知について追記
変更検知の対象は binding のみ。[foo]="bar"
というやつです。{{bar}}
も binding の形に変換されるので binding です。
onChange
は以下のようにすると使えます。
import { onChange } from 'angular2/angular2';
@Component({
selector: 'something',
lifecycle: [onChange],
bind: {
foo: 'foo',
bar: 'bar'
}
// ...
})
@Template({
// ...
})
class SomeComponent {
onChange() {
// ...
}
}
で、親の component で以下のようにしている場合、something が初期化された時と foo, bar の値が変わった時に呼ばれます。foo と bar が両方とも同じ digest で変わった際は、onChange は一回しか呼ばれません。
<something [foo]="foo" [bar]="bar"></something>
同じ調子で onDestroy
というのもあります。