Angular2で簡単なSPAを作ってみた感想

`angular2` の `RC` もリリースされたことですし、とりあえずどんなワークフローになるか知りたかったので簡単な `SPA` を作ってみることにしました。せっかくなので **Material Design for Angular2** も使ってみました（執筆時点でまだ `2.0.0-alpha.5 granite-gouda` ですが）。作ってみて現時点で思ったことを記しておきます。 **申し訳ないですがあまりまとまりのない文章です！** **誤った記載などありましたらご指摘いただければすごくうれしいです**

↓こんなの作りました([ソース](https://github.com/kenfdev/ng2-simple-spa))
![ng2.gif](https://qiita-image-store.s3.amazonaws.com/0/38528/0de0807b-b909-2c20-6fb0-85b0440e2cbd.gif)


# 概要

画面としては

* ログイン画面
* ユーザー登録画面
* ダッシュボード（投稿一覧）
* 新規投稿/編集画面

を作りました。サーバーサイドはMockの `json-server` を、特にカスタマイズせずに使ったので、ユーザー登録周りとかはただのハリボテです。 `Material Design` のコンポーネントがまだ無いもの（例えばダイアログとか）はいったんダサいですけど `confirm` とかで代用しました（どんな感じに使うのか知りたかっただけなので）。また、他の `css` フレームワークとかはややこしくなるので使ってません。
ざっくりとした動きは↓のような感じです。

# 所感
まだ本当に表面しか触ってないですけど、率直に楽しいです（これは個人的な意見すぎるかもしれませんが）。特に `TypeScript` 信者な自分としては、サクサクとコード補完の恩恵も受けながら `ES2015` 記法でコンポーネントを作っていくのは楽しいです。と、そんな偏った意見はさておき、こんなシンプルなSPAだけど気づいた点についてメモっておきます。

**「良い」と思った**

* 一つの `class` でコンポーネントが完結するのはうれしい
* `View` にローカル変数作れるのうれしい（#）
* `http` がデフォルトで `Observable` を提供してくれるのはうれしい
* **Style Guide** があるのうれしい
* `Material Design` 使うの簡単。けど部品がまだまだ無い（alphaですしね）

**「悪い」というよりは「気になった」（と、よくわかってない）**

* `Angular2` の公式ドキュメントが結構いろいろなところでコードが壊れている
* コンポーネント内で使いたいコンポーネントは `directives` に都度指定する必要があるのがちょっとだけ手間
* `Router` 関連の仕様がまだ安定していない( `CanActivate` とか `path` から `name` 消えちゃったりとか、 `useAsDefault` なくなっちゃったりとか）

順番にもうちょっと詳しくまとめます

## 一つのclassでコンポーネントが完結するのはうれしい

`Decorator` が使えるおかげで一つの `class` でコンポーネントを定義できます。Angular1の場合、 `component` というものが1.5で追加されたものの、 `component` も `directive` も `controller` を別途指定しないといけないので、下のように２箇所で宣言する必要があります。また `class` はホイストされないので `component` の定義よりも先に宣言しておく必要があります（少なくとも私の認識では。。。）。 `directive` は `function` なので `class` を先に書いてもちゃんと動くんですけどね。

```js
class HogeController {
  // 実装省略
}

export const HogeComponent = {
	template: 'hoge.html',
	bindings: {
	  hoge: '<',
	  moge: '@',
	  piyo: '&'
	},
	controller: HogeController // ここに functionで直接実装してもいいですが、ちょっとそれは。。。
}
```

なので、 `HogeComponent` がコンポーネントの定義を説明しているのに、下までスクロール（ `class` の定義の下にあるので）しないとその中身が確認できない状態です。超個人的ですが、宣言はやっぱり先に見えてる方がわかりやすいと思います。
Angular2は `Decorator` を使ってコンポーネントのメタ情報を定義できるので見やすいと感じました。宣言が先にあって、実装が後にあるイメージです。例えば今回作ったダッシュボードコンポーネントは↓のような感じになります。地味に `selector` も `Decorator` で指定できるようになったのもうれしいです。

```ts
@Component({
    selector: 'mta-dashboard',
    templateUrl: 'src/app/dashboard/dashboard.component.html',
    directives: [
        ROUTER_DIRECTIVES,
        MD_PROGRESS_CIRCLE_DIRECTIVES,
        MD_LIST_DIRECTIVES,
        MD_CARD_DIRECTIVES,
        MdButton
    ],
    pipes: [TranslatePipe]
})
export class DashboardComponent implements OnInit {

    posts: any[] = [];

    constructor(
        private router: Router,
        private postsService: PostsService,
        private authService: AuthService
    ) { }
    
    ngOnInit() {
        this.fetchPosts();
    }

    private fetchPosts() {
        this.postsService.getPosts()
            .subscribe(posts => this.posts = posts);
    }

    onNewClicked() {
        this.router.navigate(['/posts/new']);
    }

    onEdit(post: any) {
        this.router.navigate([`/posts/${post.id}`])
    }

    onDelete(post: any) {
        // TODO: replace when md-dialog is ready
        if (window.confirm('削除してもいいですか？')) {
            this.postsService.deletePost(post.id)
                .subscribe(res => {
                    this.fetchPosts();
                });
        }
    }
}
```

## `View` にローカル変数作れるのうれしい（#）

Viewの中で `DOM` の参照をローカル変数として用意できる（#を使う）のも便利だと思いました。これのおかげでコンポーネント側に、Viewと連動する為のフィールドを別途用意する必要がなくなるケースが増えそうです。例えば今回作ったログイン画面とかで使ってます。

```html
<!-- login.component.html -->
<form>
    <div>
        <md-input placeholder="{{'Username' | translate}}" #username></md-input>
    </div>
    <div>
        <md-input placeholder="{{'Password' | translate}}" type="password" #password></md-input>
    </div>
    <button md-raised-button color="primary" (click)="onLogin(username.value, password.value)">{{'Login' | translate}}</button>
</form>

<a [routerLink]="['/signup']">{{'Signup' | translate}}</a>
```

`#username` と `#password` をそれぞれ `input` にくっつけることで、その `DOM` の参照を作れて、それをボタン押下時に `onLogin` に値を渡しています。渡ってきた値は `LoginComponent` で引数として使えます。

```ts
@Component({
    selector: 'mta-login',
    templateUrl: 'src/app/top/login/login.component.html',
    directives: [ROUTER_DIRECTIVES, MdInput, MdButton],
    pipes: [TranslatePipe]
})
export class LoginComponent {
    constructor(
        private router: Router,
        private authService: AuthService,
        private translater: TranslateService
    ) { }

    onLogin(username: string, password: string) {
        this.authService.login(username, password)
            .subscribe(user => {
                this.router.navigate(['/dashboard']);
            });
    }

}
```

`Angular1` だったら `input` 毎に `ng-model` 指定して、 `onLogin` でその値を使うイメージになると思います。

```html
<input type="text" ng-model="$ctrl.model.username" />
<input type="password" ng-model="$ctrl.model.password" />
<button type="buttoon" ng-click="$ctrl.onLogin()" />
```

```js
class HogeController {
  onLogin() {
	  	this.authService.login(this.model.username, this.model.password)
	  	// 以下略
  }
}
```

## `http` がデフォルトで `Observable` を提供してくれるのはうれしい

`.NET` 時代から `Rx` 大好き人間なので、 `Angular2` の `http` が提供してくれるのがデフォルトで `Promise` じゃなくて `Observable` なのはテンションが上がります。なんでもかんでも `Observable` でやりたくなりますね。これを気に `RxJS` の知識をもっと増やしたいと思います。

また、 `template` 側でも `asyncPipe` を使えば `Observable` を `template` で直接使うことができます！どういうことかというと、従来（例えばAngular1とか）であれば↓のようなコードをよく書いていたと思います。

```js
ngOnInit() {
    this.fetchPosts();
}

private fetchPosts() {
    this.posts = this.postsService.getPosts()
      // レスポンスをローカル変数に格納
      .subscribe(posts => this.posts = posts);
}
```

取得してきたデータをローカル変数に格納して、それを `template` で↓使います。

```html
<!-- postsをtemplateで使う -->
<md-card *ngFor="let post of posts">
    <md-card-title>{{post.title}}</md-card-title>
    <!-- 以下略 -->
</md-card>
```

これを、 `asyncPipe` を使えば `template` で直接 `Observable` を使うことができます。

```js
ngOnInit() {
    this.fetchPosts();
}

private fetchPosts() {
    // Observableをそのまま`posts`に入れる
    this.posts = this.postsService.getPosts().do(() => this.isLoading = false);
}
```

```html
<!-- postsをtemplateで使う -->
<md-card *ngFor="let post of posts | async">
    <md-card-title>{{post.title}}</md-card-title>
    <!-- 以下略 -->
</md-card>
```

一手間減っただけにしか見えないかもですが、 `Observable` を `template` で直接使えるのは便利！（なはず）

## Style Guideがあるのうれしい

`Angular1` 時代からある @johnpapa 氏の（といっても `Community Driven`?）[Style Guide](https://angular.io/styleguide)。 まだ `rc` ですし、本格的なアプリケーションも世にほとんど出ていない状態で Style Guideも何もあったもんじゃない、って思われるかもしれませんが、 `Angular1` からの流れでそのまま使える考えなどもあるのでやっぱりこういう指標があるのは使う側としてうれしいです。一人でやる分にも役に立ちますが、チームでコーディングする上でもガイドがあるのはうれしいですね。ガイドのカテゴリも

* Do: 常に守るべきルール
* Consider: 守ったほうが良いけど、自分なりに理由があるなら守らなくてOK
* Avoid: やっちゃダメなルール

のように分かれていてすごく見やすいです。

## Material Design使うの簡単。けど部品がまだまだ無い（alphaですしね）

コンポーネントで用意されてるので、そのまま使うだけです。CSSほぼ意識せずにそれっぽい見た目のものができあがるのはうれしいです。まだまだ部品が揃っていないですけど、さくっと何か創りたいときとかにい重宝しそうです。これからのアップデートに期待です！

## Angular2の公式ドキュメントが結構いろいろなところでコードが壊れている

まだ一応 `rc` といえど、公式ドキュメントのサンプルコードのリンクが割りとそこら中で壊れていて結構困りました。そういうときはもう [Github](https://github.com/angular/angular.io) を直接みて `issue` とかから現在の正解を見つけるしかない、って状態です。

## Router関連の仕様がまだ安定していない

上にも関連していますが、中でも特に `Router` の仕様が安定していなくて困りました。「ログインしていなかったら見れない画面」を作りたかったのですが、現状の仕組みでは多分無理かなって結論に達しました。 `CanActivate` が無くなってしまっていますし、あったとしても `Decorator` 内にサービスを `Inject` できないので、ログイン状態を知る術がない。（ただし、厳密には `injector` を使えば取得することは可能）
あと `path` から `name` (`ui-router` みたいなやつ） 消えちゃったりとか、デフォルトの遷移先である `useAsDefault` なくなっちゃったりとかしてて、まだまだこれからって感じがします。

## コンポーネント内で使いたいコンポーネントはdirectivesに都度指定する必要があるのがちょっとだけ手間

これは私の認識があっているのかちょっと自信が無いのですが、コンポーネント内で使いたい他のコンポーネントは全部 `Decorator` に書く必要ありそうです。なので上の例にも載せていたように、基本的に↓のような感じに `directives` 配列に使いたいコンポーネントを詰め込む感じです。

```ts
@Component({
    selector: 'mta-dashboard',
    templateUrl: 'src/app/dashboard/dashboard.component.html',
    directives: [
        ROUTER_DIRECTIVES,
        MD_PROGRESS_CIRCLE_DIRECTIVES,
        MD_LIST_DIRECTIVES,
        MD_CARD_DIRECTIVES,
        MdButton
    ],
    pipes: [TranslatePipe]
})
export class DashboardComponent implements OnInit {
```

`Angular1` は一つのグローバルスペースに全てが定義されていたので、とにかく `angular.module` で宣言しちゃえばどこででも使えたんですけど、 `Angular2` には独自の `module` なんてものは無いですし、そもそも `Angular1` でのグローバルスペースも、名前の競合とか起きると面倒くさいことになっていたので、これは改善になります。が、いざいろいろコンポーネント作ってたらこの `directives` を書くのが割りとめんどくさかったです。が、が、他のコンポーネントを `Import` してきて自分のコンポーネントで使うので、当たり前といえば当たり前ですね！

# 最後に

とにかく最初にも書きましたが、作るのすごく楽しいです。 `TypeScript` も `ES2015` も `RxJS` も使えてワクワクしちゃいます。これにさらに `ng2-redux` も組み合わせたいですね。これから `Material Design` の部品も増えていくでしょうし、徐々にこのサンプルアプリもブラッシュアップしていけたらと思います。
まとまりのない記事ですがここまで読んでくださりありがとうございます！

[ソース](https://github.com/kenfdev/ng2-simple-spa)は Githubに上げているので、よろしければ遊んでみてください。