xengineer’s diary

結果、メモ的な内容になっています。

mysqlの基本的操作(その1)

概要

いつもDB操作するときにググるのでメモ・・・

DB作って、テーブル作って、カラムにデータ挿入して、そのデータ更新する。

あとテーブルの削除。

簡単なところでよく使うSQLをメモ。

Database作成

syntaxはこれ。

> create database <db_name>;

実際動かすとこんな感じ。

$ mysql -u<username> -p<password>

mysql> create database arel_test;
Query OK, 1 row affected (0.04 sec)

mysql> show databases;
+-----------------------+
| Database              |
+-----------------------+
| information_schema    |
| arel_test             |
| performance_schema    |
+-----------------------+
3 rows in set (0.00 sec)

Databse情報表示

いくつかあるので、以下に僕がよく使うやつを。

Database一覧表示

> show databases;

Database環境設定表示

全部表示したい場合は↓↓

> show variables;

特定のを探してる場合は↓↓

> show variables like <variable>;

例えば、character_set_databaseが見たいときは、

mysql> show variables like "character_set_database";
+------------------------+-------+
| Variable_name          | Value |
+------------------------+-------+
| character_set_database | utf8  |
+------------------------+-------+
1 row in set (0.05 sec)

Table 作成

syntaxはこれ。

> create table <table_name>(col data_type, col data_type);

col: カラム名。好きに名前をつけましょう data_type: mysqlで決められてるので、そこから選びましょう

どんなdata_typeがあるのかな、とググッてみた↓↓

MySQL Data Types

バージョンによっても多少違ったりするところはあると思うので、

一番は、各バージョンのMySQLのドキュメントを参照してちょ。

では試しに、test_tableという名前のテーブルを、こんなカラムで作ってみる。

カラム名 : data_type

  • col01 : int
  • col02 : varchar(64)
mysql> create table test_table(col01 int, col02 varchar(64));
Query OK, 0 rows affected (0.74 sec)

mysql> show tables;
+-------------------+
| Tables_in_ar_test |
+-------------------+
| test_table        |
+-------------------+
1 row in set (0.01 sec)

Table情報表示

Table一覧表示

さっき↑で使ったけど、

> show tables;

でテーブルの一覧が表示されます。

Table詳細表示

> describe <table_name>;

で、詳細でます。

例えば、こんな感じ。

mysql> desc test_table;
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| col01 | int(11)     | YES  |     | NULL    |       |
| col02 | varchar(64) | YES  |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.31 sec)

descは短縮系。

データ挿入

insert文ってやつです。

> insert into <table_name> (col, col, ...) values (val, val, ...);

最初のカッコの中に、データを挿入したいカラムを列挙して、

挿入する値自体は、次のカッコの中に、並べればOK。

さてさて、やってみよう。

mysql> insert into test_table (col01, col02) values (1, 'testvalue');
Query OK, 1 row affected (0.33 sec)

mysql> select * from test_table;
+-------+-----------+
| col01 | col02     |
+-------+-----------+
|     1 | testvalue |
+-------+-----------+
1 row in set (0.27 sec)

おー、できたできた。

確認したいので、ついでにselect文も。

> select * from <table_name>;

で、<table_name>というテーブルにある全カラム(*が全カラム、っていう意味)の、

全データが出てきます。

* に、カラム名を入れると、特定カラムだけ引っ張ってこれます。

データ更新

そのまんま。既存データを更新しますよと。

> update <table_name> set <column_name>=<value>;

例えば、さっきのテーブルに、

col01 : 1 col02 : testvalue2

っていうのを更にinsertして、col01の値を、"2"に更新してみる。

mysql> mysql> insert into test_table (col01, col02) values (1, 'testvalue2');
Query OK, 1 row affected (0.30 sec)

mysql> select * from test_table;
+-------+------------+
| col01 | col02      |
+-------+------------+
|     1 | testvalue  |
|     1 | testvalue2 |
+-------+------------+
2 rows in set (0.00 sec)

mysql> update test_table set col01=2;
Query OK, 2 rows affected (0.48 sec)
Rows matched: 2  Changed: 2  Warnings: 0

mysql> select * from test_table;
+-------+------------+
| col01 | col02      |
+-------+------------+
|     2 | testvalue  |
|     2 | testvalue2 |
+-------+------------+
2 rows in set (0.00 sec)

あー、なるほど。。。col01が両方2になった・・・。

というわけで条件をつける。

col02=='testvalue2'のカラムだけcol01を更新しよう。

そんな時は where文を使います。

> update <table_name> set <column_name>=<value> where <column_name>=<value>;

じゃ、これを使ってみる。

mysql> update test_table set col01=1 where col02='testvalue2';
Query OK, 1 row affected (0.38 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from test_table;
+-------+------------+
| col01 | col02      |
+-------+------------+
|     2 | testvalue  |
|     1 | testvalue2 |
+-------+------------+
2 rows in set (0.00 sec)

おー。できたできた。

テーブル削除

さて、最後にきれいにテーブルを削除して終わりにしますか。

> drop table <table_name>;

で、きれいさっぱりいけます。

下記実演。

mysql> show tables;
+-------------------+
| Tables_in_ar_test |
+-------------------+
| test_table        |
+-------------------+
1 row in set (0.00 sec)

mysql> drop table test_table;
Query OK, 0 rows affected (0.49 sec)

mysql> show tables;
Empty set (0.00 sec)

ほんとにただのメモですわ。

おわり。

herokuのpostgres dataのbackup/restore

今回は、herokuのpostgresからデータをbackup(export)して、

違うpostgresインスタンスにデータをrestore(import)したので、そのメモ。

まず、色んなとこに出てる、heroku pgbackups がなくなっている!

heroku pg:backupsを使えとのことでした。

ざっくりフロー

  • backupする
  • restoreする
  • 確認する

です。

backupする

バックアップ取る側のアプリ名を、<backup_app_name>としてます。 リストアする側は、<restore_app_name>ね。

$ heroku pg:backups capture --app <backup_app_name>
Use Ctrl-C at any time to stop monitoring progress; the backup
will continue running. Use heroku pg:backups info to check progress.
Stop a running backup with heroku pg:backups cancel.

HEROKU_POSTGRESQL_SILVER ---backup---> b002

Backup completed

これで、バックアップ完了。

heroku上でDBのバックアップを確認すると、

f:id:xengineer:20151106184013p:plain

こんな感じにちゃんと取れてるのが見える。

b003があるのは、二回やってみたから。

restoreする

次は、リストア。

下記コマンドを実行する。

pgbackups時代に存在した、pgbackup urlはなくなって、pg:backups public-urlになったらしい。

DATABASE_URLは、たぶん、--app <restore_app_name>でもいけると思う。

$ heroku pg:backups restore `heroku pg:backups public-url --app <backup_app_name>` DATABASE_URL

 !    WARNING: Destructive Action
 !    This command will affect the app: cedar-14-app-root4
 !    To proceed, type "cedar-14-app-root4" or re-run this command with --confirm cedar-14-app-root4

> cedar-14-app-root4
Use Ctrl-C at any time to stop monitoring progress; the backup
will continue restoring. Use heroku pg:backups to check progress.
Stop a running restore with heroku pg:backups cancel.

r001 ---restore---> DATABASE
Restore completed

これで無事データ移行完了!

確認する

一応ちゃんと確認したかったので、

$ heroku pg:psql
$ heroku pg:psql --app <restore_app_name>
---> Connecting to HEROKU_POSTGRESQL_SILVER_URL (DATABASE_URL)

psql (9.3.5, server 9.3.9)
SSL connection (cipher: DHE-RSA-AES256-SHA, bits: 256)
Type "help" for help.
cravitee::SILVER=> \z
                                       Access privileges
 Schema |            Name             |   Type   | Access privileges | Column access privileges
--------+-----------------------------+----------+-------------------+--------------------------
 public | images                      | table    |                   |
....
....
中略
....
....
 public | users                       | table    |                   |
(27 rows)

これでテーブル確認できたので、移行完了!

終わり。

herokuのstack update(Cedar-10 to Cedar-14)

概要

heroku に、アプリデプロイしようと思ったら、

「お前のアプリ、Cedar-10で、deprecatedだからだめ」

と言われまして、アップデートしました手順です。

基本的にはここに書いてあることを従順にこなしただけ。

devcenter.heroku.com

ざっくりフロー

  1. heroku toolbeltのインストール
  2. Cedar-14のお試し環境でアプリを動かしてみる
  3. 大丈夫なので本番に反映する

という流れで。

heroku toolbeltのインストール

まず、僕の環境が今回は、Ubuntu14.04.1 LTSなので、

Heroku Toolbeltこちらからインストールします。

$ wget -O- https://toolbelt.heroku.com/install-ubuntu.sh | sh

これ実行したらおっけー。

ちなみに、実行すると、↓↓に通信いきます。

http://heroku-toolbelt.s3.amazonaws.com/apt/InRelease

で、社内からの環境ではセキュリティソフトに阻まれてインストールできなかったので、

インフラの方にお願いして、通るようにしてもらってからのスタートでした。

で、まぁインストールは終了、と。

Cedar-14のお試し環境でアプリを動かしてみる

今回はここが一番面倒。

$ heroku create --remote heroku-cedar-14 --stack cedar-14 cedar-14-app

heroku createコマンドは、herokuのアプリを作成してくれるコマンド。

このコマンドでやってることは、

  • git remoteに、heroku-cedar-14を登録する
  • stackは、cedar-14を指定
  • cedar-14-appっていうアプリをherokuに作る

です。

で、実際実行したら、、、こうなった。

$ heroku create --remote heroku-cedar-14 --stack cedar-14 cedar-14-app
 !    Name is already taken

Name is already taken??

なぜだ。作った覚えはない。しかもherokuのダッシュボード上にもそんなアプリはないし。

んー、よくわからん・・・とりあえず違う名前でやってみる。

$ heroku create --remote heroku-cedar-14 --stack cedar-14 cedar-14-app-root4
Creating cedar-14-app-root4... done, stack is cedar-14
http://cedar-14-app-root4.herokuapp.com/ | git@heroku.com:my-app.git
Git remote heroku-cedar-14 added

ほう。動いた。ダッシュボード上もできてる。

この、cedar-14-app-root4っていうのを、cedar-14のお試し環境にして、

ここに、cedar-10で動いてるアプリをデプロイして動作確認してみる流れ。

ではでは、既存アプリをcloneしてデプロイ。

$ git clone <既存アプリ>
$ cd <既存アプリ>
$ git push heroku-cedar-14 master
Counting objects: 6079, done.
Compressing objects: 100% (1881/1881), done.
Writing objects: 100% (6079/6079), 985.74 KiB | 0 bytes/s, done.
Total 6079 (delta 4285), reused 5835 (delta 4064)
remote: Compressing source files... done.
remote: Building source:
remote:
..........
...中略...
..........
remote: -----> Compressing... done, 33.5MB
remote: -----> Launching... done, v5
remote:        https://cedar-14-app-root4.herokuapp.com/ deployed to Heroku ★ここにデプロイ先URLが書いてあるよ
remote:
remote: Verifying deploy... done.
To https://git.heroku.com/cedar-14-app-root4.git
 * [new branch]      master -> master

な感じで、

https://cedar-14-app-root4.herokuapp.com/にアクセス。

あれ?エラー画面。ぬぬぬ・・・。

そうか。herokuの環境設定、やってない。だわ。

heroku環境設定

既にcedar-10で動いてる環境があるので、そこからコピる。

まずは、cedar-10の環境設定を出力。

$ heroku config --app cedar-10-app
=== cedar-10-app Config Vars
AWS_ACCESS_KEY_ID:        xxxxxxxxxxx
AWS_SECRET_ACCESS_KEY:    XXXXXXXXXXXXX
DATABASE_URL:             postgres://xxxxx:yyyyy@ec2-xxx-xxx-xxx-xxx.compute-1.amazonaws.com:XXXX/yyyyyy
LANG:                     en_US.UTF-8
PAPERCLIP_HASH_SECRET:    yyyyyyyyyyyyyyyy
RACK_ENV:                 production
RAILS_ENV:                production
RAILS_SERVE_STATIC_FILES: enabled
S3_BUCKET_NAME:           xxxxxxxxxxxx
SECRET_KEY_BASE:          yyyyyyyyyyyyyy

僕はこれを、GUIからぽちぽち入力しました。

よしよし。

ではアクセス!

・・・まだエラー。

んー、↓↓コマンヅでログを確認してみた。

$ heroku logs -n 200
$ heroku logs -t

そうか。rake db:migrate、やってない。だわ。

↓↓実行。

$ heroku run rake db:migrate

これでOK!!!!

https://cedar-14-app-root4.herokuapp.com/

にアクセスしたら、いけましたーー!!!

動作確認も無事完了したので、次は本番反映。

大丈夫なので本番に反映する

公式Webにもある通り、下記コマンドを実行しますよ、と。

$ heroku stack:migrate cedar-14 -a cravitee
Stack set. Next release on cravitee will use cedar-14.
Run `git push heroku master` to create a new release on cedar-14.

ふむふむ。次のリリースからは、cedar-14になるよ。

次のリリースを作って、cedar-14を試すために、git push heroku masterを実行してみ、と。

変更することがない方は、下記コマンドで空っぽコミットを・・・

 git commit --allow-empty -m "Upgrading to Cedar-14"
[master xxxxxx] Upgrading to Cedar-14

からのこれ。

$ git push heroku master

僕は、普段toolbeltからやってないので、普通にPushして、

github → wercker → heroku

でデプロイして確認しました。

ふむふむ。

f:id:xengineer:20151106182214p:plain

ちゃんと Cedar-14になっておるー。

終わり。

angularjs2のtutorialをやってみた(その3)

お断り事項

本記事は、Angularjs 公式サイトのチュートリアルを

純粋にやってみているだけです。

英語苦手な方には悪くないと思いますが、本家のほうが、アップデートもかかるし、

英語問題ない方は本家をみたほうがよいです。

5分クイックスタートをやってみたときの記事は以下から。

angularjs2のtutorialをやってみた - xengineer’s diary

Tour of Heroesの前半をやってみたときの記事は以下から。

angularjs2のtutorialをやってみた(その2) - xengineer’s diary

概要

今回やるのは、下記リンクにある、"Tour of Heroes"というチュートリアルです。

Tutorial: Tour of Heroes - ts ←これは、前回の記事で書いた Master/Detail - ts ←今回はこれをやってみる

前回までは、↓↓こんな感じで、

f:id:xengineer:20151105104239p:plain

  • アプリ名(Tour of Heroes)表示
  • ヒーローの詳細表示(上記では、Windstormの詳細)
  • ヒーロー名をテキストフィールドで編集すると、"Windstorm details!"の名前部分が変わる

というところまでやりました。

本来の最終目標は、これに、

  • ヒーローをリスト表示する
  • リストからヒーローを選択して、詳細表示する

上記機能が追加されたところまでなので、今回はそこまでやる予定。

前提条件確認などなど

前提条件:

  • OS: Ubuntu 14.04.1 LTS
  • npm: 2.14.7
  • angularjs: 2.0.0-alpha.44
  • systemjs: 0.19.5
  • live-server: 0.8.1
  • typescript: 1.6.2
  • Linuxの操作には慣れている(開発にあたっての環境整備はできるレベル)
  • AngularjsのチュートリアルMaster/Detail - ts、をやっていること

まずは、前回(Master/Detail - ts)の記事の最後と同じ状態のディレクトリ構造になってるかの確認を。

↓↓こうなってればいい。

angular2-tour-of-heroes
  |
  +--- node_modules
  |
  +--- src
  |     +---app
  |     |    |
  |     |    +-- app.ts
  |     |
  |     +-- index.html
  |     +-- tsconfig.json
  |
  +── package.json 

では、この状態で、tsc/live-serverは起動しちゃいます。

$npm run tsc
$npm start

ざっくりフロー

本記事のざっくりした流れです。

  1. 早速!Heroリスト表示
  2. ヒーローを選ぶ!
  3. ヒーローをクリックしてリストから選択するまで
  4. directive配列肥大について

詳細

以降ではやったことを少し詳細に。

早速!Heroリスト表示

ヒーロー生成!

ついにヒーローたちを沢山召喚できます。

これ↓↓を、app.tsに追記しましょう。

たぶんどこでもよさそう。

var HEROES: Hero[] = [
  { "id": 11, "name": "Mr. Nice" },
  { "id": 12, "name": "Narco" },
  { "id": 13, "name": "Bombasto" },
  { "id": 14, "name": "Celeritas" },
  { "id": 15, "name": "Magneta" },
  { "id": 16, "name": "RubberMan" },
  { "id": 17, "name": "Dynama" },
  { "id": 18, "name": "Dr IQ" },
  { "id": 19, "name": "Magma" },
  { "id": 20, "name": "Tornado" }
];

入力して追加していく方式ではなくて、一旦は配列でこっちから与える方式で表示なので、

まずは、Heroクラスの配列を作ってるのですね。

この状態だと、AppComponent内に定義されてないので、AppComponent内に↓↓を追記して宣言します。

public heroes = HEROES;

ヒーロー表示!!(準備)

次は表示します。

templateに↓↓を追記します。(これは完全に下準備)

この、li tagの中で、heroesをloopさせて表示する魂胆ですな。

<h2>My Heroes</h2>
<ul class="heroes">
  <li>
    <!-- each hero goes here -->
  </li>
</ul>

位置的には、↓↓の行の上に突っ込みましょう。

<h2>{{hero.name}} details!</h2>

ヒーロー表示!!(angularのfor文)

次に、まさにヒーローをループさせる処理を書きます。

angularで、loop処理書くには、NgForなるdirectiveを使うようです。

さっき書いたtemplateの、li tagに、*ng-for="#hero of heroes"なるdirectiveを追記します。

<li *ng-for="#hero of heroes">
  <span class="badge">{{hero.id}}</span> {{hero.name}}
</li>

こんな感じ。

*がついているのは、li elementとその子elementが、master templateを構成してるってことを示すから、

らしいんだけど、master template?なんだろうね。そのうちわかったらまとめよう・・・今は見てみぬ振り。

"#hero of heroes" は、heros配列を、ローカル変数、heroに入れてループしなさいな、ってことらしいです。

#heroって、シャープを冒頭につけて書くと、ローカル変数になるんだと。

はい、ではここまでを反映した版。

import {bootstrap, Component, FORM_DIRECTIVES} from 'angular2/angular2';

class Hero {
  id: number;
  name: string;
}

var HEROES: Hero[] = [               ★Heroの配列作成を追記
  { "id": 11, "name": "Mr. Nice" },
  { "id": 12, "name": "Narco" },
  { "id": 13, "name": "Bombasto" },
  { "id": 14, "name": "Celeritas" },
  { "id": 15, "name": "Magneta" },
  { "id": 16, "name": "RubberMan" },
  { "id": 17, "name": "Dynama" },
  { "id": 18, "name": "Dr IQ" },
  { "id": 19, "name": "Magma" },
  { "id": 20, "name": "Tornado" }
];

// Annotation section
@Component({
  selector: 'my-app',
  template: `
    <h1>{{title}}</h1>
    <h2>My Heroes</h2>  ★★★ここからヒーローたち表示追記★★★
    <ul class="heroes">
      <li *ng-for="#hero of heroes">
        <span class="badge">{{hero.id}}</span> {{hero.name}}
      </li>
    </ul>               ★★★★★★★★ここまで★★★★★★★★
    <h2>{{hero.name}} details!</h2>
    <div><label>id: </label>{{hero.id}}</div>
    <label>name: </label>
    <div><input [(ng-model)]="hero.name" placeholder="name"></div>
    `,
  directives: [FORM_DIRECTIVES]
})
// Component controller

class AppComponent {
  public title = 'Tour of Heroes';
  public heroes = HEROES;
  public hero: Hero = {
    id: 1,
    name: 'Windstorm'
  };
}

bootstrap(AppComponent);

よーし!これで表示されるかなー!

http://127.0.0.1:8080

にアクセスすると・・・

↓↓エラーがでます。Developer toolに(ブラウザには何も表示されない)

EXCEPTION: Template parse errors:
Can't bind to 'ngForOf' since it isn't a known native property in AppComponent > ul:nth-child(5) > li:nth-child(1)[*ng-for=#hero of heroes]
Property binding ngForOf not used by any directive on an embedded template in AppComponent > ul:nth-child(5) > li:nth-child(1)[*ng-for=#hero of heroes]
angular2.dev.js:21835 EXCEPTION: Template parse errors:
Can't bind to 'ngForOf' since it isn't a known native property in AppComponent > ul:nth-child(5) > li:nth-child(1)[*ng-for=#hero of heroes]
Property binding ngForOf not used by any directive on an embedded template in AppComponent > ul:nth-child(5) > li:nth-child(1)[*ng-for=#hero of heroes]BrowserDomAdapter.logError @ angular2.dev.js:21835BrowserDomAdapter.logGroup @ angular2.dev.js:21846ExceptionHandler.call @ angular2.dev.js:4431(anonymous function) @ angular2.dev.js:19543NgZone._onError @ angular2.dev.js:10711errorHandling.onError @ angular2.dev.js:10630run @ angular2.dev.js:141(anonymous function) @ angular2.dev.js:10644zoneBoundFn @ angular2.dev.js:111lib$es6$promise$$internal$$tryCatch @ angular2.dev.js:1507lib$es6$promise$$internal$$invokeCallback @ angular2.dev.js:1519lib$es6$promise$$internal$$publish @ angular2.dev.js:1490(anonymous function) @ angular2.dev.js:219microtask @ angular2.dev.js:10670run @ angular2.dev.js:138(anonymous function) @ angular2.dev.js:10644zoneBoundFn @ angular2.dev.js:111lib$es6$promise$asap$$flush @ angular2.dev.js:1301
angular2.dev.js:21835 STACKTRACE:BrowserDomAdapter.logError @ angular2.dev.js:21835ExceptionHandler.call @ angular2.dev.js:4433(anonymous function) @ angular2.dev.js:19543NgZone._onError @ angular2.dev.js:10711errorHandling.onError @ angular2.dev.js:10630run @ angular2.dev.js:141(anonymous function) @ angular2.dev.js:10644zoneBoundFn @ angular2.dev.js:111lib$es6$promise$$internal$$tryCatch @ angular2.dev.js:1507lib$es6$promise$$internal$$invokeCallback @ angular2.dev.js:1519lib$es6$promise$$internal$$publish @ angular2.dev.js:1490(anonymous function) @ angular2.dev.js:219microtask @ angular2.dev.js:10670run @ angular2.dev.js:138(anonymous function) @ angular2.dev.js:10644zoneBoundFn @ angular2.dev.js:111lib$es6$promise$asap$$flush @ angular2.dev.js:1301
angular2.dev.js:21835 Error: Template parse errors:
Can't bind to 'ngForOf' since it isn't a known native property in AppComponent > ul:nth-child(5) > li:nth-child(1)[*ng-for=#hero of heroes]
Property binding ngForOf not used by any directive on an embedded template in AppComponent > ul:nth-child(5) > li:nth-child(1)[*ng-for=#hero of heroes]
    at new BaseException (angular2.dev.js:16034)
    at TemplateParser.parse (angular2.dev.js:21192)
    at angular2.dev.js:24913
    at Zone.run (angular2.dev.js:138)
    at Zone.run (angular2.dev.js:10644)
    at zoneBoundFn (angular2.dev.js:111)
    at lib$es6$promise$$internal$$tryCatch (angular2.dev.js:1507)
    at lib$es6$promise$$internal$$invokeCallback (angular2.dev.js:1519)
    at lib$es6$promise$$internal$$publish (angular2.dev.js:1490)
    at angular2.dev.js:219

理由は、エラーに書いてあるとおりで、NgForってなんだよ!ちゃんとimportしてね。

ということなので、importと、@Componentのdirectivesに、NgForを追記した版。

import {bootstrap, Component, FORM_DIRECTIVES, NgFor} from 'angular2/angular2';  ★NgFor追記

class Hero {
  id: number;
  name: string;
}

var HEROES: Hero[] = [
  { "id": 11, "name": "Mr. Nice" },
  { "id": 12, "name": "Narco" },
  { "id": 13, "name": "Bombasto" },
  { "id": 14, "name": "Celeritas" },
  { "id": 15, "name": "Magneta" },
  { "id": 16, "name": "RubberMan" },
  { "id": 17, "name": "Dynama" },
  { "id": 18, "name": "Dr IQ" },
  { "id": 19, "name": "Magma" },
  { "id": 20, "name": "Tornado" }
];

// Annotation section
@Component({
  selector: 'my-app',
  template: `
    <h1>{{title}}</h1>
    <h2>My Heroes</h2>
    <ul class="heroes">
      <li *ng-for="#hero of heroes">
        <span class="badge">{{hero.id}}</span> {{hero.name}}
      </li>
    </ul>
    <h2>{{hero.name}} details!</h2>
    <div><label>id: </label>{{hero.id}}</div>
    <label>name: </label>
    <div><input [(ng-model)]="hero.name" placeholder="name"></div>
    `,
  directives: [FORM_DIRECTIVES, NgFor]   ★NgFor追記
})
// Component controller

class AppComponent {
  public title = 'Tour of Heroes';
  public heroes = HEROES;
  public hero: Hero = {
    id: 1,
    name: 'Windstorm'
  };
}

bootstrap(AppComponent);

はい!やっと表示されました↓↓

見た目しょぼいけど。

f:id:xengineer:20151105124418p:plain

見た目がしょぼすぎるので、StyleSheetをあててみます。

styles:[`
  .heroes {list-style-type: none; margin-left: 1em; padding: 0; width: 10em;}
  .heroes li { cursor: pointer; position: relative; left: 0; transition: all 0.2s ease; }
  .heroes li:hover {color: #369; background-color: #EEE; left: .2em;}
  .heroes .badge {
    font-size: small;
    color: white;
    padding: 0.1em 0.7em;
    background-color: #369;
    line-height: 1em;
    position: relative;
    left: -1px;
    top: -1px;
  }
  .selected { background-color: #EEE; color: #369; }
  `],

こんなStyleSheet。badgeと、heroes classのtransitionの設定と。

実際の見た目。

f:id:xengineer:20151105134006g:plain

StyleSheet追記した版↓↓

import {bootstrap, Component, FORM_DIRECTIVES, NgFor} from 'angular2/angular2';

class Hero {
  id: number;
  name: string;
}

var HEROES: Hero[] = [
  { "id": 11, "name": "Mr. Nice" },
  { "id": 12, "name": "Narco" },
  { "id": 13, "name": "Bombasto" },
  { "id": 14, "name": "Celeritas" },
  { "id": 15, "name": "Magneta" },
  { "id": 16, "name": "RubberMan" },
  { "id": 17, "name": "Dynama" },
  { "id": 18, "name": "Dr IQ" },
  { "id": 19, "name": "Magma" },
  { "id": 20, "name": "Tornado" }
];

// Annotation section
@Component({
  selector: 'my-app',
  styles:[`   ★stylesheet追記
  .heroes {list-style-type: none; margin-left: 1em; padding: 0; width: 10em;}
  .heroes li { cursor: pointer; position: relative; left: 0; transition: all 0.2s ease; }
  .heroes li:hover {color: #369; background-color: #EEE; left: .2em;}
  .heroes .badge {
    font-size: small;
    color: white;
    padding: 0.1em 0.7em;
    background-color: #369;
    line-height: 1em;
    position: relative;
    left: -1px;
    top: -1px;
  }
  .selected { background-color: #EEE; color: #369; }
  `],
  template: `
    <h1>{{title}}</h1>
    <h2>My Heroes</h2>
    <ul class="heroes">
      <li *ng-for="#hero of heroes">
        <span class="badge">{{hero.id}}</span> {{hero.name}}
      </li>
    </ul>
    <h2>{{hero.name}} details!</h2>
    <div><label>id: </label>{{hero.id}}</div>
    <label>name: </label>
    <div><input [(ng-model)]="hero.name" placeholder="name"></div>
    `,
  directives: [FORM_DIRECTIVES, NgFor]
})
// Component controller

class AppComponent {
  public title = 'Tour of Heroes';
  public heroes = HEROES;
  public hero: Hero = {
    id: 1,
    name: 'Windstorm'
  };
}

bootstrap(AppComponent);

TypeScriptの中にCSS書くのやだなぁ。

ちなみに、こういう感じに書くと、このStyleは、AppComponentの中にしか影響は及ぼさないとのこと。

それはとてもいい。でも書く場所は切り離したい。

ヒーローを選ぶ!

ヒーローたちを表示するところまでは平和に完了したので、

次は表示されてるヒーローたちから、一人選択する機能。

現状はこれ↓↓ f:id:xengineer:20151105134006g:plain

つまり、ヒーローたちのリストがあって、その下に、1人のヒーローの詳細が

表示してある。(今は、Windstormさん固定)

この詳細表示のところに、リストから選択したヒーローの内容を反映させる機能をつけます。

(このUIパターンは、"master-detail"っていう一般的なパターンらしい)

master : ヒーローたちのリスト detail : 選択されたヒーロー

ヒーローをクリックしてリストから選択するまで

「ヒーローをクリックしてリストから選択するまで」は、

  1. クリックイベントを取得する
  2. 「ヒーローが選択された状態」を作る
  3. ヒーローがクリックされたら、当該ヒーローを選択する

の3ステップあります。

まずひとつめの、

クリックイベントを取得する

から。

これは、li tagに仕込みます。↓↓のように、(click)="onSelect(hero)"を設定すればOK。

<li *ng-for="#hero of heroes" (click)="onSelect(hero)">
  <span class="badge">{{hero.id}}</span> {{hero.name}}
</li>

hero は、ng-for文でheroesをループさせたときのloop内の変数。

onSelectメソッドは、まだ存在しないやーつ。これがクリックしたヒーローを選択状態にするメソッドになる予定。

次は、

「ヒーローが選択された状態」を作る

AppComponentの、

  public hero: Hero = {
    id: 1,
    name: 'Windstorm'
  };

これ。は、staticで固定されすぎなので、選択したヒーローを表示するように下記に変更します。

public selectedHero: Hero;

今回は、ユーザが選択するまでは、ヒーロー詳細は表示しないようにするらしいので、Heroは初期化不要。

そして、onSelectメソッドを、AppComponentに追記。

onSelect(hero: Hero) { this.selectedHero = hero; }

これで、selectedHeroに、クリックされたheroが格納される。

でも現状だと、templateの中が、以前のままで、selectedHeroが登場しないので、下記のように修正。

<h2>{{selectedHero.name}} details!</h2>
<div><label>id: </label>{{selectedHero.id}}</div>
<div>
    <label>name: </label>
    <input [(ng-model)]="selectedHero.name" placeholder="name"></input>
</div>

この状態で、ブラウザをリロードすると、、、こんなエラー。ちーん。

EXCEPTION: TypeError: Cannot read property 'name' of undefined in [null]
angular2.dev.js:21835 EXCEPTION: TypeError: Cannot read property 'name' of undefined in [null]BrowserDomAdapter.logError @ angular2.dev.js:21835BrowserDomAdapter.logGroup @ angular2.dev.js:21846ExceptionHandler.call @ angular2.dev.js:4431(anonymous function) @ angular2.dev.js:19543NgZone._onError @ angular2.dev.js:10711errorHandling.onError @ angular2.dev.js:10630run @ angular2.dev.js:141(anonymous function) @ angular2.dev.js:10644zoneBoundFn @ angular2.dev.js:111lib$es6$promise$$internal$$tryCatch @ angular2.dev.js:1507lib$es6$promise$$internal$$invokeCallback @ angular2.dev.js:1519lib$es6$promise$$internal$$publish @ angular2.dev.js:1490(anonymous function) @ angular2.dev.js:219microtask @ angular2.dev.js:10670run @ angular2.dev.js:138(anonymous function) @ angular2.dev.js:10644zoneBoundFn @ angular2.dev.js:111lib$es6$promise$asap$$flush @ angular2.dev.js:1301
angular2.dev.js:21835 ORIGINAL EXCEPTION: TypeError: Cannot read property 'name' of undefinedBrowserDomAdapter.logError @ angular2.dev.js:21835ExceptionHandler.call @ angular2.dev.js:4440(anonymous function) @ angular2.dev.js:19543NgZone._onError @ angular2.dev.js:10711errorHandling.onError @ angular2.dev.js:10630run @ angular2.dev.js:141(anonymous function) @ angular2.dev.js:10644zoneBoundFn @ angular2.dev.js:111lib$es6$promise$$internal$$tryCatch @ angular2.dev.js:1507lib$es6$promise$$internal$$invokeCallback @ angular2.dev.js:1519lib$es6$promise$$internal$$publish @ angular2.dev.js:1490(anonymous function) @ angular2.dev.js:219microtask @ angular2.dev.js:10670run @ angular2.dev.js:138(anonymous function) @ angular2.dev.js:10644zoneBoundFn @ angular2.dev.js:111lib$es6$promise$asap$$flush @ angular2.dev.js:1301
angular2.dev.js:21835 ORIGINAL STACKTRACE:BrowserDomAdapter.logError @ angular2.dev.js:21835ExceptionHandler.call @ angular2.dev.js:4443(anonymous function) @ angular2.dev.js:19543NgZone._onError @ angular2.dev.js:10711errorHandling.onError @ angular2.dev.js:10630run @ angular2.dev.js:141(anonymous function) @ angular2.dev.js:10644zoneBoundFn @ angular2.dev.js:111lib$es6$promise$$internal$$tryCatch @ angular2.dev.js:1507lib$es6$promise$$internal$$invokeCallback @ angular2.dev.js:1519lib$es6$promise$$internal$$publish @ angular2.dev.js:1490(anonymous function) @ angular2.dev.js:219microtask @ angular2.dev.js:10670run @ angular2.dev.js:138(anonymous function) @ angular2.dev.js:10644zoneBoundFn @ angular2.dev.js:111lib$es6$promise$asap$$flush @ angular2.dev.js:1301
angular2.dev.js:21835 TypeError: Cannot read property 'name' of undefined
    at AbstractChangeDetector.ChangeDetector_AppComponent_0.detectChangesInRecordsInternal (eval at <anonymous> (http://127.0.0.1:8080/node_modules/angular2/bundles/angular2.dev.js:20415:14), <anonymous>:94:32)
    at AbstractChangeDetector.detectChangesInRecords (http://127.0.0.1:8080/node_modules/angular2/bundles/angular2.dev.js:20209:14)
    at AbstractChangeDetector.runDetectChanges (http://127.0.0.1:8080/node_modules/angular2/bundles/angular2.dev.js:20192:12)
    at AbstractChangeDetector._detectChangesInShadowDomChildren (http://127.0.0.1:8080/node_modules/angular2/bundles/angular2.dev.js:20259:14)
    at AbstractChangeDetector.runDetectChanges (http://127.0.0.1:8080/node_modules/angular2/bundles/angular2.dev.js:20196:12)
    at AbstractChangeDetector.detectChanges (http://127.0.0.1:8080/node_modules/angular2/bundles/angular2.dev.js:20183:12)
    at http://127.0.0.1:8080/node_modules/angular2/bundles/angular2.dev.js:14960:27
    at Array.forEach (native)
    at LifeCycle_.tick (http://127.0.0.1:8080/node_modules/angular2/bundles/angular2.dev.js:14959:31)
    at tick (http://127.0.0.1:8080/node_modules/angular2/bundles/angular2.dev.js:19629:16)BrowserDomAdapter.logError @ angular2.dev.js:21835ExceptionHandler.call @ angular2.dev.js:4444(anonymous function) @ angular2.dev.js:19543NgZone._onError @ angular2.dev.js:10711errorHandling.onError @ angular2.dev.js:10630run @ angular2.dev.js:141(anonymous function) @ angular2.dev.js:10644zoneBoundFn @ angular2.dev.js:111lib$es6$promise$$internal$$tryCatch @ angular2.dev.js:1507lib$es6$promise$$internal$$invokeCallback @ angular2.dev.js:1519lib$es6$promise$$internal$$publish @ angular2.dev.js:1490(anonymous function) @ angular2.dev.js:219microtask @ angular2.dev.js:10670run @ angular2.dev.js:138(anonymous function) @ angular2.dev.js:10644zoneBoundFn @ angular2.dev.js:111lib$es6$promise$asap$$flush @ angular2.dev.js:1301
angular2.dev.js:21835 ERROR CONTEXT:BrowserDomAdapter.logError @ angular2.dev.js:21835ExceptionHandler.call @ angular2.dev.js:4447(anonymous function) @ angular2.dev.js:19543NgZone._onError @ angular2.dev.js:10711errorHandling.onError @ angular2.dev.js:10630run @ angular2.dev.js:141(anonymous function) @ angular2.dev.js:10644zoneBoundFn @ angular2.dev.js:111lib$es6$promise$$internal$$tryCatch @ angular2.dev.js:1507lib$es6$promise$$internal$$invokeCallback @ angular2.dev.js:1519lib$es6$promise$$internal$$publish @ angular2.dev.js:1490(anonymous function) @ angular2.dev.js:219microtask @ angular2.dev.js:10670run @ angular2.dev.js:138(anonymous function) @ angular2.dev.js:10644zoneBoundFn @ angular2.dev.js:111lib$es6$promise$asap$$flush @ angular2.dev.js:1301
angular2.dev.js:21835 _Context {element: input, componentElement: my-app, context: AppComponent, locals: Object, injector: Injector

これは、最初、selectedHeroは、空っぽなので、表示がおかしくなってるみたいで、

selctedHeroが undefinedのときは、そもそもヒーロー詳細は表示しない、

と、書き換えておきます。

<div *ng-if="selectedHero">
  <h2>{{selectedHero.name}} details!</h2>
  <div><label>id: </label>{{selectedHero.id}}</div>
  <div>
    <label>name: </label>
    <input [(ng-model)]="selectedHero.name" placeholder="name"></input>
  </div>
</div>

で、ここで登場する謎の、ng-if directive。でもまぁif文だよね。

例によって、importとdirectivesに追記して・・・

って、いう版↓↓

import {bootstrap, Component, FORM_DIRECTIVES, NgFor, NgIf} from 'angular2/angular2'; ★NgIf追記

class Hero {
  id: number;
  name: string;
}

var HEROES: Hero[] = [
  { "id": 11, "name": "Mr. Nice" },
  { "id": 12, "name": "Narco" },
  { "id": 13, "name": "Bombasto" },
  { "id": 14, "name": "Celeritas" },
  { "id": 15, "name": "Magneta" },
  { "id": 16, "name": "RubberMan" },
  { "id": 17, "name": "Dynama" },
  { "id": 18, "name": "Dr IQ" },
  { "id": 19, "name": "Magma" },
  { "id": 20, "name": "Tornado" }
];

// Annotation section
@Component({
  selector: 'my-app',
  styles:[`
  .heroes {list-style-type: none; margin-left: 1em; padding: 0; width: 10em;}
  .heroes li { cursor: pointer; position: relative; left: 0; transition: all 0.2s ease; }
  .heroes li:hover {color: #369; background-color: #EEE; left: .2em;}
  .heroes .badge {
    font-size: small;
    color: white;
    padding: 0.1em 0.7em;
    background-color: #369;
    line-height: 1em;
    position: relative;
    left: -1px;
    top: -1px;
  }
  .selected { background-color: #EEE; color: #369; }
  `],
  template: `
    <h1>{{title}}</h1>
    <h2>My Heroes</h2>
    <ul class="heroes">
      <li *ng-for="#hero of heroes" (click)="onSelect(hero)"> ★クリックイベント取得
        <span class="badge">{{hero.id}}</span> {{hero.name}}
      </li>
    </ul>
    <div *ng-if="selectedHero">   ★★★ここから修正★★★
      <h2>{{selectedHero.name}} details!</h2>
      <div><label>id: </label>{{selectedHero.id}}</div>
      <div>
        <label>name: </label>
        <input [(ng-model)]="selectedHero.name" placeholder="name"></input>
      </div>
    </div>            ★★★★ここまで★★★★
    `,
  directives: [FORM_DIRECTIVES, NgFor, NgIf] ★NgIf追記
})
// Component controller

class AppComponent {
  public title = 'Tour of Heroes';
  public heroes = HEROES;
  public selectedHero: Hero;      ★選択されたヒーロー格納用
  onSelect(hero: Hero) { this.selectedHero = hero; } ★onSelectメソッド追記
}

bootstrap(AppComponent);

結果は、こんな感じ↓↓↓ f:id:xengineer:20151105164620g:plain

  • ヒーローたちがリスト表示になってて
  • 選択できて
  • 選択すると詳細が表示されて
  • 名前を編集できる

おー。すばらC。

選択中のヒーローを見つけやすくする

現状、詳細情報をみれば、誰が選択されてるかがわかるけど、

リストの中から探すのはちょっと大変。

なので、選択中のヒーローのli tagに、selectedっていうclassをつけます。

で、StyleSheetで、そこはちょっと色つける、とかやる。

まずは、選択されてるヒーローを取得するメソッドを作ります。

getSelectedClass(hero: Hero) {
  return { 'selected': hero === this.selectedHero };
}

これを、AppComponentに追記すると、AppComponentの、member?の、

selectedHeroが、li tagのheroと等しいかどうか次第で、classに、selectedをつけたり外したりしてくれる。

で、liに、property bindingで、ng-class directiveなるものを入れてあげる。

property bindingは、きっとCSSの値変えられたりするんだろうな。

<li *ng-for="#hero of heroes"
  [ng-class]="getSelectedClass(hero)"
  (click)="onSelect(hero)">
  <span class="badge">{{hero.id}}</span> {{hero.name}}
</li>

新しいdirective使っちゃったんで、またimportとdirectivesに追記。

import {bootstrap, Component, 
        FORM_DIRECTIVES, NgClass, NgFor, NgIf} from 'angular2/angular2';
directives: [FORM_DIRECTIVES, NgClass, NgFor, NgIf]

で、これを全部反映した版↓↓

import {bootstrap, Component, FORM_DIRECTIVES, NgClass, NgFor, NgIf} from 'angular2/angular2'; ★ここを変更

class Hero {
  id: number;
  name: string;
}

var HEROES: Hero[] = [
  { "id": 11, "name": "Mr. Nice" },
  { "id": 12, "name": "Narco" },
  { "id": 13, "name": "Bombasto" },
  { "id": 14, "name": "Celeritas" },
  { "id": 15, "name": "Magneta" },
  { "id": 16, "name": "RubberMan" },
  { "id": 17, "name": "Dynama" },
  { "id": 18, "name": "Dr IQ" },
  { "id": 19, "name": "Magma" },
  { "id": 20, "name": "Tornado" }
];

// Annotation section
@Component({
  selector: 'my-app',
  styles:[`
  .heroes {list-style-type: none; margin-left: 1em; padding: 0; width: 10em;}
  .heroes li { cursor: pointer; position: relative; left: 0; transition: all 0.2s ease; }
  .heroes li:hover {color: #369; background-color: #EEE; left: .2em;}
  .heroes .badge {
    font-size: small;
    color: white;
    padding: 0.1em 0.7em;
    background-color: #369;
    line-height: 1em;
    position: relative;
    left: -1px;
    top: -1px;
  }
  .selected { background-color: #EEE; color: #369; }
  `],
  template: `
    <h1>{{title}}</h1>
    <h2>My Heroes</h2>
    <ul class="heroes">
    <li *ng-for="#hero of heroes"
      [ng-class]="getSelectedClass(hero)"★ここを追記
      (click)="onSelect(hero)">
      <span class="badge">{{hero.id}}</span> {{hero.name}}
    </li>
    </ul>
    <div *ng-if="selectedHero">
      <h2>{{selectedHero.name}} details!</h2>
      <div><label>id: </label>{{selectedHero.id}}</div>
      <div>
        <label>name: </label>
        <input [(ng-model)]="selectedHero.name" placeholder="name"></input>
      </div>
    </div>
    `,
  directives: [FORM_DIRECTIVES, NgClass, NgFor, NgIf] ★ここを変更
})
// Component controller

class AppComponent {
  public title = 'Tour of Heroes';
  public heroes = HEROES;
  public selectedHero: Hero;
  onSelect(hero: Hero) { this.selectedHero = hero; }
  getSelectedClass(hero: Hero) {
    return { 'selected': hero === this.selectedHero };
  }
}

bootstrap(AppComponent);

そうすると、こう動く。

おお、選択されている。

f:id:xengineer:20151105173149g:plain

でもこれでいまいち理解できてないのは、

ng-classの挙動。

<li *ng-for="#hero of heroes"
  [ng-class]="getSelectedClass(hero)"
  (click)="onSelect(hero)">
  <span class="badge">{{hero.id}}</span> {{hero.name}}
</li>

これは描画時に発動するのかなぁ・・・

こう書いてあるということは、clickされた場合って、onSelectが呼び出されるけど、

呼びだされたあとに、ng-classが呼び出されるのかなぁ。

この辺がよくわからない。。。

directive配列肥大について

こんな小さいアプリ書いてるだけで、

こんなにdirective書かないといけないとなると、あとが怖いですね。

import {bootstrap, Component, FORM_DIRECTIVES, NgClass, NgFor, NgIf} from 'angular2/angular2';

それの改善ができるみたいです。

↑↑のimportの中の、FORM_DIRECTIVESは、ng-modelを利用するにあたって必要なdirectiveの詰め合わせでした。

他にもそういうのがあるようで。

今回使った、NgClass, NgFor, NgIfは、CORE_DIRECTIVES配列につめあわされているので、

それを書いておけばOK!!!だそうな。

というわけで、最終版↓↓

import {bootstrap, Component, FORM_DIRECTIVES, CORE_DIRECTIVES} from 'angular2/angular2'; ★ここを変更

class Hero {
  id: number;
  name: string;
}

var HEROES: Hero[] = [
  { "id": 11, "name": "Mr. Nice" },
  { "id": 12, "name": "Narco" },
  { "id": 13, "name": "Bombasto" },
  { "id": 14, "name": "Celeritas" },
  { "id": 15, "name": "Magneta" },
  { "id": 16, "name": "RubberMan" },
  { "id": 17, "name": "Dynama" },
  { "id": 18, "name": "Dr IQ" },
  { "id": 19, "name": "Magma" },
  { "id": 20, "name": "Tornado" }
];

// Annotation section
@Component({
  selector: 'my-app',
  styles:[`
  .heroes {list-style-type: none; margin-left: 1em; padding: 0; width: 10em;}
  .heroes li { cursor: pointer; position: relative; left: 0; transition: all 0.2s ease; }
  .heroes li:hover {color: #369; background-color: #EEE; left: .2em;}
  .heroes .badge {
    font-size: small;
    color: white;
    padding: 0.1em 0.7em;
    background-color: #369;
    line-height: 1em;
    position: relative;
    left: -1px;
    top: -1px;
  }
  .selected { background-color: #EEE; color: #369; }
  `],
  template: `
    <h1>{{title}}</h1>
    <h2>My Heroes</h2>
    <ul class="heroes">
    <li *ng-for="#hero of heroes"
      [ng-class]="getSelectedClass(hero)"
      (click)="onSelect(hero)">
      <span class="badge">{{hero.id}}</span> {{hero.name}}
    </li>
    </ul>
    <div *ng-if="selectedHero">
      <h2>{{selectedHero.name}} details!</h2>
      <div><label>id: </label>{{selectedHero.id}}</div>
      <div>
        <label>name: </label>
        <input [(ng-model)]="selectedHero.name" placeholder="name"></input>
      </div>
    </div>
    `,
  directives: [FORM_DIRECTIVES, CORE_DIRECTIVES] ★ここを変更
})
// Component controller

class AppComponent {
  public title = 'Tour of Heroes';
  public heroes = HEROES;
  public selectedHero: Hero;
  onSelect(hero: Hero) { this.selectedHero = hero; }
  getSelectedClass(hero: Hero) {
    return { 'selected': hero === this.selectedHero };
  }
}

bootstrap(AppComponent);

これで目標のブツはできたぜー!!!

次は、せっかくなので、angular使ってSPAでも作ろうかな。

丁度作ってたやつが丁度よさげな題材なので。

あー、わからないことがいっぱいだった・・・

尚、angularjs 5min quick startをやってみた記事は、↓↓↓こちら angularjs2のtutorialをやってみた - xengineer’s diary

Tour of Heroesをやってみた記事の前半は、↓↓↓こちら angularjs2のtutorialをやってみた(その2) - xengineer’s diary

テザリングで作業前に準備するもの

そのまんまですが。

テザリングで作業すると、どのくらい通信発生してるのかわからなくて

ビクビクしながら作業することになります。(なりました)

それで、世の中の方々がどうしてるかグーグル先生に聞いてみました。

その1. TripModeを使うべし!

ottan.xyz

www.tripmode.ch

$7.99だけど、命の源パケット様が止まるよりは安いと思います。

何をしてくれるツールかというと、テザリング時に、自端末と外部との通信量の

合計と、アプリ毎の通信量をみれるようになるのです。

便利便利。あまりにも通信量が多いアプリは、通信禁止にもできるし、最高です。

タスクバーに常駐してくれてるので、突然通信量増えたりしてもすぐにチェックできる優れもの。

その2. Bandwidth+ も便利!

これは、itunes store探してたらでてきたやつ。

Bandwidth+

Bandwidth+

  • Harold Chu
  • ユーティリティ
  • 無料

まぁ、これも便利。

でもTripModeがあれば使わないかも。通信量の確認が主な機能で、

XXGBまであとどのくらい、的な確認もできるけど、通信制限はできないので、

その辺は自分で管理しましょう。

あとは、システム環境設定から、ちゃんとアプリの自動更新を切ったり、不要な通知を

切ったり、その辺の設定をちゃんとしておけば、そこまで通信が増えることもないでしょう。

これで安心テザリング

終わり。

angularjs2のtutorialをやってみた(その2)

お断り事項

本記事は、Angularjs 公式サイトのチュートリアルを

純粋にやってみているだけです。

英語苦手な方には悪くないと思いますが、本家のほうが、アップデートもかかるし、

英語問題ない方は本家をみたほうがよいです。

それと、5分クイックスタートをやってみたときの記事は以下から。

angularjs2のtutorialをやってみた - xengineer’s diary

概要

今回やるのは、下記リンクにある、"Tour of Heroes"というチュートリアルです。

Tutorial: Tour of Heroes - ts

きっと、前の5分のやつよりは色々わかることもあるのでしょう。

前提条件:

  • OS: Ubuntu 14.04.1 LTS
  • npm: 2.14.7
  • angularjs: 2.0.0-alpha.44
  • systemjs: 0.19.5
  • live-server: 0.8.1
  • typescript: 1.6.2
  • Linuxの操作には慣れている(開発にあたっての環境整備はできるレベル)
  • Angularjsの、5 Min Quickstart - ts、は理解できるレベル

さて、早速余談。僕はAngular2しか触ってないんだけど

Angular1.xとは結構変わっているところも多いみたい。

  • controllerがなくなった
  • directiveの使い方が簡単になった
  • angular.moduleがなくなった
  • $scopeがなくなった

などなど。angularの中の人の動画をみてたら、結構、Reactとか、fluxをみて、

「おー、これはいい」ってなって取り込んでるとこはあるぽい。

ざっくりフロー

本記事のざっくりした流れです。

  1. Tour of Heroesってなに?
  2. プロジェクトフォルダー作成
  3. TypeScript?
  4. 必須ライブラリのインストール
  5. 環境設定とタスク/httpd実行
  6. 動作イメージ
  7. Hero表示
  8. Hero編集
  9. Two-way binding
  10. TemplateDirective宣言
  11. FORM Directive宣言
  12. 実行

詳細

以降ではやったことを少し詳細に。

Tour of Heroesってなに?

そう、この謎タイトル。

どうやら、西洋のヒーローたちの一覧を作って、閲覧できるようにするツールらしいです。

以下スクショ。

ダッシュボード

f:id:xengineer:20151102175429p:plain

Magnetaさんの詳細編集

f:id:xengineer:20151102175435p:plain

ヒーローたち一覧

f:id:xengineer:20151102175441p:plain

作るサンプルにセンスを感じる。

こんなツールを作ります。がんばろう。

プロジェクトフォルダー作成

さ、フォルダ作り。

こんなフォルダ構成にしたい。

/
+- src
    +- app

下記コマンヅ実行。

$ mkdir -p angular2-tour-of-heroes/src/app
$ cd angular2-tour-of-heroes

記事内では、プロジェクトのroot directoryは、と呼びます。

では次へいきます。

TypeScript?

5分クイックスタートの記事を書いたときは、コード自体は、Javascriptで書いたんだけど、

今回は、ES6の便利機能も使ってみたいので、TypeScriptで書いてみよう。

凄い簡単にTypeScriptについて書くと、

マイクロソフトが開発した言語で、Javascriptのスーパーセット。

なので、実際に動かすときは、JSに変換しないといけない。

その変換を、「トランスパイル」っていう。

なので、前回の記事に加えて、Angular Componentを更新したら、トランスパイルして、JSに変換する、

っていう新たな手順も入ります。気をつけよっと。

必須ライブラリのインストール

まずは、インストール。下記コマンヅを実行します。

$ cd <WORKDIR>
$ npm init -y
$ npm i angular2@2.0.0-alpha.44 systemjs@0.19.5 --save --save-exact
$ npm i typescript live-server --save-dev

はて、systemjsとtypescriptは新しいので少し説明。

  • systemjs
    • require/import的にモジュールをロードする機能をjsに実装するためのライブラリ
  • typescript

これでライブラリのインストールは完了、と。

環境設定とタスク/httpd実行

今回は、TypeScriptのトランスパイラの設定と、httpdの設定をしますわい。

インストールコマンヅ実行後のpackage.jsonは、こんな感じ。

# package.json
{
  "name": "angular_test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "angular2": "2.0.0-alpha.44",
    "systemjs": "0.19.5"
  },
  "devDependencies": {
    "live-server": "^0.8.2",
    "typescript": "^1.6.2"
  }
}

何やら沢山。で、scriptセクションを書き換えます。

■変更前
"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1"
},

■変更後
"scripts": {
  "tsc": "tsc -p src -w",
  "start": "live-server --open=src"
},

tscってなんかいますね。

tscについて

tscっていうのは、TypeScriptの、トランスパイラ。

TypeScriptを、JSに変換してくれる。ナイス。

-p src: srcディレクトリ配下をトランスパイルしてくれる

-w : ファイルに変更があったかをずっと監視してくれる

で、tscのtranspile設定ファイル(tsconfig.json)を作る。

$ cd <WORKDIR>
$ vi src/tsconfig.json
{
  "compilerOptions": {
    "target": "ES5",
    "module": "commonjs",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "removeComments": false,
    "noImplicitAny": false
  }
}

この辺は・・・そのうち勉強するということで・・・

これで、下記を実行すると、tscが変更のあったファイルを監視して、自動でトランスパイルしてくれるのと、

live-serverが立ち上がって、いつでもサンプルを確認できる状態に!

やっほーい。

$ npm run tsc

> angular_test@1.0.0 tsc /home/nemoto_hideaki/work/root4/angular_test
> tsc -p src -w

message TS6042: Compilation complete. Watching for file changes.

$ npm start

> angular_test@1.0.0 start /home/nemoto_hideaki/work/root4/angular_test
> live-server --open=src

Serving "/home/nemoto_hideaki/work/root4/angular_test" at http://127.0.0.1:8080

動作イメージ

インストールやら設定やら終わったところで、TypeScriptで書いた場合の、

全体の動作イメージをまとめてみます。

f:id:xengineer:20151104114039p:plain

書いてるうちに、gdgdになっちゃいましたが、、、

TypeScriptが入ったので、app.tsファイル(Angular ComponentをTypeScriptで書いたファイル)から、

app.jsに、変換する処理を、tscコマンドがやってるところが追加になっただけ、かな。

それにともなって、tscのコンフィグが追加になった、と。

Hero表示

ではやっと本題に入って、Heroの表示画面を。

ファイルを沢山作っていくけど、最終的なディレクトリ構造はこうなる。

angular2-tour-of-heroes
  +-- node_modules
  +-- src
  |    +-- app
  |    |    +-- app.ts
  |    |
  |    +-- index.html
  |    +-- tsconfig.json
  |
  +-- package.json

まずは、Angular componentをTypescriptで書きます。拡張子は、ts。

$ vi <WORKDIR>/src/app/app.ts

下記をぺったり貼り付ける。

# app.ts
import {Component, bootstrap} from 'angular2/angular2';
@Component({
  selector: 'my-app',
  template: '<h1>{{title}}</h1><h2>{{hero}} details!</h2>'
})
class AppComponent {
  public title = 'Tour of Heroes';
  public hero = 'Windstorm';
}
bootstrap(AppComponent);

次は、htmlファイル。

$ vi <WORKDIR>/src/index.html

下記をぺったり貼り付ける。

# index.html
<html>
  <head>
    <title>Angular 2 QuickStart</title>
    <script src="../node_modules/systemjs/dist/system.src.js"></script>
    <script src="../node_modules/angular2/bundles/angular2.dev.js"></script>
    <script>
      System.config({
        packages: {'app': {defaultExtension: 'js'}}
      });
      System.import('app/app');
    </script>
  </head>
  <body>
    <my-app>Loading...</my-app>
  </body>
</html>

で、tscとlive-serverは、再起動したほうがよさげ。

http://127.0.0.1:8080にアクセスしたら、

f:id:xengineer:20151104120814p:plain

こんなんでまーす。

Hero オブジェクト化

ここまでは、ツアーといっても、Windstormさんしかいない。

それにヒーローを追加するにしても、↓↓に毎回ヒーローを追加していかないといけなくて大変。

class AppComponent {
  public title = 'Tour of Heroes';
  public hero = 'Windstorm';
}

そのため、ヒーロークラスを作ってしまいましょう、というのが次。

app.tsに追記します。

# app.ts
import {Component, bootstrap} from 'angular2/angular2';
@Component({
  selector: 'my-app',
  template: '<h1>{{title}}</h1><h2>{{hero.name}} details!</h2>'
})

class Hero {      ★このclassを追記したよ
  id: number;
  name: string;
}
class AppComponent {
  public title = 'Tour of Heroes';
  public hero: Hero = { ★ここ変えたよ
    id: 1,
    name: 'Windstorm'
  };
}
bootstrap(AppComponent);

これで、http://127.0.0.1:8080にアクセス!!!

Loading...

ずっとこれが出るだけ・・・

Chrome Developer toolで見てみると、エラー!

angular2.dev.js:21835 EXCEPTION: Error during instantiation of Token Promise<ComponentRef>!.
angular2.dev.js:21835 ORIGINAL EXCEPTION: No Directive annotation found on AppComponentBrowserDomAdapter.logError @ angular2.dev.js:21835ExceptionHandler.call @ angular2.dev.js:4440(anonymous function) @ angular2.dev.js:19642run @ angular2.dev.js:138(anonymous function) @ angular2.dev.js:10644NgZone.run @ angular2.dev.js:10607ApplicationRef_.bootstrap @ angular2.dev.js:19615commonBootstrap @ angular2.dev.js:26650bootstrap @ angular2.dev.js:27475(anonymous function) @ app.ts:31(anonymous function) @ app.ts:31__exec @ system.src.js:1374entry.execute @ system.src.js:3535linkDynamicModule @ system.src.js:2933link @ system.src.js:2776execute @ system.src.js:3108doDynamicExecute @ system.src.js:715link @ system.src.js:908doLink @ system.src.js:569updateLinkSetOnLoad @ system.src.js:617(anonymous function) @ system.src.js:430run @ angular2.dev.js:138zoneBoundFn @ angular2.dev.js:111lib$es6$promise$$internal$$tryCatch @ angular2.dev.js:1507lib$es6$promise$$internal$$invokeCallback @ angular2.dev.js:1519lib$es6$promise$$internal$$publish @ angular2.dev.js:1490(anonymous function) @ angular2.dev.js:219run @ angular2.dev.js:138zoneBoundFn @ angular2.dev.js:111lib$es6$promise$asap$$flush @ angular2.dev.js:1301
angular2.dev.js:21835 ORIGINAL STACKTRACE:BrowserDomAdapter.logError @ angular2.dev.js:21835ExceptionHandler.call @ angular2.dev.js:4443(anonymous function) @ angular2.dev.js:19642run @ angular2.dev.js:138(anonymous function) @ angular2.dev.js:10644NgZone.run @ angular2.dev.js:10607ApplicationRef_.bootstrap @ angular2.dev.js:19615commonBootstrap @ angular2.dev.js:26650bootstrap @ angular2.dev.js:27475(anonymous function) @ app.ts:31(anonymous function) @ app.ts:31__exec @ system.src.js:1374entry.execute @ system.src.js:3535linkDynamicModule @ system.src.js:2933link @ system.src.js:2776execute @ system.src.js:3108doDynamicExecute @ system.src.js:715link @ system.src.js:908doLink @ system.src.js:569updateLinkSetOnLoad @ system.src.js:617(anonymous function) @ system.src.js:430run @ angular2.dev.js:138zoneBoundFn @ angular2.dev.js:111lib$es6$promise$$internal$$tryCatch @ angular2.dev.js:1507lib$es6$promise$$internal$$invokeCallback @ angular2.dev.js:1519lib$es6$promise$$internal$$publish @ angular2.dev.js:1490(anonymous function) @ angular2.dev.js:219run @ angular2.dev.js:138zoneBoundFn @ angular2.dev.js:111lib$es6$promise$asap$$flush @ angular2.dev.js:1301
angular2.dev.js:21835 Error: No Directive annotation found on AppComponent
    at new BaseException (angular2.dev.js:16034)
    at DirectiveResolver.resolve (angular2.dev.js:9953)
    at RuntimeMetadataResolver.getMetadata (angular2.dev.js:12685)
    at TemplateCompiler.compileHostComponentRuntime (angular2.dev.js:24877)
    at RuntimeCompiler_.compileInHost (angular2.dev.js:27391)
    at DynamicComponentLoader_.loadAsRoot (angular2.dev.js:13289)
    at di_1.provide.useFactory (angular2.dev.js:19433)
    at Injector._instantiate (angular2.dev.js:22778)
    at Injector._instantiateProvider (angular2.dev.js:22714)
    at Injector._new (angular2.dev.js:22704)

なんだなんだ・・・と色々試してみて、最終チュートをじっくり読んでみた。

Hero class作成の箇所に、こんなことが書いてある。

Hero object

At the moment, our hero is just a name. Our hero needs more properties. Let's convert the hero from a literal string to a class.

Create a Hero class with id and name properties. Keep this near the top of the app.ts file for now.

Keep this near the top of the app.ts file for now.

near ですよ。どのくらいトップの近くか言ってくれよ!というわけで、importのすぐ下の行に書いたらちゃんと動いた。

↓↓が最終形態。

# app.ts
import {Component, bootstrap} from 'angular2/angular2';

class Hero {    ★ここを上に持ってきた
  id: number;
  name: string;
}

@Component({
    selector: 'my-app',
    template: '<h1>{{title}}</h1><h2>{{hero.name}} details!</h2>'
})
class AppComponent {
  public title = 'Tour of Heroes';
  public hero: Hero = {
    id: 1,
    name: 'Windstorm'
  };
}
bootstrap(AppComponent);

もうちょい表示改善

現状だと、templateのどこにもdiv tag的なものがないので、ヒーロー追加すると、

どんどん横に表示が広がってしまって美しくない。

ので、適度にdiv/labelを入れて、見れるtemplateにしますよ、と。

# app.ts
import {Component, bootstrap} from 'angular2/angular2';

class Hero {
  id: number;
  name: string;
}

@Component({
    selector: 'my-app',
    template: '<h1>{{title}}</h1><h2>{{hero.name}} details!</h2><div><label>id: </label>{{hero.id}}</div><div><label>name: </label>{{hero.name}}</div>'★ここを変えたよ
})
class AppComponent {
  public title = 'Tour of Heroes';
  public hero: Hero = {
    id: 1,
    name: 'Windstorm'
  };
}
bootstrap(AppComponent);

横長、です。back-ticksで囲ったところは、改行OKだそうで、こんな感じにかけます。

template:`
  <h1>{{title}}</h1>
  <h2>{{hero.name}} details!</h2>
  <div><label>id: </label>{{hero.id}}</div>
  <div><label>name: </label>{{hero.name}}</div>
  `

これ組み込んだ版↓↓

# app.ts
import {Component, bootstrap} from 'angular2/angular2';

class Hero {
  id: number;
  name: string;
}

@Component({
  selector: 'my-app',
  template:` ★ここ変えたよ
    <h1>{{title}}</h1>
    <h2>{{hero.name}} details!</h2>
    <div><label>id: </label>{{hero.id}}</div>
    <div><label>name: </label>{{hero.name}}</div>
    `
})
class AppComponent {
  public title = 'Tour of Heroes';
  public hero: Hero = {
    id: 1,
    name: 'Windstorm'
  };
}
bootstrap(AppComponent);

今度はこんな感じに表示されまする。

f:id:xengineer:20151104123238p:plain

Hero編集

さて、ここまでは表示をいじってきたけど、ここからは、ヒーロー編集に入ります。

まずは、templateに、input tagいれて、入力できるところを作ります。

template:`
  <h1>{{title}}</h1>
  <h2>{{hero.name}} details!</h2>
  <div><label>id: </label>{{hero.id}}</div>
  <div>
    <label>name: </label>
    <div><input value="{{hero.name}}" placeholder="name"></div> ★ここ追記
  </div>
  `

これで、こんな感じにフォームが出てくる。

f:id:xengineer:20151104124417p:plain

この状態だと、入力しても、何も反映されないので、Two-way bindingして、

ちゃんと表示にも反映されるようにします。

Two-way binding

Two-way bindingとは??から。

Stackoverflowにあったので引用。

javascript - What is two way binding? - Stack Overflow

  • When properties in the model get updated, so does the UI.
    • モデルのプロパティがアップデートされると、UIの表示もアップデートされる
  • When UI elements get updated, the changes get propagated back to the model.
    • UIエレメントがアップデートされると、モデルにもそれが波及する

ということでした。one-wayだと、上記のどっちか、なんでしょうね。

Angular1.xは、Two-way binding対応だったものの、

ネットで調べた感じでは、Angular2.xでは、One-wayになるぽい記述が多かったんだけど・・・

チュートには、堂々と Two-wayで載っている・・・まぁチュートのとおりにやります。

NgModel directive

この、bindingを実現するのに、NgModel directiveを使います。

NgModelについては、下記URLに記載があるけど、、、読んでもそんなにすっと入ってこないです、はい。

もうちょい前段の知識が必要そうなので、一旦詳細は忘れて、「Two-way binding == NgModel」くらいで進もう。

Template Syntax - ts

まず、input tagを置き換える。

template:`
  <h1>{{title}}</h1>
  <h2>{{hero.name}} details!</h2>
  <div><label>id: </label>{{hero.id}}</div>
  <div>
    <label>name: </label>
    <div><input value="{{hero.name}}" placeholder="name"></div>    ★この行を削除して
    <div><input [(ng-model)]="hero.name" placeholder="name"></div> ★この行を追記する
  </div>
  `

この状態だと、http://127.0.0.1:8080にいってもなにもみれないのであしからず。

TemplateDirective宣言

上の状態だと、ng-model directiveが何者かを書いてないので、トランスパイルできないのかな?

なので、ng-modelが何者かをangularに教えてあげる。

(app.tsの)import文に、NgModelを追記。

import {bootstrap, Component, NgModel} from 'angular2/angular2';

更に、(app.tsの) @Component宣言のすぐ下に、当該Componentが利用するdirectiveを追記。

諸々盛り込んだ版↓↓

# app.ts
import {bootstrap, Component, NgModel} from 'angular2/angular2'; ★ここ変えたよ

class Hero {
  id: number;
  name: string;
}

@Component({
  selector: 'my-app',
  template:`
    <h1>{{title}}</h1>
    <h2>{{hero.name}} details!</h2>
    <div><label>id: </label>{{hero.id}}</div>
    <div>
      <label>name: </label>
      <div><input [(ng-model)]="hero.name" placeholder="name"></div> ★ここ変えたよ
    </div>
    `,
  directives: [NgModel] ★ここ追記
})
class AppComponent {
  public title = 'Tour of Heroes';
  public hero: Hero = {
    id: 1,
    name: 'Windstorm'
  };
}
bootstrap(AppComponent);

で、いつものURLをみてみると、、、エラー(Chrome Developer toolのコンソールにでる)。

ぎゃー。沢山。

EXCEPTION: No value accessor for '' in [null]
angular2.dev.js:21835 EXCEPTION: No value accessor for '' in [null]BrowserDomAdapter.logError @ angular2.dev.js:21835BrowserDomAdapter.logGroup @ angular2.dev.js:21846ExceptionHandler.call @ angular2.dev.js:4431(anonymous function) @ angular2.dev.js:19543NgZone._onError @ angular2.dev.js:10711errorHandling.onError @ angular2.dev.js:10630run @ angular2.dev.js:141(anonymous function) @ angular2.dev.js:10644zoneBoundFn @ angular2.dev.js:111lib$es6$promise$$internal$$tryCatch @ angular2.dev.js:1507lib$es6$promise$$internal$$invokeCallback @ angular2.dev.js:1519lib$es6$promise$$internal$$publish @ angular2.dev.js:1490(anonymous function) @ angular2.dev.js:219microtask @ angular2.dev.js:10670run @ angular2.dev.js:138(anonymous function) @ angular2.dev.js:10644zoneBoundFn @ angular2.dev.js:111lib$es6$promise$asap$$flush @ angular2.dev.js:1301
angular2.dev.js:21835 ORIGINAL EXCEPTION: No value accessor for ''BrowserDomAdapter.logError @ angular2.dev.js:21835ExceptionHandler.call @ angular2.dev.js:4440(anonymous function) @ angular2.dev.js:19543NgZone._onError @ angular2.dev.js:10711errorHandling.onError @ angular2.dev.js:10630run @ angular2.dev.js:141(anonymous function) @ angular2.dev.js:10644zoneBoundFn @ angular2.dev.js:111lib$es6$promise$$internal$$tryCatch @ angular2.dev.js:1507lib$es6$promise$$internal$$invokeCallback @ angular2.dev.js:1519lib$es6$promise$$internal$$publish @ angular2.dev.js:1490(anonymous function) @ angular2.dev.js:219microtask @ angular2.dev.js:10670run @ angular2.dev.js:138(anonymous function) @ angular2.dev.js:10644zoneBoundFn @ angular2.dev.js:111lib$es6$promise$asap$$flush @ angular2.dev.js:1301
angular2.dev.js:21835 ORIGINAL STACKTRACE:BrowserDomAdapter.logError @ angular2.dev.js:21835ExceptionHandler.call @ angular2.dev.js:4443(anonymous function) @ angular2.dev.js:19543NgZone._onError @ angular2.dev.js:10711errorHandling.onError @ angular2.dev.js:10630run @ angular2.dev.js:141(anonymous function) @ angular2.dev.js:10644zoneBoundFn @ angular2.dev.js:111lib$es6$promise$$internal$$tryCatch @ angular2.dev.js:1507lib$es6$promise$$internal$$invokeCallback @ angular2.dev.js:1519lib$es6$promise$$internal$$publish @ angular2.dev.js:1490(anonymous function) @ angular2.dev.js:219microtask @ angular2.dev.js:10670run @ angular2.dev.js:138(anonymous function) @ angular2.dev.js:10644zoneBoundFn @ angular2.dev.js:111lib$es6$promise$asap$$flush @ angular2.dev.js:1301
angular2.dev.js:21835 Error: No value accessor for ''
    at new BaseException (http://127.0.0.1:8080/node_modules/angular2/bundles/angular2.dev.js:16034:21)
    at _throwError (http://127.0.0.1:8080/node_modules/angular2/bundles/angular2.dev.js:25067:11)
    at Object.setUpControl (http://127.0.0.1:8080/node_modules/angular2/bundles/angular2.dev.js:25049:7)
    at NgModel.onChanges (http://127.0.0.1:8080/node_modules/angular2/bundles/angular2.dev.js:13743:18)
    at AbstractChangeDetector.ChangeDetector_AppComponent_0.detectChangesInRecordsInternal (eval at <anonymous> (http://127.0.0.1:8080/node_modules/angular2/bundles/angular2.dev.js:20415:14), <anonymous>:162:57)
    at AbstractChangeDetector.detectChangesInRecords (http://127.0.0.1:8080/node_modules/angular2/bundles/angular2.dev.js:20209:14)
    at AbstractChangeDetector.runDetectChanges (http://127.0.0.1:8080/node_modules/angular2/bundles/angular2.dev.js:20192:12)
    at AbstractChangeDetector._detectChangesInShadowDomChildren (http://127.0.0.1:8080/node_modules/angular2/bundles/angular2.dev.js:20259:14)
    at AbstractChangeDetector.runDetectChanges (http://127.0.0.1:8080/node_modules/angular2/bundles/angular2.dev.js:20196:12)
    at AbstractChangeDetector.detectChanges (http://127.0.0.1:8080/node_modules/angular2/bundles/angular2.dev.js:20183:12)BrowserDomAdapter.logError @ angular2.dev.js:21835ExceptionHandler.call @ angular2.dev.js:4444(anonymous function) @ angular2.dev.js:19543NgZone._onError @ angular2.dev.js:10711errorHandling.onError @ angular2.dev.js:10630run @ angular2.dev.js:141(anonymous function) @ angular2.dev.js:10644zoneBoundFn @ angular2.dev.js:111lib$es6$promise$$internal$$tryCatch @ angular2.dev.js:1507lib$es6$promise$$internal$$invokeCallback @ angular2.dev.js:1519lib$es6$promise$$internal$$publish @ angular2.dev.js:1490(anonymous function) @ angular2.dev.js:219microtask @ angular2.dev.js:10670run @ angular2.dev.js:138(anonymous function) @ angular2.dev.js:10644zoneBoundFn @ angular2.dev.js:111lib$es6$promise$asap$$flush @ angular2.dev.js:1301
angular2.dev.js:21835 ERROR CONTEXT:BrowserDomAdapter.logError @ angular2.dev.js:21835ExceptionHandler.call @ angular2.dev.js:4447(anonymous function) @ angular2.dev.js:19543NgZone._onError @ angular2.dev.js:10711errorHandling.onError @ angular2.dev.js:10630run @ angular2.dev.js:141(anonymous function) @ angular2.dev.js:10644zoneBoundFn @ angular2.dev.js:111lib$es6$promise$$internal$$tryCatch @ angular2.dev.js:1507lib$es6$promise$$internal$$invokeCallback @ angular2.dev.js:1519lib$es6$promise$$internal$$publish @ angular2.dev.js:1490(anonymous function) @ angular2.dev.js:219microtask @ angular2.dev.js:10670run @ angular2.dev.js:138(anonymous function) @ angular2.dev.js:10644zoneBoundFn @ angular2.dev.js:111lib$es6$promise$asap$$flush @ angular2.dev.js:1301
angular2.dev.js:21835 _Context {element: input, componentElement: my-app, context: AppComponent, locals: Object, injector: Injector…}

FORM Directive宣言

どうやら、NgModelを宣言しただけでは足りないようだ。(って、本家英文にも書いてある)

さすがに、NgModel使うにあたって必要になる directiveを全部探すのは骨が折れるので、、、

その辺は便利なまとめ方があるようで。

ng-model directiveを使うために、FORM_DIRECTIVESという便利配列が定義されてるので、

それをimportしちゃえばOK!だそうです。

つまり、

# app.ts
import {bootstrap, Component, FORM_DIRECTIVES} from 'angular2/angular2'; ★ここ変えたよ

class Hero {
  id: number;
  name: string;
}

@Component({
  selector: 'my-app',
  template:`
    <h1>{{title}}</h1>
    <h2>{{hero.name}} details!</h2>
    <div><label>id: </label>{{hero.id}}</div>
    <div>
      <label>name: </label>
      <div><input [(ng-model)]="hero.name" placeholder="name"></div>
    </div>
    `,
  directives: [FORM_DIRECTIVES] ★ここ追記
})
class AppComponent {
  public title = 'Tour of Heroes';
  public hero: Hero = {
    id: 1,
    name: 'Windstorm'
  };
}
bootstrap(AppComponent);

実行

なんとなんと。これで、http://127.0.0.1:8080 にアクセスしたら、

まずはこの画面。

f:id:xengineer:20151104142856p:plain

テキストフィールドを編集すると、、、、

f:id:xengineer:20151104142921p:plain

ちゃんとすぐに表示にも反映されましたとさ!便利!

ひゃっほー。長かったけど、終わり。

でもまだ先はあるぽい。

続きはまた別記事で~。

の、別記事↓↓↓ angularjs2のtutorialをやってみた(その3) - xengineer’s diary

メモ

あと、実はもう一箇所エラー出してたのでメモ。

結論、directiveの付け方間違ってた。エラーは↓↓これ。

EXCEPTION: TypeError: Cannot read property 'hostView' of undefined
angular2.dev.js:21835 EXCEPTION: TypeError: Cannot read property 'hostView' of undefinedBrowserDomAdapter.logError @ angular2.dev.js:21835BrowserDomAdapter.logGroup @ angular2.dev.js:21846ExceptionHandler.call @ angular2.dev.js:4431(anonymous function) @ angular2.dev.js:19543NgZone._onError @ angular2.dev.js:10711errorHandling.onError @ angular2.dev.js:10630run @ angular2.dev.js:141(anonymous function) @ angular2.dev.js:10644zoneBoundFn @ angular2.dev.js:111lib$es6$promise$$internal$$tryCatch @ angular2.dev.js:1507lib$es6$promise$$internal$$invokeCallback @ angular2.dev.js:1519lib$es6$promise$$internal$$publish @ angular2.dev.js:1490(anonymous function) @ angular2.dev.js:219microtask @ angular2.dev.js:10670run @ angular2.dev.js:138(anonymous function) @ angular2.dev.js:10644zoneBoundFn @ angular2.dev.js:111lib$es6$promise$asap$$flush @ angular2.dev.js:1301
angular2.dev.js:21835 STACKTRACE:BrowserDomAdapter.logError @ angular2.dev.js:21835ExceptionHandler.call @ angular2.dev.js:4433(anonymous function) @ angular2.dev.js:19543NgZone._onError @ angular2.dev.js:10711errorHandling.onError @ angular2.dev.js:10630run @ angular2.dev.js:141(anonymous function) @ angular2.dev.js:10644zoneBoundFn @ angular2.dev.js:111lib$es6$promise$$internal$$tryCatch @ angular2.dev.js:1507lib$es6$promise$$internal$$invokeCallback @ angular2.dev.js:1519lib$es6$promise$$internal$$publish @ angular2.dev.js:1490(anonymous function) @ angular2.dev.js:219microtask @ angular2.dev.js:10670run @ angular2.dev.js:138(anonymous function) @ angular2.dev.js:10644zoneBoundFn @ angular2.dev.js:111lib$es6$promise$asap$$flush @ angular2.dev.js:1301
angular2.dev.js:21835 TypeError: Cannot read property 'hostView' of undefined
    at tick (angular2.dev.js:19626)
    at Zone.run (angular2.dev.js:138)
    at Zone.run (angular2.dev.js:10644)
    at zoneBoundFn (angular2.dev.js:111)
    at lib$es6$promise$$internal$$tryCatch (angular2.dev.js:1507)
    at lib$es6$promise$$internal$$invokeCallback (angular2.dev.js:1519)
    at lib$es6$promise$$internal$$publish (angular2.dev.js:1490)
    at angular2.dev.js:219
    at microtask (angular2.dev.js:10670)
    at Zone.run (angular2.dev.js:138)

間違ってたのはここ。

■ 間違い
<div><input [(ng-model)]="{{hero.name}}" placeholder="name"></div>

■ 正しい
<div><input [(ng-model)]="hero.name" placeholder="name"></div>

curly bracketつけっぱなしにしちゃってたのがいけなかった。

直したら無事表示されましたとさ。(記事内では正しいほうに直してます)

gitでcommitとかaddミスったとき

僕はこれもよくやるんでね・・・

間違って git add !

まずは、addミスって、変なファイルをstageしちゃったとき。

これは、git statusすると出てくるメッセージにも書いてあるんだけど、

$ git checkout - <ファイル名>

ってやれば unstage されます。びっくりポンや。

間違って git commit !

次は commit してから間違ったことに気づいたとき。

よくあるやつ。

$ git reset --soft HEAD^

これで直前の commit は取り消せて、add した直後にまで戻りますん。

何個か前まで戻したいときは、

$ git reset --soft HEAD~{n} # n:戻したい数

これでいけーる。

こういうのばっかり覚える。