Angular2の使い方

Angular2(beta12)の使い方について簡潔に説明します
細かい部分は省くので、公式サイト等を参考にしてください
あと、β版ということで情報が正しくなくなる可能性もありますがご了承ください

インストール

npm install angular2@2.0.0-beta.12

beta版のためか依存しているパッケージがインストールされないので同様に下記もインストール

npm install es6-shim@0.35.0 reflect-metadata@0.1.2 rxjs@5.0.0-beta.2 zone.js@0.6.6

コンパイル用のパッケージとかは必要に応じてインストールしてください

一応、下記リポジトリにQuickStartを作ってあるのでお試しください
手軽に試したい場合は、Plunkerもオススメです

https://github.com/kanatapple/angular2-webpack-quick-start

使い方

アプリケーションの起動

ES6のモジュール読み込みでbootstrapとルートコンポーネントを読み込み
bootstrap関数にルートコンポーネントを渡します

// main.ts
import {bootstrap} from 'angular2/platform/browser';
import {App} from './app';

bootstrap(App, [])
  .catch(err => console.error(err));

 

// app.ts
import {Component} from 'angular2/core'

@Component({
  selector: 'my-app',
  template: `
    <div>
      <h2>Hello World</h2>
    </div>
  `
})
export class App {
}

 

// HTML
<body>
  <my-app>
    loading...
  </my-app>
</body>

@ComponentはDecorators構文といってclassなどに付加情報を設定するものです
上記ではselectorとtemplateという情報を付与しています

selectorはquerySelectorなどに渡すCSSセレクタと同じものです
CSSセレクタにマッチするタグの中にアプリケーションが展開されます
例えば、selector: 'body'とするとbodyタグの中に展開されることになります

templateはHTMLテンプレートです
この内容がCSSセレクタにマッチしたタグの中に展開されます
templateUrlとすると外部のテンプレートを設定することも可能です

動作サンプル

バインディング

単方向バインディング

モデルからビューへの反映です

<h2>{{title}}</h2>

上記のようにすると、クラスに定義されたtitleプロパティの内容が反映されます

双方向バインディング

<input type="text" [(ngModel)]="title">

上記のようにすると、inputタグ等で入力した値がクラスに定義されたプロパティに反映されます

動作サンプル

inputタグに入力した値がモデルに反映され、h2タグの中身も変わることが確認できると思います

イベントバインディング

下記のようにすると、clickなどのイベント発火時に関数を呼び出すことができます

@Component({
  selector: 'my-app',
  template: `
    <div>
      <button (click)="onClick()">Click Me</button>
    </div>
  `
})
export class App {
  private title: string = 'Hello World';
  
  private onClick(): void {
    alert('Clicked!!');
  }
}

動作サンプル

他にもタグの属性値にバインディングすることもできますが
公式サイトを参考にするとすぐに理解できると思いますので見てみてください

コンポーネント

コンポーネントが子コンポーネントを扱うにはComponent Decoratorでdirectivesを設定し
templateに子コンポーネントで定義したCSSセレクタ(タグ)を追加します

@Component({
  selector: 'my-app',
  template: `
    <div>
      <h2>Parent Component</h2>
      <child-component></child-component>
    </div>
  `,
  directives: [ChildComponent]
})

動作サンプル

制御構文

ngIf、ngFor、ngSwitchなどがあります(ngSwitchの説明は省略)

動作サンプル

ngIf

タグに*ngIf="条件式"と記述すると、条件を満たす時にタグが出力されます
条件を満たさない時はタグ自体が出力されません
条件式の記述方法はJavaScriptと同様です

ngFor

<ul>
  <li *ngFor="#alphabet of alphabets; #idx = index">[{{idx}}]{{alphabet}}</li>
</ul>

上記のように、タグに*ngFor="#変数名 of Iterableな変数"と記述すると繰り返しタグが出力されます
#変数名はLocal Template Variablesというもので、そのタグ、もしくは、子要素で使える変数です
また#変数名 = indexと記述すると、現在のインデックスが取得できます

DI(Dependency Injection)

いわゆる「依存性の注入」ってやつです
私がAngular2で一番良くできていると思っているのがこれです

注入される側

HelloServiceというクラスのインスタンスが注入されるとします
下記のように普通にクラスを定義します

export class HelloService {
  public hello(): string {
    return 'Hello Angular2!!';
  }
}

注入する側

Component Decoratorのprovidersに注入するクラス(Provider)を指定し
classのconstructorの引数で受け取るようにします

@Component({
  selector: 'my-app',
  template: `
    <div>
      <h2>{{hello}}</h2>
    </div>
  `,
  providers: [HelloService]
})
export class App {
  private hello: string;
  
  constructor(private helloService: HelloService) {
    this.hello = helloService.hello();
  }
}

上記のようにすると、Appクラスがインスタンス化される時に
自動的にHelloServiceが注入されるようになります

動作サンプル

インジェクトチェーン

この注入の仕組みですが、JavaScriptのprototypeチェーンに似ています
自身のprovidersにProviderがなければ、親のコンポーネントを探しに、親のコンポーネントになければ、さらに上に・・という感じに
そしてこのインジェクトチェーンの頂点はbootstrap関数の第2引数です
つまり、ここで指定されたProviderはどのコンポーネントでも使用できるということになります

bootstrap(App, [HelloService]);

上記で説明した以外にもDIについてはいろいろあるのですが
下記の記事が非常にわかりやすいので参考にしてみてください

http://qiita.com/laco0416/items/61eed550d1f6070b36ab

@Input

親のコンポーネントから子コンポーネントに値を渡したい時があると思います
そんな時に使うのが@Inputです
@Inputはコンポーネントに属性を定義するAPIです

export class ChildComponent {
  @Input() id: string;
  @Input('userName') name: string;
}

上記のように@Inputで定義したものが属性として扱えます
属性名と内部の名前を変えたい場合は@Input('属性名')とすると変えることが可能です

コンポーネントで下記のようにします

@Component({
  selector: 'my-app',
  template: `
    <div>
      <child-component id="0" userName="Alice"></child-component>
      <child-component id="1" [userName]="name"></child-component>
    </div>
  `,
  directives: [ChildComponent]
})
export class App {
  private name: string = 'Bob';
}

一番目のように値を直接指定して子コンポーネントに渡すこともできますし
二番目のように内部の変数の値を子コンポーネントに渡すことも可能です

また、子コンポーネントに値が渡ってくるタイミングですが
constructorではなくAngular2のライフサイクルのngOnChangesのタイミングなのでご注意を ※2016/04/15 ngOnInitと書いてましたが、ngOnChangesでした

動作サンプル

まとめ

長々と書いてきましたが、これだけおさえておけばAngular2を使う時に悩むことはないと思います
@Outputという便利な機能がありますが、今回は省略しました
私はAngular1系をあまり触ってこなかったんですが、Angular2は非常によくできてるFWだと思います
是非お試しください!!

Github Pagesを使ったhttpsページの作り方

httpsのページを作るには証明書を置いたりしないといけないので
ちょっとめんどくさかったりします
Github pagesはSSLに対応しているので、簡単に作成することが可能です

Github Pagesではユーザー用とプロジェクト(リポジトリ)用の2種類が作れます

ユーザー用

ユーザー用のページはアカウントにつき1つ作れます

リポジトリ作成

(username).github.ioというリポジトリを作成します

f:id:jewelofren:20160322112057p:plain

READMEファイルは必要ないので、「Initialize this repository with a README」には
チェックはいれません

ファイル配置

次に、masterブランチに配置したいHTMLファイルをコミットします

↓こんな感じで
https://github.com/kanatapple/kanatapple.github.io

アクセスしてみる

ユーザー用のページは(username).github.ioというURLになるのでアクセスしてみます https://kanatapple.github.io/

f:id:jewelofren:20160322113343p:plain

ちゃんとできてますね

プロジェクト用

プロジェクト用はリポジトリごとに作れます

リポジトリ作成

github-pages-sampleというリポジトリを作ってみます

f:id:jewelofren:20160322114208p:plain

READMEファイルについては「ユーザー用」と同様

ファイル配置

プロジェクト用では「gh-pages」というブランチにファイルを配置します

↓こんな感じで

https://github.com/kanatapple/github-pages-sample

masterブランチは必要ないので、作ってしまった場合は消してもOKです

アクセスしてみる

プロジェクト用のページは(username).github.io/リポジトリ名というURLになるのでアクセスしてみます https://kanatapple.github.io/github-pages-sample/

f:id:jewelofren:20160322115256p:plain

ちゃんとできてますね

まとめ

  • ユーザー用サイトは(username).github.ioというリポジトリのmasterブランチにファイルを配置する。URLはhttps://(username).github.io
  • プロジェクト用サイトは(username).github.io/リポジトリ名というリポジトリのgh-pagesブランチにファイルを配置する。URLはhttps://(username).github.io/リポジトリ名

Service Workerなどhttpsでの接続が必要なものも、Github Pagesを使えば簡単に検証できますね

Generatorsについて

SymbolIteratorと順番に説明してきました
最後にGenerators(yield)について説明します

Generatorsとは

ジェネレータとはイテレータを強力にサポートするものです

例えば、イテレータを使って1から10まで順番に表示する場合

let array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
for (let n of array) {
    console.log(n);
} 

for文使ったり、関数化すればもっと簡潔に書けますが
イテレータを使うことが前提の場合こんな感じで書けます

ジェネレータを使うとスマートに書けます

function* gfn(from, to) {
    while(from <= to) {
        yield from++;
    }
}

var g = gfn(1, 10);
for (var n of g) {
    console.log(n);
}

上記のgfnがジェネレータ関数といい、gfnから得られるオブジェクトをジェネレータといいます

ジェネレータはIterableでもあり、Iteratorでもあります

g[Symbol.iterator]
/*
[Symbol.iterator]() { [native code] }
*/

g.next()
/*
Object {value: 1, done: false}
*/

ジェネレータ関数の書き方

function* gfn() {
    yield 1;
    yield 2;
    yield 3;
}

var g = gfn();
console.log(g.next()); // Object {value: 1, done: false}
console.log(g.next()); // Object {value: 2, done: false}
console.log(g.next()); // Object {value: 2, done: false}
console.log(g.next()); // Object {value: undefined, done: true}

ジェネレータ関数を実行すると、ジェネレータを返すだけで関数自体は実行されません
nextメソッドが呼ばれると、次のyieldまで処理が進み IteratorResultが返され処理が一時停止します
最後まで処理が進むとdoneがtrueになります

ジェネレータの内部状態の変更

nextメソッドに値を渡すと、ジェネレータが一時停止する直前のyieldの結果となります

function* gfn() {
    var counter = 0;
    while (true) {
        var reset = yield counter++;
        if (reset) {
            counter = 0;
        }

    }
}

var g = gfn();
console.log(g.next()); // Object {value: 0, done: false}
console.log(g.next()); // Object {value: 1, done: false}
console.log(g.next()); // Object {value: 2, done: false}
console.log(g.next(true)); // Object {value: 0, done: false}
                           // trueを渡したので、yieldが結果がtrueとなりresetされた
console.log(g.next()); // Object {value: 1, done: false}

yield*

yield*にはIterableなオブジェクトを渡すことが可能です
前回説明しましたが、String、Array、TypedArray、Map、SetなどがIterableなオブジェクトです

function* gfn() {
    yield 1;
    yield* [2, 3, 4];
    yield* 'abc';
}

var g = gfn();
console.log(g.next()); // Object {value: 1, done: false}
console.log(g.next()); // Object {value: 2, done: false}
console.log(g.next()); // Object {value: 3, done: false}
console.log(g.next()); // Object {value: 4, done: false}
console.log(g.next()); // Object {value: "a", done: false}
console.log(g.next()); // Object {value: "b", done: false}
console.log(g.next()); // Object {value: "c", done: false}
console.log(g.next()); // Object {value: undefined, done: true}

yield*に渡された値を反復し、その値によってyieldを呼び出すイメージです 下記のような感じ

function* gfn() {
    var array = [2, 3, 4];
    for (let n of array) {
        yield n;
    }
}

また、冒頭でも書きましたが、ジェネレータはIterableでもあるのでyield*に渡せます

function* g1() {
    yield 1;
    yield* g2();
    yield 5;
}

function* g2() {
    yield 2;
    yield 3;
    yield 4;
}

var g = g1();
console.log(g.next()); // Object {value: 1, done: false}
console.log(g.next()); // Object {value: 2, done: false}
console.log(g.next()); // Object {value: 3, done: false}
console.log(g.next()); // Object {value: 4, done: false}
console.log(g.next()); // Object {value: 5, done: false}
console.log(g.next()); // Object {value: undefined, done: true}

ジェネレータを使った非同期処理

ジェネレータを使うと非同期処理を同期的に記述することができます

function async(gfn) {
    var g = gfn();
    (function next() {
        var result = g.next();
        if (!result.done) {
            result.value.then(next);
        }
    })();
}

function sleep(msec) {
    return new Promise(function(resolve) {
        setTimeout(function() {
            resolve();
        }, msec);
    });
}

async(function* (){
    console.log('start');
    yield sleep(1000);
    console.log('finish');
});

yieldにPromiseを渡すことで、Promiseがresolveされるまで処理が待機されます
ただ、上の例だとPromiseに限定した使い方しかできないので
coという便利なライブラリを使った方が確実です

まとめ

  • イテレータを強力にサポートするもの
  • Iterableでもあり、Iteratorでもある
  • functionのあとに*(アスタリスク)を記述する
  • yield、yield*を使う
  • 非同期処理を同期的に書ける

まだ自分でもそこまで把握できてない部分があるので、間違ってる箇所があればご指摘ください!
基本的な部分はおさえてあるかなって思ってます

Angular2+Webpack

Angular2の環境構築が結構面倒だったので
需要があるかわかりませんがQuickStart作ってみました

github.com

「Angular 2 Quick Start」って表示されるだけのものなので
必要に応じて肉付けしてください

lite-server使ってるんで
TypeScriptを更新すると自動でブラウザのリロードが走ります

Angular2をやりたいけど、環境構築がめんどくさいって方使ってみてください

Chromeアプリをブラウザから起動する

ブラウザからChromeアプリを起動する方法がわからなかったので調べてみた

url_handlersを使えばいいらしい(Chromeブラウザ限定です)

"url_handlers": {
  "url_handle_test": {
    "matches": [
      "http://www.example.com/*"
    ],
    "title": "URL Handle Test"
  }
}

上記のように設定しておく

この状態でhttp://www.example.com/にアクセスするとChromeアプリが起動する

ブラウザから渡したパラメータをChromeアプリで使いたい場合は
chrome.app.runtime.onLaunched.addListenerに渡したcallbackの引数から取得できる

chrome.app.runtime.onLaunched.addListener(function(launchData) {
});

http://www.example.com/?param=123456にアクセスした時のlaunchData f:id:jewelofren:20160314110357p:plain

launchDataのurlにアクセスした時のURLが渡されるので、後はゴニョゴニョすればOK
manifest.jsonで指定したtitleがどこで使われるのかは不明・・・