デザイナーのためのAngularチュートリアル

一緒に働くデザイナーのために書いた記事です。[Angular](https://angular.jp/) で作ったアプリのスタイルの書き方をチュートリアル形式で学んでいきます。

> Angular では スタイルの記述方式を選択・変更する事ができます。今回は *SCSS* を選択したのでファイルの拡張子が *.scss* となっています。

# セットアップ

チュートリアルに合わせて node のバージョンを変更します。
node のバージョンマネージャー [nvm](https://github.com/nvm-sh/nvm) を使っているなら下記コマンドでバージョンを変更できます。

```bash
$ nvm install v12.13.0
$ nvm use v12.13.0
$ node -v
> v12.13.0 # OK!
```

チュートリアル用の GitHub リポジトリをローカルに持ってきます。

```bash
$ git@github.com:ringtail003/handson-angular-css.git
$ cd handson-angular-css
```

パッケージをインストールして Angular アプリを起動します。

```bash
# パッケージのインストール
$ npm ci

# Angularアプリの起動
$ npm start

# ブラウザで開く
$ open http://localhost:4200
```

こんな画面が表示されればチュートリアルを始める準備完了です。

<img width="400" src="https://user-images.githubusercontent.com/15980747/68537499-51346a00-03a8-11ea-8377-7e5d4adf1c36.png">

# チュートリアルの前に基礎知識

Angular のフロントエンド開発では、いくつものパーツを作りそれを組み合わせてひとつのページを構成します。それぞれのパーツは「コンポーネント」と呼び、コンポーネントごとに分割されたフォルダ構造をしています。

セットアップしたチュートリアルのフォルダをエディタで開いてみてください。`src/app/components` フォルダにいくつかのコンポーネントが存在していますね。これらはフォルダ名と同じタグで HTML にマークアップする事ができます。

<img width="300" src="https://user-images.githubusercontent.com/15980747/68537660-38798380-03ab-11ea-8fed-b8e5dcde6755.png">

チュートリアル用に用意したアプリでは、これらのコンポーネントを使ってブラウザに下記のようなタグが展開されます。

```HTML
<tree>
  <twig>
    <leaf1></leaf1>
    <leaf2></leaf2>
  <twig>
    
  <twig>
    <leaf1></leaf1>
    <leaf2></leaf2>
  <twig>
</tree>
```

階層関係を示すために **tree（木） / twig（枝） / leaf（葉）** というコンポーネントの名前にしてみました。この木には2本の枝があって、それぞれ2枚の葉っぱがあります。

```bash
└── tree
    ├── twig
    │   ├── leaf1
    │   └── leaf2
    ├── twig
    │   ├── leaf1
    │   └── leaf2
```

エディタに戻って `src/app/components` にあるフォルダをどれか開いてみてください。 `html` `scss` `spec.ts` `ts` のというファイルが並んでいますね。これがひとつのコンポーネントを構成する基本のファイルです。これらのファイルのうち `.scss` がデザイナーの領域です。

<img width="300" src="https://user-images.githubusercontent.com/15980747/68537745-b12d0f80-03ac-11ea-84b6-8bd88cffb727.png">

またチュートリアルを進める上で **青枠で囲ったファイル** を編集する事になりますので、迷子になったらこの図を見に来てください。

<img width="300" src="https://user-images.githubusercontent.com/15980747/68537641-f94b3280-03aa-11ea-91a5-020d27853c98.png">

# さあ始めよう

ここまで問題ないでしょうか？
エラーでアプリが起動しないとか困った事があれば、右隣に座っているやさしそうなエンジニアに聞いてみてください。

それではチュートリアル行ってみましょう！
15分程度で終わるはずです。

> チュートリアル完了後のソースはこちらです。うまく動かない時、参考にしてください。
> https://github.com/ringtail003/handson-angular-css/tree/handson-goal

# 1) コンポーネントにスタイルを設定してみよう

階層の根っこにある `<tree>` コンポーネントにスタイルを設定してみましょう。

```HTML
<tree> <!--ここ★-->
  <twig>
    <leaf1></leaf1>
    <leaf2></leaf2>
  <twig>
    
  <twig>
    <leaf1></leaf1>
    <leaf2></leaf2>
  <twig>
</tree>
```

ブラウザに「tree」と表示されているテキストは `.html` を見ると `<p>tree</p>` だという事が分かります。`.scss` を下記のように編集してフォントの色を変更してみましょう。

```diff
// src/app/components/tree/tree.component.scss
+ p {
+   color: #fff;
+ }
```

ファイルを保存するとブラウザは勝手にリロードします。リロード後の画面では `<tree>` コンポーネントのフォントの色が変わるはずです。

<img width="400" src="https://user-images.githubusercontent.com/15980747/68537966-5ac1d000-03b0-11ea-983e-63d7ad03b69e.png">

## Note: スタイルのスコープ

ところで、この画面の HTML の構造を見てみると `<tree>` コンポーネントだけでなく `<twig>` や `<leaf>` も `<p>` タグを持っている事が分かります。どうして `<tree>` 以外の `<p>` タグにはスタイルが適用されないのでしょうか？

<img width="400" alt="スクリーンショット 2019-11-10 11 56 30" src="https://user-images.githubusercontent.com/15980747/68538037-64980300-03b1-11ea-9f78-8164e40b65f3.png">

理由は **{コンポーネント名}.component.scss に書かれたスタイルをカプセル化し、そのコンポーネントだけに適用する** という Angular の仕組みによるものです。

特定の要素にだけスタイルが当たるように、よくこんなスタイルの書き方をしますよね。

```scss
.user-profile-box {
  color: blue;
}

.user {
  div.profile {
    .button[active] {
      color: red;
    }
  }
}
```

Angular の場合は、スタイルがバッティングしないように、クラス名をあてがったり階層を指定する必要はありません。 `<tree>` のスタイルを設定する時は `tree.component.html` と `tree.component.scss` の内容だけ気にすれば良いのです。

あちこちのコンポーネントで `p { color: red; }` のように書かれていたとしても、それぞれ別のスタイルとして扱われ他のコンポーネントに影響を与えないからです。

# 2) コンポーネント自身のタグにスタイルを設定してみよう

さきほどの例ではコンポーネントが持っている `<p>` に対してスタイルを設定しました。今度はコンポーネント自身を示すタグ（ `<tree>` とか `<twig>` ）に対してスタイルを設定してみましょう。特定のタグを `display: table-cell` で並べたいなど、配置に関するものに使えるはずです。

例として階層の末端の `<leaf1>` `<leaf2>` にスタイルを設定してみます。

```HTML
<tree>
  <twig>
    <leaf1></leaf1> <!--ここ★-->
    <leaf2></leaf2> <!--ここ★-->
  <twig>
    
  <twig>
    <leaf1></leaf1> <!--ここ★-->
    <leaf2></leaf2> <!--ここ★-->
  <twig>
</tree>
```

`<leaf1>` `<leaf2>` それぞれの `.scss` で自分自身のタグ（ `<leaf1>` `<leaf2>` ）のスタイルを設定するには **:host という疑似クラスセレクターを使用** します。

```diff
// src/app/components/leaf1/leaf1.component.scss

// <leaf1> に対するスタイル定義
+ :host {
+   display: inline-block;
+ }
```

```diff
// src/app/components/leaf２/leaf2.component.scss

// <leaf2> に対するスタイル定義
+ :host {
+   display: inline-block;
+ }
```

ブラウザの画面はこのように変わるはずです。

<img width="400" alt="1" src="https://user-images.githubusercontent.com/15980747/68538307-c9edf300-03b5-11ea-86ea-d8c629e8847d.png">

# 3) コンポーネント自身のクラスに応じてスタイルを設定してみよう

次は `<leaf1 class="active">` のように、コンポーネントに与えられたクラスに対してスタイルを設定してみます。

```HTML
<tree>
  <twig>
    <leaf1></leaf1> <!--ここ★-->
    <leaf2></leaf2> <!--ここ★-->
  <twig>
    
  <twig>
    <leaf1></leaf1> <!--ここ★-->
    <leaf2></leaf2> <!--ここ★-->
  <twig>
</tree>
```

クリックした時に `class=""` と `class="active"` が切り替わるようにしたいので、階層関係の親である `<twig>` のコードをちょっと変更します。

下記のコードを何も考えずにコピペしてください。

```diff
// src/app/components/twig/twig.component.ts
export class TwigComponent implements OnInit {
+  activeLeaf = null;

  constructor() { }

  ngOnInit() {
  }

+  activate(no: number) {
+    this.activeLeaf = no;
+  }

+  isActive(no: number) {
+    return no === this.activeLeaf;
+  }
}
```

```diff
// src/app/components/twig/twig.component.html
<div>
  <p>twig</p>
- <leaf1></leaf1>
- <leaf2></leaf2>
+  <leaf1
+    [class.active]="isActive(1)"
+    (click)="activate(1)"
+  ></leaf1>
+  <leaf2
+    [class.active]="isActive(2)"
+    (click)="activate(2)"
+  ></leaf2>
</div>
```

ブラウザでエラーが出たらコピペをどこかミスしています。さっきセットアップを手伝ってくれた右隣のエンジニアに聞いてみてください。「えぇ、また？」って顔をされたら「あっ解決しました！」って答えつつ、もうひとつ右隣のさらにやさしそうなエンジニアに聞いてみてください。

コピペしたコードのおかげで画面の「leaf1」「leaf2」の枠をクリックすると `class="active"` がスイッチするようになりました。では active な時のスタイルを設定しましょう。

```diff
// src/app/components/leaf1/leaf1.component.scss
+ :host(.active p) {
+   background: yellow;
+ }
```

```diff
// src/app/components/leaf2/leaf2.component.scss
+ :host(.active p) {
+   background: yellow;
+ }
```

ブラウザの画面で「leaf1」「leaf2」の枠をクリックしてみてください。クラスに応じてスタイルが変わるはずです。

<img width="400" src="https://user-images.githubusercontent.com/15980747/68538655-8bf3cd80-03bb-11ea-9e38-a061e2007931.png">

# 4) 親コンポーネントから子コンポーネントのスタイルを操作してみよう

ここまでチュートリアルを進めてみて「leaf1 と leaf2 って結局同じスタイル書いてんじゃん、効率悪いし面倒くせ( `д´)ｹｯ」って思いましたよね。絶対そう思ってますよね。私にはあなたの心が見えます。

それでは今度は、配下の子コンポーネントに一斉に適用するスタイルを書いてみましょう。

```HTML
<tree>
  <twig> <!--ここ★-->
    <leaf1></leaf1>
    <leaf2></leaf2>
  <twig>
    
  <twig> <!--ここ★-->
    <leaf1></leaf1>
    <leaf2></leaf2>
  <twig>
</tree>
```

`<twig>` コンポーネントは `<leaf1>` `<leaf2>` を持っています。クリック操作によって `<leaf1 class="active">` のように変化するため、クラス指定でスタイルを書いてみましょう。

```diff
// src/app/components/twig/twig.component.scss

// 自身の持っているタグのうち active クラスが付いたものに適用
+ .active {
+   color: orange;
+ }
```

どうですか！一斉に適用する事もできちゃうんですよ！！

<img width="400" src="https://user-images.githubusercontent.com/15980747/68538662-b0e84080-03bb-11ea-8502-e275212eace4.png">

# 5) スタイルのスコープを突破してみよう

親コンポーネントから子コンポーネントのスタイルを操作するもうひとつの方法として `encapsulation` を指定するというやり方もあります。

```HTML
<tree>
  <twig> <!--ここ★-->
    <leaf1></leaf1>
    <leaf2></leaf2>
  <twig>
    
  <twig> <!--ここ★-->
    <leaf1></leaf1>
    <leaf2></leaf2>
  <twig>
</tree>
```

エンジニアの領域に手を入れる事になりますが、たった2行なので勇気を振り絞ってコピペしてください。

```diff
// src/app/components/twig/twig.component.ts
- import { Component, OnInit } from '@angular/core';
+ import { Component, OnInit, ViewEncapsulation } from '@angular/core';

@Component({
  selector: 'twig',
  templateUrl: './twig.component.html',
-  styleUrls: ['./twig.component.scss']
+  styleUrls: ['./twig.component.scss'], // 末尾カンマを忘れずに
+  encapsulation: ViewEncapsulation.None,
})
```

```diff
// src/app/components/twig/twig.component.scss
+ div {
+   font-weight: bold;
+   color: gray;
+ }
```

`ViewEncapsulation` は Angular でスタイルのカプセル化の方法を指定するものです。

- `Emulated` （デフォルト）Shadow DOM をエミュレートしカプセル化が行われます。
- `None` カプセル化を行いません。
- `Native` `ShadowDom` ブラウザが提供する Shadow DOM を使用します。非対応ブラウザではエラーになります。Native は Shadow DOM 標準化前のベンダごとの実装を使用するようです。

コピペコードでは `None` を指定したので、`<twig>` コンポーネントに限りカプセル化は一切行われなくなりました。ブラウザを確認すると `<twig>` とその配下のコンポーネントの `<div>` のフォントが一斉に変わるはずです。

<img width="400" src="https://user-images.githubusercontent.com/15980747/68540103-29f19300-03d0-11ea-943b-8e0fc12cbf1a.png">

# 6) テーマに応じたスタイルを設定してみよう

デザイナーならばアプリのデザインのテーマを作ったり購入して使う事もあると思います。今度はテーマに応じてちょこっと調整するためのスタイルを設定しましょう。

```HTML
<tree>
  <twig>
    <leaf1></leaf1> <!--ここ★-->
    <leaf2></leaf2> <!--ここ★-->
  <twig>
    
  <twig>
    <leaf1></leaf1> <!--ここ★-->
    <leaf2></leaf2> <!--ここ★-->
  <twig>
</tree>
```

まず架空のテーマ `theme-funky` クラスをアプリのベースとなる HTML ファイルに設定しておきます。

```diff
// src/index.html
- <body>
+ <body class="theme-funky">
  <app-root></app-root>
</body>
```

次に `<leaf1>` `<leaf2>` にスタイルを設定します。**:host-context 疑似クラスセレクター** を使うと DOM ツリーを body タグまで遡って CSS クラスやタグを検索する事ができます。

```diff
// src/app/components/leaf1/leaf1.component.scss

// 祖先要素に theme-funky クラスが存在すれば適用
+ :host-context(.theme-funky) p::before {
+   content: "🌵"
+ }
```

```diff
// src/app/components/leaf２/leaf2.component.scss

// 祖先要素に theme-funky クラスが存在すれば適用
+ :host-context(.theme-funky) p::before {
+   content: "🍀"
+ }
```

ブラウザを確認すると文字がちょっとファンキーになっているはずです。

<img width="400" alt="1" src="https://user-images.githubusercontent.com/15980747/68540118-7ccb4a80-03d0-11ea-9e84-f121029a060f.png">

`index.html` に書いたテーマを別のものに変更すると、ファンキーさは消えます。

```diff
// src/index.html
- <body class="theme-funky">
+ <body class="theme-serious">
  <app-root></app-root>
</body>
```

## Note: テーマの切り替え

画面にドロップダウンを設置してアプリのユーザーが任意にテーマを切り替える事もあるかもしれません。その場合はソースコードで分岐したほうが楽なので、エンジニアに依頼してください。

```diff
// src/app/app.component.ts
+ import { HostBinding } from '@angular/core';

export class AppComponent {
+   @HostBinding('class') get themeClass() {
+     return userProfile.theme || null; // theme-funky | theme-serious | null
+   }
```

> 併用した場合は `@HostBinding` が優先されるため `<body>` の指定はフォールバックとして利用できます。

# 7) アプリケーション全体のスタイルを設定してみよう

時にはアプリケーション全体で共通して使うスタイルを書くこともあるでしょう。例えばバリデーションエラーを示す `class="invalid"` など、一箇所にスタイルを集約しておきたいですよね。

`src` のすぐ下の `styles.scss` はそのためのファイルです。ここにスタイルを追加してみましょう。

```diff
// src/styles.scss
+ div {
+   border: solid 1px gray;
+ }
```

ブラウザを確認すると階層に関係なく全ての `<div>` に枠線が表示されるはずです。

<img width="400" src="https://user-images.githubusercontent.com/15980747/68539252-cb261c80-03c3-11ea-83f8-9936a1137a06.png">

以上でチュートリアルは終わりです！お疲れさまでした。

# おわりに

チュートリアルが完了した画面を改めて見てください。サンプルとは言え、まあひどい出来のデザインですね。

<img width="400" src="https://user-images.githubusercontent.com/15980747/68539327-17259100-03c5-11ea-83c0-105581c82046.png">

私はスタイルを当てる作業が嫌いなんです。オサレなものを作ろうと思う純粋な心とはうらはらに、いつもゴミみたいな画面が出来上がってひどい自己嫌悪に陥るからです。チュートリアルを終えて Angular で CSS/SCSS をガンガン書く準備ができたデザイナーさんへ、最後にメッセージを残して締めくくりたいと思います。

<img width="800" src="https://user-images.githubusercontent.com/15980747/68539384-3244d080-03c6-11ea-9f9a-d951d72cb101.png">