第3回：ログイン画面を作る（データバインディング）

[目次：Webアプリ開発ロードマップ](https://qiita.com/radiance1104/private/7c5b04ce4cf8ec7bd708)
# 第3回：ログイン画面を作る（データバインディング）
今回はログイン画面を作成していきます。

## Angularを構成する要素
まずAngularを構成する要素について説明します。
Angularでは主にコンポーネントとサービスを使いアプリケーションを作成していきます。
コンポーネントは画面や部品を構成し、1画面には必ず1つ以上のコンポーネントを使用します。
しかし、コンポーネントで保持するデータは他のコンポーネントから直接参照できません。
また、コンポーネントが保持するデータはコンポーネントが消える時に一緒に消えてしまいます。
そういうときにサービスにデータを保持させ、複数のコンポーネントでデータを共有することができます。サービスはアプリケーションが終了するまでデータを保持し続けます。
また、同じ種類のコンポーネントを部品として複数生成することができますが、同じ種類のサービスはアプリケーション内に一つしか生成されません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202428/9612e7b6-739b-d227-249c-af728daead6f.png)

## Visual Studio Codeをインストールする
ソースコードのエディタには、何でも良いのですが今回は無料のVisual Studio Codeを利用します。
[公式サイト](https://azure.microsoft.com/ja-jp/products/visual-studio-code/)からインストールしてください。

## Visual Studio Codeでプロジェクトフォルダを開く
Visual Studio Codeを起動し、サイドメニューのExplorerからOpen Folderをクリックし、プロジェクトのフォルダを開いてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202428/575dce0c-575a-c11f-5a84-0e9047374bae.png)

## ログイン画面のコンポーネントを追加する
1) src/app/の下に、componentsフォルダとLonginComponentを追加します。
フォルダの位置を確認してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202428/47b8c914-0e5e-176c-6410-4fc9a455b72e.png)

2) 下記のコマンドを実行し、コンポーネントを生成します。
gはgenerateの略です。

```
$ ng g component components/sign-in
CREATE src/app/components/sign-in/sign-in.component.scss (0 bytes)
CREATE src/app/components/sign-in/sign-in.component.html (20 bytes)
CREATE src/app/components/sign-in/sign-in.component.spec.ts (621 bytes)
CREATE src/app/components/sign-in/sign-in.component.ts (272 bytes)
UPDATE src/app/app.module.ts (482 bytes)
```

フォルダ内に4つのファイルが生成されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202428/506f8817-7406-eae5-257c-70911a986ff9.png)

## コンポーネントのファイルについて

| ファイル名 | 説明 |
| --- | --- |
| sign-in.component.html | コンポーネントのhtmlテンプレート |
| sign-in.component.scss | コンポーネントのスタイル |
| sign-in.component.spec.ts | 自動試験のファイル |
| sign-in.component.ts | TypeScriptで書かれたコンポーネントの本体 |

sign-in.component.tsの@Componentの設定を確認してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202428/f1442d23-fe41-f2bb-07a7-0c6e5d4fa1ce.png)

selectorは他のコンポーネントから呼び出すときに利用する名称、
templateUrl、styleUrlsでhtmlやscssファイルを関連付けています。
templateUrlやstyleUrlsは記載方法が複数あり、ファイルを指定せず直接記載する方法もあります。
そのため、最低限sign-in.component.tsがあればコンポーネントとして成立します。

## モジュールを確認する
src/app/app.module.tsを開き、更新内容を確認してください。
赤線の2行が自動的に更新され、アプリとコンポーネントの関連付けがされています。
先ほどの`$ ng g component`コマンドを実行しなくとも、この行を記載すればコンポーネントを追加できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202428/1a0116ec-b9ab-afd7-d2f8-b012bc5ae11c.png)

## ルーティングを設定する
ログイン画面を表示するため、SignInComponentにパスを関連付ます。
src/app/app-routing.module.tsを開き、下記の赤い部分のように修正を行います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202428/4fa4e8db-e1dd-e43e-0d1e-2831b63ebf86.png)

```
import { SignInComponent } from './components/sign-in/sign-in.component';
```

この1行は`./components/sign-in/sign-in.component`ファイルから`SignInComponent`クラスをインポートするという意味です。

```
{path: '', redirectTo: 'sign-in', pathMatch: 'full'},
```

この1行はパス指定なしで呼び出された時に、/sign-inにリダイレクトするという意味です。
http://localhost:4200/にアクセスするとhttp://localhost:4200/sign-inにリダイレクトされます。

```
{path: 'sign-in', component: SignInComponent},
```

この1行はsign-inが呼び出されたときにSignInComponentが表示されるという意味です。

## src/app/app.component.htmlのサンプル部分を削除する
app.component.htmlが自動生成されたときにサンプルが含まれているので削除します。
最後の`<router-outlet></router-outlet>`以外は不要なので削除してください。
この<router-outlet>の中に、app-routing.module.tsで指定したコンポーネントが挿し代わって表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202428/374edce9-3f9d-5fca-e2a7-767004bfcbd5.png)

## ログインページの表示を確認する。
ブラウザで`http://localhost:4200`を表示するとログインページが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202428/57440799-0fbd-066e-bef2-06b6316b13f2.png)

## ログインページをカスタマイズする
### htmlファイルの修正
ログインページにメールアドレス、パスワードの入力欄、サインインボタンを入れます。
sign-in.component.htmlを下記のように修正します。

```
<div id="sign-in">
  <div id="pane">
    <label>fakebook</label>
    <input type="email" placeholder="email">
    <input type="password" placeholder="password">
    <button>Sign in</button>
  </div>
</div>
```

`<xxx></xxx>`をHTMLタグ(tag)と呼びます。
id="xxx"やtype="xxx"を属性(attribute)と呼びます。英語の直訳です。
`<input>`タグのtype属性に"email"を指定するとメールアドレスで利用する文字だけが入力できるようになります。"password"を指定すると、伏字になります。
placeholder属性は何も入力されていないときに表示されるグレーの文字です。
id属性はcssやjsから操作するときに指定する任意のキーワードです。

画像のような画面が表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202428/74678e54-bde2-6fda-86c2-11e10d1e2989.png)

### tsファイルの修正
次に、入力されたメールアドレスをプログラムで扱いたいので、TypeScriptに変数を用意します。
sign-in.component.tsのSignInComponentクラスにemailプロパティとpasswordプロパティを追加します。初期値は空文字('')とします。

```
export class SignInComponent implements OnInit {
  email = '';
  password = '';

  constructor() { }
```

### htmlとTypeScriptを繋ぐ(データバインディング)
sign-in.component.htmlに下記のように修正します。
[(ngModel)]="xxx"と`<label>{{xxx}}</label>`を追加しています。

```
<div id="sign-in">
  <div id="pane">
    <label>fakebook</label>
    <input type="email" placeholder="email" [(ngModel)]="email">
    <input type="password" placeholder="password" [(ngModel)]="password">
    <label>{{email}}</label>
    <button>Sign in</button>
  </div>
</div>
```

このまま動かそうとすると、下記のエラーが発生します。
これは、デフォルトではngModelの機能が読み込まれていないためです。

```
Can't bind to 'ngModel' since it isn't a known property of 'input'.
```

app.module.tsに下記の修正を行い、FormsModuleを読み込ませます。
これでエラーは解消します。

```
〜
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
〜
  imports: [
    BrowserModule,
    AppRoutingModule,
    FormsModule,
  ],
〜
```

メースアドレスの入力欄に適当に文字を入力してみましょう。
すると`<label>`の中の文字が自動的に更新されます。
さきほどhtmlに埋め込んだ`[(ngModel)]="email"`はinputに文字が入力されると、
TypeScript側のemailプロパティの値がリアルタイムに更新されます。
また、`{{email}}`は、TypeScript側のemailプロパティの値がリアルタイムに表示されます。
これをデータバインディングといいます。

`<label>{{email}}</label>`の部分は今後不要なので、削除しておきましょう。

### スタイルで見た目を飾る
最後にscssファイルを更新し、見た目を飾ります。

#### リセットCSSを行う
まず一番最初に行うことは、アプリ共通のcssにリセットCSSを適用させることです。
ブラウザによってデフォルトで指定されているスタイルが異なるため、表示を合わせる必要があります。
リセットCSSを適用することでブラウザ間の誤差を小さくすることができます。
src/styles.scssに下記を記載します。インターネットで"reset css"を検索すると出てきますので、探してみてください。

```
/*
html5doctor.com Reset Stylesheet
v1.6.1
Last Updated: 2010-09-17
Author: Richard Clark - http://richclarkdesign.com
Twitter: @rich_clark
*/

html, body, div, span, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
abbr, address, cite, code,
del, dfn, em, img, ins, kbd, q, samp,
small, strong, sub, sup, var,
b, i,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, figcaption, figure,
footer, header, hgroup, menu, nav, section, summary,
time, mark, audio, video {
    margin:0;
    padding:0;
    border:0;
    outline:0;
    font-size:100%;
    vertical-align:baseline;
    background:transparent;
}

body {
    line-height:1;
}

article,aside,details,figcaption,figure,
footer,header,hgroup,menu,nav,section {
    display:block;
}

nav ul {
    list-style:none;
}

blockquote, q {
    quotes:none;
}

blockquote:before, blockquote:after,
q:before, q:after {
    content:'';
    content:none;
}

a {
    margin:0;
    padding:0;
    font-size:100%;
    vertical-align:baseline;
    background:transparent;
}

/* change colours to suit your needs */
ins {
    background-color:#ff9;
    color:#000;
    text-decoration:none;
}

/* change colours to suit your needs */
mark {
    background-color:#ff9;
    color:#000;
    font-style:italic;
    font-weight:bold;
}

del {
    text-decoration: line-through;
}

abbr[title], dfn[title] {
    border-bottom:1px dotted;
    cursor:help;
}

table {
    border-collapse:collapse;
    border-spacing:0;
}

/* change border colour to suit your needs */
hr {
    display:block;
    height:1px;
    border:0;  
    border-top:1px solid #cccccc;
    margin:1em 0;
    padding:0;
}

input, select {
    vertical-align:middle;
}

html, body {
  height: 100%;
}
```

また、デフォルトではhtmlとbodyの高さが100%になっていないため、下記のスタイルも追加しておきます。

```
html, body {
  height: 100%;
}
```

#### ログイン画面のスタイルを設定する
スタイルは好きなように指定してよいので指定しておいてください。
下記はサンプルです。

```
#login {
  width: 100%;
  height: 100%;
  background-color: #1877f2;
  text-align: center;
  #pane {
    width: 300px;
    display: inline-block;
    label {
      color: white;
      font-size: 30px;
    }
    input {
      width: 100%;
      height: 30px;
      border-radius: 6px;
      display: block;
      margin-top: 10px;
      box-sizing: border-box;
    }
    button {
      width: 100%;
      height: 30px;
      border-radius: 6px;
      display: block;
      margin-top: 60px;
    }
  }
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202428/2a47f87e-6a41-86f7-1b19-7305c0c2d5e0.png)

### 課題
下記は仕事でよく指定される仕様です。試してみてください。
* 上下左右で中央寄せ。ブラウザの大きさを拡縮しても常に中央に表示されるようにしてみましょう。
* 文字列の入力制限。パスワードに入力できる文字を半角英数＋記号(/,.)に制限してみましょう。

## 最後に
次回はフィード一覧を作り、画面遷移とディレクティブを学習します。
今回のソースコードは[GitHub](https://github.com/radiance1104/web-learning-loadmap/tree/create-login-page)に入っています。