Angular2もベータ版となり、触りだす方も多くなったと思います。しかし、Angular2のサイトではTypeScriptのサンプルは豊富ですが、ノーマルなJavaScriptで記述されているものがあまり見当たりません。
あまりAngular2を触ってませんが、学習目的で次のことを行いました。
- コンポーネントの利用
- ルータの定義と利用
- サービスの依存性注入
- カスタムフィルタの作り方
作業フォルダーを作る
mkdir Angular2Study && cd $_
必要なライブラリをインストール
npmを利用した場合
npm init -y npm install angular2@2.0.0-beta.1 --save npm install rxjs@5.0.0-beta.1 --save
HTMLとして次のように定義しライブラリを読み込ませます。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>Angular2 Study</title> </head> <body> <!-- 1. Display the application --> <!-- 2. Load libraries --> <script src="node_modules/angular2/bundles/angular2-polyfills.js"></script> <script src="node_modules/rxjs/bundles/Rx.umd.js"></script> <script src="node_modules/angular2/bundles/angular2-all.umd.js"></script> <!-- 3. Load our 'modules' --> </body> </html>
cdnの場合
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>Angular2 Study</title> </head> <body> <!-- 1. Display the application --> <!-- 2. Load libraries --> <script src="https://code.angularjs.org/2.0.0-beta.1/angular2-polyfills.min.js"></script> <script src="https://code.angularjs.org/2.0.0-beta.1/Rx.umd.min.js"></script> <script src="https://code.angularjs.org/2.0.0-beta.1/angular2-all.umd.min.js"></script> <!-- 3. Load our 'modules' --> </body> </html>
※ UMD (Universal Module Definition)
Angular2を動かしてみる
index.htmlのbodyタグに追加しAngular2を実行します
<!-- 1. Display the application --> <my-app>Loading...</my-app> <!-- 2. Load libraries --> ・・・ <!-- 3. Load our 'modules' --> <script> (function(app) { app.AppComponent = ng.core .Component({ selector: 'my-app', template: '<h1>My First Angular {{1+1}} App</h1>' }) .Class({ constructor: function() {} }); document.addEventListener('DOMContentLoaded', function() { ng.platform.browser.bootstrap(app.AppComponent); }); })(window.app || (window.app = {})); </script>
live-server 等を実行すると"My First Angular 2 App" という文字がブラウザに表示されます。
ng-appというのは無くなりAngularを実行するのはbootstrap
で行います。
参考までに、DOMContentLoaded というのはDOMの生成完了時に実行されるイベントです。
npm i live-server -g live-server
デコレータ
下記部分を見なおしてみます
app.AppComponent = ng.core .Component({ selector: 'my-app', template: '<h1>My First Angular {{1+1}} App</h1>' }) .Class({ constructor: function() {} });
ComponentやClassはデコレータできるので次のようにも書くことが出来ます。
app.AppComponent = ng.core .Class({ constructor: function() {} }); app.AppComponent = ng.core .Component({ selector: 'my-app', template: '<h1>My First Angular {{1+1}} App</h1>' })(app.AppComponent);
Angular1であった変数として引き渡す場合、つまり下記「{{name}}」のようなパターンで利用したい場合は
app.AppComponent = ng.core .Component({ selector: 'my-app', template: '<h1>My First {{name}} {{1+1}} App</h1>' })(app.AppComponent);
.Classでthis.name
を記載すればバインドされます。
app.AppComponent = ng.core .Class({ constructor: function() { this.name = 'Angular'; } });
Routing
ルーティングを行うためにいくつかのファイルを実装する必要があります。 まずコンポーネントとして
component/home/home.html component/home/home.js component/todo/todo.html component/todo/todo.js
というファイルを作成します。
HTMLは下記の通りとします。
(home.html)
<h1>Home</h1>
(todo.html)
<h1>Todo</h1>
JavaScriptはそれぞれ
(home.js)
(function(app) { app.HomeComponent = ng.core .Component({ selector: 'app-home', templateUrl: 'components/home/home.html' }) .Class({ constructor: function () {} }); })(window.app || (window.app = {}));
(todo.js)
(function(app) { app.HomeComponent = ng.core .Component({ selector: 'app-home', templateUrl: 'components/todo/todo.html' }) .Class({ constructor: function () {} }); })(window.app || (window.app = {}));
ルーティングの設定を行うJavaScriptは下記のように RouteConfig を使って定義することが可能です。ちなみに、Angular2のルーティングURLですが、デフォルトがハッシュバンではなくHTML5Modeです。
(scripts/main.js)
app.AppComponent = ng.core .Class({ constructor: function () {} }); app.AppComponent = ng.core .Component({ selector: 'app-main', template: ` <h1>Component Router</h1> <a [routerLink]="['Home']">Home</a> <a [routerLink]="['Todo']">Todos</a> <router-outlet></router-outlet> `, directives:[ ng.router.ROUTER_DIRECTIVES ] })(app.AppComponent); app.AppComponent = ng.router .RouteConfig([ {path: '/home', name: 'Home', component: app.HomeComponent, useAsDefault: true}, {path: '/todo', name: 'Todo', component: app.TodoComponent} ])(app.AppComponent); document.addEventListener('DOMContentLoaded', function() { ng.platform.browser.bootstrap(app.AppComponent, [ng.router.ROUTER_PROVIDERS]); });
ソースを追いきれてないですが、routerLink で
<a [routerLink]="['Home']">Home</a> <a [routerLink]="['Todo']">Todos</a>
を
<a [routerLink]="['home']">Home</a> <a [routerLink]="['todo']">Todos</a>
とし、ルーティングの設定を
{path: '/home', name: 'home', component: app.HomeComponent, useAsDefault: true}, {path: '/todo', name: 'todo', component: app.TodoComponent}
としてもエラー(EXCEPTION: Error during instantiation of Router! (RouterLink -> Router).)が発生します。
最後にindex.htmlに必要なファイルとディレクティブを定義すると完成です。
<!-- 1. Display the application --> <app-main>loading...</app-main> <!-- 2. Load libraries --> <script src="node_modules/angular2/bundles/angular2-polyfills.js"></script> <script src="node_modules/rxjs/bundles/Rx.umd.js"></script> <script src="node_modules/angular2/bundles/angular2-all.umd.js"></script> <!-- 3. Load our 'modules' --> <script src="components/home/home.js"></script> <script src="components/todo/todo.js"></script> <script src="scripts/main.js"></script>
今回はTypeScriptを利用せず、モダンなライブラリも利用せず、Angular1に近いコーティングでAngular2を書き振る舞いを確認し書き進めてみました。
サービスの依存性注入
ビジネスロジックを記述するサービスを定義しComponentで利用します。Angularの依存性注入にはいくつか方法があり
- アプリケーション全体での依存性注入
- コンポーネントでの依存性注入
アプリケーション全体ではBootstrap時に行います。
ng.platform.browser.bootstrap(app.AppComponent, [Service]);
ここではコンポーネントでの依存性注入を見ていきます。依存性注入させるサービスを作成します。
(service/app.service.js)
(function(app) { var App = function () { } App.prototype.someMethod = function () { return 'Some Method!'; } app.AppService = ng.core .Injectable() .Class({ constructor: App }); })(window.app || (window.app = {}));
問題はこのサービスをコンポーネントに受け渡す方法ですが、コンポーネントで
- providersを定義する
- constructor句で引数渡しする
- コンポーネントのfunction生成時に引数として取り込む
ですが、Angular1の$Injectを使ったときと似たような雰囲気です。コードは上記のhome.js
を利用すると
(home.js)
(function(app) { var Home = function (AppService) { this.name = AppService.someMethod(); } /* Componentsの登録 */ app.HomeComponent = ng.core .Component({ selector: 'app-home', templateUrl: 'components/home/home.html', providers: [app.AppService] }) .Class({ constructor: [app.AppService, Home] }); })(window.app || (window.app = {}));
最後にindex.html
にサービスを読みこませれば完了です。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <base href="/"> <title>Document</title> </head> <body> <!-- 1. Display the application --> <app-main>loading...</app-main> <!-- 2. Load libraries --> <script src="node_modules/angular2/bundles/angular2-polyfills.js"></script> <script src="node_modules/rxjs/bundles/Rx.umd.js"></script> <script src="node_modules/angular2/bundles/angular2-all.umd.js"></script> <!-- 3. Load our 'modules' --> <script src="service/app.service.js"></script> <script src="components/home/home.js"></script> <script src="components/todo/todo.js"></script> <script src="scripts/main.js"></script> </body> </html>
カスタムフィルタ
ngForを使って一覧表を作成し、簡易検索画面を作ってみます。まずHTMLは次の通り
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <!-- 1. Display the application --> <my-app>Loading...</my-app> <!-- 2. Load libraries --> <script src="node_modules/angular2/bundles/angular2-polyfills.js"></script> <script src="node_modules/rxjs/bundles/Rx.umd.js"></script> <script src="node_modules/angular2/bundles/angular2-all.umd.js"></script> <!-- 3. Load our 'modules' --> <script> (function(app) { app.AppComponent = ng.core .Component({ selector: 'my-app', template: ` <input type="text" [(ngModel)]="hoge"> <ul> <li *ngFor="#data of demoData">{{data.name}} - {{data.age}}</li> </ul> ` }) .Class({ constructor: function() { this.demoData = [ {name: '山田', age: 24}, {name: '田中', age: 28}, {name: '佐藤', age: 18}, {name: '井上', age: 32}, {name: '高橋', age: 46} ] } }); document.addEventListener('DOMContentLoaded', function() { ng.platform.browser.bootstrap(app.AppComponent); }); })(window.app || (window.app = {})); </script> </body> </html>
Angular1のデモでよく見かけたfilter
は存在せず自分で作ります。フィルターはPips
で作成します。今回のフィルターは簡易検索のため次のように定義します
(pipes/search.pipe.js)
(function(app) { app.SearchPipe = ng.core .Pipe({ name: 'search' }) .Class({ constructor: function () {}, transform(value, hoge) { return value.filter((item)=>item.name.startsWith(hoge)) } }); })(window.app || (window.app = {}));
もしくは
(function(app) { var Search = function () { } Search.prototype.transform = function (value, hoge) { return value.filter((item)=>item.name.startsWith(hoge)) } app.SearchPipe = ng.core .Pipe({ name: 'search' }) .Class({constructor: Search}); })(window.app || (window.app = {}));
HTMLは次のように書き足します
<!-- 3. Load our 'modules' --> <script src="pipes/search.pipe.js"></script> <script> (function(app) { app.AppComponent = ng.core .Component({ selector: 'my-app', template: ` <input type="text" [(ngModel)]="hoge"> <ul> <li *ngFor="#data of demoData | search:hoge">{{data.name}} - {{data.age}}</li> </ul> `, pipes: [app.SearchPipe] }) .Class({ constructor: function() { this.demoData = [ {name: '山田', age: 24}, {name: '田中', age: 28}, {name: '佐藤', age: 18}, {name: '井上', age: 32}, {name: '高橋', age: 46} ] } }); document.addEventListener('DOMContentLoaded', function() { ng.platform.browser.bootstrap(app.AppComponent); }); })(window.app || (window.app = {})); </script>
<li *ngFor="#data of demoData | search:hoge">
でsearch
フィルターを使ってます。hoge
はsearch
フィルターに対する引数になります。対応するpipes/search.pipe.js
のtransform(value, hoge)部分は「|(パイプ)」で受けた値value
と<input type="text" [(ngModel)]="hoge">
で定義されたhoge
を引数として処理します。処理内容はpipes/search.pipe.js
を確認してください。あとはコンポーネントにpipes
の定義を加えれば絞込検索を作ることができます。
超基本的なところまでは書き進められたかなと思います。もう少し綺麗に書けないかなと思うところもありますがひとまずこれでということで。ドキュメントとしてはCheat Sheet
とか見るととても良かった。
間違いとかココはこういうふうじゃない?とかありましたらご意見お願い致します