Socket.ioのチュートリアルを実施

英語の元のページはこちら

前提条件としてnode.jsがインストールされていることが必要です。

node.jsのインストール

cd /tmp
curl --silent --location https://rpm.nodesource.com/setup_8.x | sudo bash -
sudo yum -y install nodejs
sudo yum -y install gcc-c++ make

package.jsonの作成

cd
mkdir chat
cd chat
vi package.json

package.jsonに次の内容を入力します:

{
  "name": "socket-chat-example",
  "version": "0.0.1",
  "description": "my first socket.io app",
  "dependencies": {}
}

expressのインストール

次にフレームワークのexpressをインストールします。 package.jsondependenciesの編集を省略するためにnpm install --saveを実行します:

cd ~/chat
npm install --save express@4.15.2

index.jsの作成

次の内容でindex.jsを作成します:

var app = require('express')();
var http = require('http').Server(app);

app.get('/', function(req, res){
  res.send('<h1>Hello world</h1>');
});

http.listen(3000, function(){
  console.log('listening on *:3000');
});

上記の内容ですが、 * appを、HTTPサーバに渡せる関数ハンドラとして初期化する * ルートハンドラ/を定義する。これはウェブページをアクセスした際に呼び出される * ポート3000でウェブサーバをlistenする

ウェブサーバの起動

node index.jsと実行すると、次の画面が出るはずです:

https://socket.io/assets/img/chat-1.png

ここでブラウザをhttp://localhost:3000に向けると、次のような画面が表示されます:

https://socket.io/assets/img/chat-2.png

HTMLを別ファイルに分ける

index.jsでは、res.sendに対してHTMLを文字列として渡しています。HTMLが長くなると見通しが悪くなりコードが管理しづらいので、index.htmlを作成してそこにHTMLを格納します:

app.get('/', function(req, res){
res.sendFile(__dirname + '/index.html');
});

index.htmlには次の内容を記述します:

<!doctype html>
<html>
  <head>
    <title>Socket.IO chat</title>
    <style>
      * { margin: 0; padding: 0; box-sizing: border-box; }
      body { font: 13px Helvetica, Arial; }
      form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
      form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
      form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
      #messages { list-style-type: none; margin: 0; padding: 0; }
      #messages li { padding: 5px 10px; }
      #messages li:nth-child(odd) { background: #eee; }
    </style>
  </head>
  <body>
    <ul id="messages"></ul>
    <form action="">
      <input id="m" autocomplete="off" /><button>Send</button>
    </form>
  </body>
</html>

ここで、node index.jsを再度実行し(ctrl-cで一旦殺してから、再度、実行)ブラウザからhttp://localhost:3000を アクセスすると次のような画面が出るはずです:

https://socket.io/assets/img/chat-3.png

Socket.IOの組み込み

Socket.IOは次の2つから構成されています:

  • Node.JSのHTTPサーバと連動して動くsocket.io
  • ブラウザ側で読み込まれるクライアントライブラリ sockiet.io-client

開発中においては、socket.ioがクライアントに対して自動的にサービスを提供してくれるため、現時点ではsocket.ioのみをインストールします:

npm install --save socket.io

これでsocket.ioがインストールされpackage.jsonが更新されます。次に、index.jsを編集しsocket.ioを読み込みます:

var app  = require('express')();
var http = require('http').Server(app);
var io   = require('socket.io')(http);

app.get('/', function(req, res){
  res.sendFile(__dirname + '/index.html');
});

io.on('connection', function(socket){
  console.log('a user connected');
});

http.listen(3000, function(){
  console.log('listening on *:3000');
});

上記ですが、httpオブジェクト(HTTPサーバ)を渡してsocket.ioインスタンスを初期化しています。 次にconnectionイベントを待ち受け、コンソールに出力しています。

ブラウザ側への組み込み

index.htmlを開いて、次のコードを</body>の直前に入れます:

<script src="/socket.io/socket.io.js"></script>
<script>
  var socket = io();
</script>

これだけでsocket.io-clientを読み込み、ioグローバルを露出し、接続します。 なお、io()を呼ぶ時URLを指定していませんが、これは、ページを配信したホストにデフォルトで接続するためです。

ここでブラウザを再読込するとコンソールが "a user connected"と出力するはずです。 複数のタブを開くと、次のように複数メッセージが出力されます:

https://socket.io/assets/img/chat-4.png

各ソケットは特殊なdisconnectイベントを発行します:

io.on('connection', function(socket){
  console.log('a user connected');
  socket.on('disconnect', function(){
    console.log('user disconnected');
  });
});

ブラウザでリロードを数回行うと、disconnectを確認できます:

https://socket.io/assets/img/chat-5.png

イベントの発行

Socket.IOのコンセプトとして、お好きなイベントの送信・受信ができ、その際、お好きなデータを送れます。JSONエンコードできるデータであればなんでもOKです。バイナリデータもサポートされています。

さて、ユーザがメッセージを入力したら、サーバがそれを受信してチャットメッセージのイベントを発行するようにしましょう。index.html<script>部分を次のようにします:

<script src="/socket.io/socket.io.js"></script>
<script src="https://code.jquery.com/jquery-1.11.1.js"></script>
<script>
  $(function () {
    var socket = io();
    $('form').submit(function(){
      socket.emit('chat message', $('#m').val());
      $('#m').val('');
      return false;
    });
  });
</script>

そしてindex.jsでは、chat messageイベントを出力します:

io.on('connection', function(socket){
  socket.on('chat message', function(msg){
    console.log('message: ' + msg);
  });
});

結果として次の動画のようになるはずです(元のサイトで動画をご覧ください)

ブロードキャスト

次に、サーバから他のユーザに対してイベントを発行します。 イベントを全員に送るには、socket.ioio.emitを利用します:

io.emit('some event', { for: 'everyone' });

もし、特定のソケットを除いてメッセージを送りたい場合、broadcastフラグを使います:

io.on('connection', function(socket){
  socket.broadcast.emit('hi');
});

注:原文の意味がよくわからないのですが、これを使うとメッセージを送信した本人以外にイベントが届きます

今回はわかりやすさのため、送信者を含む全員にメッセージを送ります:

io.on('connection', function(socket){
  socket.on('chat message', function(msg){
    io.emit('chat message', msg);
  });
});

クライアント側ではchat messageイベントを受信し、ページ内に表示します:

<script>
  $(function () {
    var socket = io();
    $('form').submit(function(){
      socket.emit('chat message', $('#m').val());
      $('#m').val('');
      return false;
    });
    socket.on('chat message', function(msg){
      $('#messages').append($('<li>').text(msg));
    });
  });
</script>

これで、簡単なチャットが実現できました。

宿題

  • 誰かが接続・切断した場合に接続しているユーザに通知を送りましょう
  • ニックネーム機能を付けましょう
  • 送信者にはメッセージを送らないようにしましょう。代わりに、メッセージ入力後にDOMにすぐに追記します。
  • "○○さんが入力中です"の機能をつけましょう
  • 誰がオンラインであるかを表示する機能をつけましょう
  • 個人間のメッセージを付けましょう
  • 改良した内容をみんなと共有しましょう!

このチュートリアルを入手するには

GitHubこちらにあります。

$ git clone https://github.com/socketio/chat-example.git

Angularの公式チュートリアル:第3章「Master/Detail」

第3章では、複数のヒーローをリスト表示し、ユーザがヒーローを選択すると詳細を表示する仕組みを作ります。 元の記事はこちら

ウェブサーバの起動

(既に実施していたら不要です)

cd ~/angular-tour-of-heroes
ng serve --host=0.0.0.0 --public=グローバルIP

ヒーローの名前の定義

app.component.tsを編集し、次の定数を追加します:

const HeRoEs: Hero[] = [
  { id: 11, name: 'ビアンカ' },
  { id: 12, name: 'フローラ' },
  { id: 13, name: 'デボラ'   }
];

追加する場所は、app.component.ts@Componentの上にします。追加後のソースはこちら:

import { Component } from '@angular/core';

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

const HeRoEs: Hero[] = [
  { id: 11, name: 'ビアンカ' },
  { id: 12, name: 'フローラ' },
  { id: 13, name: 'デボラ'   }
];

@Component({
  selector: 'app-root',
  template: `<h1>{{title}}</h1>
    <div>
      <input [(ngModel)]="hero.name">
    </div>
    <div><label>id: </label>{{hero.id}}</div>
    <div><label>name: </label>{{hero.name}}</div>
  `,
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'Tour of Heroes';
  hero: Hero = {
    id: 1,
    name: 'Windstorm'
  };
}

HeRoEsは、Hero型の配列です。Heroは前章において定義したクラスです。このアプリは、いずれはウェブサービスからヒーローのデータを取得するようにしますが、現時点ではモックのヒーローを読み込みます。

次に、ヒーローを外部とバインド可能にする変数をAppComponentに定義します:

heroes = HeRoEs; // heroes: ではなく heroes = である点に注意

heroesに型の定義はありません。なぜならば、HeRoEs配列より型を推論するためです。 この時点でのソースは次の通りです:

import { Component } from '@angular/core';

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

const HeRoEs: Hero[] = [
  { id: 11, name: 'ビアンカ' },
  { id: 12, name: 'フローラ' },
  { id: 13, name: 'デボラ'   }
];

@Component({
  selector: 'app-root',
  template: `<h1>{{title}}</h1>
    <div>
      <input [(ngModel)]="hero.name">
    </div>
    <div><label>id: </label>{{hero.id}}</div>
    <div><label>name: </label>{{hero.name}}</div>
  `,
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'Tour of Heroes';
  hero: Hero = {
    id: 1,
    name: 'Windstorm'
  };
  heroes = HeRoEs;
}

ヒーローのデータはクラスの実装から分けています。なぜかというと、最終的にはヒーローの名前はデータのサービスから取得するためです。

次に、ヒーローの名前の一覧を表示します。表示するには、ヒーローの名前の配列をテンプレートにバインドし、それらに対してイテレーションしてひとつひとつを表示します。app.component.tstemplateに次のようにHTMLを追加します:

  template: `<h1>{{title}}</h1>
    <h2>私のヒーロー一覧</h2>
    <ul class="heroes">
      <li *ngFor="let hero of heroes">
        <span class="badge">{{hero.id}}</span>{{hero.name}}
      </li>
    </ul>

ngForのプレフィックス(*)は、重要なポイントです。これは<li>とその子要素がテンプレートのマスターであることを示しています。 ngForディレクティブはヒーローの配列をイテレーションし、配列の中の1つ1つのヒーローに対してテンプレートのインスタンスを描画します。

let heroの部分は、heroをテンプレートの入力変数として明示しています。この変数はイテレーションにおける現在のヒーローの値を保持しています。テンプレートの中からこの変数を参照することによりヒーローの属性をアクセスできます。

この内容で保存し、ブラウザをリロードすると、次の画面が表示されるはずです:

f:id:thebaker:20171008191644p:plain

次に、デザインを変更します。目的は、マウスカーソルをホバーしたことが分かるようにすること、および、どのヒーローが選択されているかを分かるようにすること。 元のチュートリアルではapp.component.tsの中にCSSを埋め込んでいますが、これですとソースが長くなってしまうのでここでは別ファイルに切り出します。 app.component.cssというファイルがあるので開き、次のコードをペーストし保存します:

.selected {
  background-color: #CFD8DC !important;
  color: white;
}
.heroes {
  margin: 0 0 2em 0;
  list-style-type: none;
  padding: 0;
  width: 15em;
}
.heroes li {
  cursor: pointer;
  position: relative;
  left: 0;
  background-color: #EEE;
  margin: .5em;
  padding: .3em 0;
  height: 1.6em;
  border-radius: 4px;
}
.heroes li.selected:hover {
  background-color: #BBD8DC !important;
  color: white;
}
.heroes li:hover {
  color: #607D8B;
  background-color: #DDD;
  left: .1em;
}
.heroes .text {
  position: relative;
  top: -3px;
}
.heroes .badge {
  display: inline-block;
  font-size: small;
  color: white;
  padding: 0.8em 0.7em 0 0.7em;
  background-color: #607D8B;
  line-height: 1em;
  position: relative;
  left: -1px;
  top: -4px;
  height: 1.8em;
  margin-right: .8em;
  border-radius: 4px 0 0 4px;
}

このファイルはapp.component.tsstyleUrlsで読み込みを指定されています。ブラウザをリロードすると、スタイルが適用されているのが分かると思います:

f:id:thebaker:20171008201557p:plain

なお、このスタイルは、スタイルを定義したコンポーネント、この場合はapp.component.tsにのみ適用されます。その他のHTMLには影響がありません。

さて、現時点ではリスト内のヒーローと、その下のヒーローの紹介が紐付けられていません。そこで、ユーザがリストからヒーローを選択したら、 そのヒーローの詳細が下に表示される処理を実装します。これはmaster/detailというUIパターンです。masterとは、ヒーローの一覧を指し、 detailとはユーザが選択したヒーローを示します。

masterとdetailは、selectedHeroという属性を通じて関連付けます。そしてこの属性をクリックイベントと紐付けます。

まず、<li>タグに次のようにクリックイベントをバインドします:

<li *ngFor="let hero of heroes" (click)="onSelect(hero)">

丸括弧は、<li>タグのclickイベントをターゲットとして識別します。onSelect(hero)式はAppComponentonSelect()メソッドを呼び出し、その際、heroを引数として渡します。このheroは、以前にngForディレクティブで定義したheroと同じです。

さて、AppComponentクラスは現在hero属性を持っていますが、今後はユーザが一覧から選択したヒーロを表示するのでこの属性は不要になります。代わりにselectedHero属性を設けます:

- hero: Hero = {
-   id: 1,
-   name: 'Windstorm'
- };
+ selectedHero: Hero;
}

ユーザがクリックするまでヒーローの詳細表示は不要なので、selectedHeroは宣言するだけで初期化は行いません。

次にonSelect()メソッドを追加して、selectedHero属性をユーザがクリックしたheroに設定します:

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

さて、現時点ではテンプレートは古いheroとバインドしています。そこで今回作成したselectedHeroに代わりにバインドさせます:

  template: `<h1>{{title}}</h1>
    <h2>私のヒーロー一覧</h2>
    <ul class="heroes">
      <li *ngFor="let hero of heroes" (click)="onSelect(hero)">
        <span class="badge">{{hero.id}}</span>{{hero.name}}
      </li>
    </ul>
    <div>
      <input [(ngModel)]="selectedHero.name">
    </div>
    <div><label>id: </label>{{selectedHero.id}}</div>
    <div><label>name: </label>{{selectedHero.name}}</div>
  `,

この時点でブラウザをリロードするとヒーロー一覧が表示されないはずです。 アプリが読み込まれた時点ではselectedHeroは定義されていません。 定義されていないのにselectedHero.nameを参照しているため、エラーが出ているのです:

f:id:thebaker:20171008203503p:plain

ヒーローが選択されるまでselectedHeroへの参照をDOMに含めてはなりません。そこで、selectedHeroが値をもってない場合はDOMから除外するよう、 <div>で括り、<div>*ngIfのディレクティブを付与します:

  template: `<h1>{{title}}</h1>
    <h2>私のヒーロー一覧</h2>
    <ul class="heroes">
      <li *ngFor="let hero of heroes" (click)="onSelect(hero)">
        <span class="badge">{{hero.id}}</span>{{hero.name}}
      </li>
    </ul>
    <div *ngIf="selectedHero">
      <div>
        <input [(ngModel)]="selectedHero.name">
      </div>
      <div><label>id: </label>{{selectedHero.id}}</div>
      <div><label>name: </label>{{selectedHero.name}}</div>
    </div>
  `,

これでブラウザをリロードすると次のようになります:

f:id:thebaker:20171008203848p:plain

ユーザがヒーローを選択してない場合、ngIfディレクティブは子要素をDOMから除外します。 ユーザがヒーローを選択すると、子要素をDOMに含めネストされたバインディングを評価します。

次に、ユーザが選択したヒーローがどれであるかをわかりやすくします。 app.component.tsのテンプレートにおいて、<li>に次のバインドを追加します:

[class.selected]="hero === selectedHero"

hero===selectedHerotrueの場合はCSSとしてselectedクラスを付与します。 falseの場合はクラスを除きます。

ブラウザをリロードし、ヒーローを選択するとそのヒーローの色が変わるはずです:

f:id:thebaker:20171008204512p:plain

本章では次のことを行いました: * ヒーローの一覧を表示する * ヒーローを選択可能にし、選択したヒーローの詳細を表示する * ngIfngForディレクティブの利用方法を学習

参考までに、現時点のソースを記載します:

import { Component } from '@angular/core';

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

const HeRoEs: Hero[] = [
  { id: 11, name: 'ビアンカ' },
  { id: 12, name: 'フローラ' },
  { id: 13, name: 'デボラ'   }
];

@Component({
  selector: 'app-root',
  template: `<h1>{{title}}</h1>
    <h2>私のヒーロー一覧</h2>
    <ul class="heroes">
      <li *ngFor="let hero of heroes" (click)="onSelect(hero)" [class.selected]="hero === selectedHero">
        <span class="badge">{{hero.id}}</span>{{hero.name}}
      </li>
    </ul>
    <div *ngIf="selectedHero">
      <div>
        <input [(ngModel)]="selectedHero.name">
      </div>
      <div><label>id: </label>{{selectedHero.id}}</div>
      <div><label>name: </label>{{selectedHero.name}}</div>
    </div>
  `,
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'Tour of Heroes';
  selectedHero: Hero;
  heroes = HeRoEs; // heroes: ではない点に注意

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

Angularの公式チュートリアル「Tour of Heroes」:第2章「The Hero Editor」

元のサイトはこちら。かなり端折っていますが一応日本語版です。

第2章ではアプリを実際に作ります。 チュートリアルでは開発をローカルで進めていますが私はWindowsユーザでローカル開発が苦痛なのでAmazon Linux上で進めます。

アプリの作成

cd
ng new angular-tour-of-heroes

これでangular-tour-of-heroesディレクトリ以下にアプリのファイルが生成されます

アプリの起動

念のため起動して動くかどうか確認しましょう:

cd angular-tour-of-heroes
ng serve --host=0.0.0.0 --public=グローバルIP

ブラウザでhttp://グローバルIP:4200/をアクセスするとQuickStartと同じ画面が表示されるはずです。

ターミナルをもう1つ開く

ターミナルをもう1つ開きます。なぜもう1つ開くかというと、1つ目はng serveが実行していてソース編集に使えないためです。 こちらでアプリのappディレクトリに移動します:

cd ~
cd angular-tour-of-heroes/src/app

app.component.tsの編集

まず、アプリで表示しているHTMLを変更します。 app.component.tsを開くと、次のようになっているはずです:

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'app';
}

まず、templateUrlの行を消し、代わりにtemplateを定義します。 その際、バッククオートを使ってタグを括ってください(シングルクオートでも動作しますが)

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `<h1>{{title}}</h1><h2>{{hero}} details!</h2>`,
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'app';
}

次にAppComponent classの中において、titleを定義していますが、これを変更し、かつ、heroという変数を追加します:

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `<h1>{{title}}</h1><h2>{{hero}} details!</h2>`,
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'Tour of Heroes';
  hero  = 'Windstorm';
}

これで保存します。1つ目のターミナルでコンパイルが走りますので、エラーがなければブラウザを開いてhttp://グローバルIP:4200/を確認します:

f:id:thebaker:20171007224849p:plain

templateの中の{{ }}はAngularの補完バインドシンタックスです。 コンポーネント内のtitleとhero属性をHTMLのタグの中で文字列として表現します。

Heroオブジェクトの作成

我々のヒーローにIDと名前を与えることにします。app.component.tsにHeroクラスを追記します:

import { Component } from '@angular/core';

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

@Component({
  selector: 'app-root',
  template: `<h1>{{title}}</h1><h2>{{hero}} details!</h2>`,
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'Tour of Heroes';
  hero  = 'Windstorm';
}

次に、AppComponentクラスの中で、hero属性をHero型に変更します。その際、id=1、name=Windstormで初期化します:

import { Component } from '@angular/core';

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

@Component({
  selector: 'app-root',
  template: `<h1>{{title}}</h1><h2>{{hero}} details!</h2>`,
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'Tour of Heroes';
  hero: Hero = {
    id: 1,
    name: 'Windstorm'
  };
}

ここでブラウザを開き再読込をすると次のような画面になるはずです:

f:id:thebaker:20171007225921p:plain

タイトルは正常に表示されますが、ヒーローの名前は[object Object]となっています。 これは、AppComponentにおける heroをstringからオブジェクトに変えたにも関わらず、 templateにおいて {{ hero }}と参照しているためです。

そこで、templateで名前を参照するよう、{{ hero }} から {{ hero.name }} に変更します:

import { Component } from '@angular/core';

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

@Component({
  selector: 'app-root',
  template: `<h1>{{title}}</h1><h2>{{hero.name}} details!</h2>`,
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'Tour of Heroes';
  hero: Hero = {
    id: 1,
    name: 'Windstorm'
  };
}

ブラウザを再読込すると、名前が正しく表示されるようになっているはずです。

次に、ヒーローのidとnameを表示するためのHTMLをtemplateに定義します。その際、HTMLを複数行をまたいで記載します。`(バックティック)を使用しているのは、複数行の記載を可能にするためです。これはES2015/TypeScriptのテンプレートリテラルの機能です。

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

編集後のソースはこうなります:

import { Component } from '@angular/core';

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

@Component({
  selector: 'app-root',
  template: `<h1>{{title}}</h1>
    <h2>{{hero.name}} details!</h2>
    <div><label>id: </label>{{hero.id}}</div>
    <div><label>name: </label>{{hero.name}}</div>
  `,
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'Tour of Heroes';
  hero: Hero = {
    id: 1,
    name: 'Windstorm'
  };
}

ブラウザをリロードするとこのようになっているはずです:

f:id:thebaker:20171007231138p:plain

次に、ヒーローの名前を編集可能にします。編集した名前は即座に表示に反映させたいとします。

そこでtemplateの中の名前を表示する部分を以下のように変更します:

    - <h2>{{hero.name}} details!</h2>
    + <div>
    +   <label>name: </label>
    +   <input [(ngModel)]="hero.name">
    + </div>

ngModelは、hero.name属性をこのテキストボックスにバインドします。テキストボックスへの変更はhero.name属性に反映され、hero.name属性への変更はテキストボックスに反映されます。

さて、このままではブラウザを開いても何も表示されないはずです。Chromeの場合、Ctrl+Shift+Iを教えてデベロッパーコンソールを開いてみてください。次のエラーが表示されているはずです:

f:id:thebaker:20171007231834p:plain

原因はngModelです。これはAngularにおける妥当な記述ですが標準では読み込まれてないのです。 そこで、app.module.tsファイルを編集します。

まず、3行目あたりに次を追記します:

import { FormsModule}    from '@angular/forms'; // <-- ngModelが含まれている場所

さらに@NgModuleディレクティブのimportsに次を追記します:

    FormsModule // <-- [(ngModel)]とバインドする前にFormsModuleをインポートする
import { BrowserModule } from '@angular/platform-browser';
import { NgModule }      from '@angular/core';
import { FormsModule}    from '@angular/forms'; // <-- ngModelが含まれている場所

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule // <-- [(ngModel)]とバインドする前にFormsModuleをインポートする
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

これでブラウザを開きリロードすると次の画面が表示されると思います。 入力欄を編集すると、編集結果がnameの後に表示されるはずです:

importを記載し、かつ、importsに記載している理由は、私の推測ですが、 最初のimportはTypeScriptにおけるライブラリの追加。そして2つめのimportsが Angularにおけるライブラリの追加なのだと思います。

f:id:thebaker:20171007232528p:plain

まとめ

本章では次を行いました:

  • {{ }} を使ってアプリのタイトルとHeroオブジェクトの属性をHTMLの中に読み込んだ
  • ES2015のテンプレートリテラル機能を使って複数行に渡るテンプレートを作った
  • ngModelディレクティブを通じて、<input>に双方向のデータバインディングを行い、ヒーローの名前の表示と編集を可能にした
  • なお、ngModelディレクティブはhero.nameにバインドしている全ての場所を更新します。

AngularのQuickStart

Angularを触ってみるため公式チュートリアルをやってみました。日本語版がないのでここに備忘録を兼ねて纏めていきます。  今回はまず、QuickStartです。私はAmazon Linuxを使っています。

QuickStartの実施

所要時間:約15分

Amazon Linuxのバージョン(参考)

Amazon Linux AMI 2017.09.0 (HVM), SSD Volume Type - ami-2a69be4c

node.jsのインストール

cd /tmp
curl --silent --location https://rpm.nodesource.com/setup_8.x | sudo bash -
sudo yum -y install nodejs
sudo yum -y install gcc-c++ make

node.jsのバージョン確認

QuickStartには最低でもnode.6.9.xとnpm3.x.xが必要と書いてあります。 

node -v
v8.6.0

npm -v
5.3.0

npmのデフォルトディレクトリ変更

Amazon Linux以外では不要かもしれません。 参考URL

cd
mkdir .npm-global
npm config set prefix '~/.npm-global'
vi .bash_profile

PATHを通す

- PATH=$PATH:$HOME/.local/bin:$HOME/bin
+ PATH=~/.npm-global/bin:$PATH:$HOME/.local/bin:$HOME/bin

.bash_profileを再読込してパスを有効化

source .bash_profile

Angular

インストール

npm install -g @angular/cli

QuickStartのアプリ作成

ng new my-app

アプリのビルドとウェブサーバ起動

cd my-app
ng serve --host=0.0.0.0 --public=グローバルIPもしくはFQDN

EC2の設定変更

4200番ポートへの接続を許可する

image.png

確認

http://グローバルIP:4200/

をブラウザで開く。

こちらの画面が出れば、QuickStart完了です:

f:id:thebaker:20171007185515p:plain

 

 

高い開発効率のための必要条件とは?

自分がプログラマになる前は、こう想像していた:

  1. 仕様が明確である
  2. 納期に余裕がある
  3. 最先端のツールを使える
  4. 椅子と机が使いやすい
  5. ディスプレイが何枚もある

実際にプログラムを書いて納品してみてこう感じた:

  1. 仕様が明確だがガチガチではない(実装側の都合に応じて仕様をある程度、柔軟に変更できる)
  2. 納期が遠すぎない
  3. 最先端ではなくても、自分にとって慣れた使いやすいツールを使える
  4. 開発環境をすぐに用意できる
  5. お金の心配が少ない・無い

仕様についてだが、幸い、仕様書をきっちり書いてくれるお客様に恵まれた。要求仕様書に加え、データベース設計まで行ってくれているのである。カラム名は使っているフレームワーク(CakePHP3)の命名規則に合わせたかったため、相談して変えさせてもらった。開発効率があがるだけでなく、保守性もあがるのでとてもありがたい。

 

納期だが、僕の場合、あまり遠すぎるとやる気がでない。ここは要改善点で、毎日の歯磨きのように仕事をする癖をつける(習慣化)すればよいのだと思う。

 

ツールは、慣れているかということでgitを使わせてもらった。後日、svnとの二重管理に移行しなければならなかったのが痛いが、ほとんどの開発を慣れているgitでできたのはありがたい。

 

開発環境については、お客様が用意すると時間がかかる場合が多い。今回はEC2で自分で作った。イメージ取ったり、作り直したりの自由自在が嬉しい。

 

最後にお金だが、ここは、ここはプロジェクトが伸びたため(自分の開発が遅れたので文句はいえない)入金が結果的に後ろだおしになる。するとお金が心配になって開発に集中できない。お客様は前金で一部を払うと言ってくれてたのを見え張って断ったのが間違いだった。外注さんを使うと先にお金がでていくので、次回からは、前金を貰える場合はもらおう(別途、会社を作って借りるというのも考えている)